1 /* This file is part of the KDE project
2    Copyright 2006 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18 */
19 
20 // Local
21 #include "SheetView.h"
22 
23 #include <QCache>
24 #include <QRect>
25 #include <QPainter>
26 #include <QPainterPath>
27 #ifdef CALLIGRA_SHEETS_MT
28 #include <QMutex>
29 #include <QMutexLocker>
30 #include <QReadWriteLock>
31 #include <QReadLocker>
32 #include <QWriteLocker>
33 #endif
34 
35 #include <KoViewConverter.h>
36 
37 #include "CellView.h"
38 #include "calligra_sheets_limits.h"
39 #include "PointStorage.h"
40 #include "RectStorage.h"
41 #include "Region.h"
42 #include "RowColumnFormat.h"
43 #include "RowFormatStorage.h"
44 #include "Sheet.h"
45 
46 using namespace Calligra::Sheets;
47 
48 struct CellPaintData
49 {
CellPaintDataCellPaintData50     CellPaintData(const CellView &cellView, const Cell &cell, const QPointF &coordinate)
51         : cellView(cellView)
52           , cell(cell)
53           , coordinate(coordinate)
54     {}
55     CellView cellView;
56     Cell cell;
57     QPointF coordinate;
58 };
59 
60 class Q_DECL_HIDDEN SheetView::Private
61 {
62 public:
Private()63     Private()
64 #ifdef CALLIGRA_SHEETS_MT
65         : cacheMutex(QMutex::Recursive)
66 #endif
67     {}
68     const Sheet* sheet;
69     const KoViewConverter* viewConverter;
70     QRect visibleRect;
71     QCache<QPoint, CellView> cache;
72 #ifdef CALLIGRA_SHEETS_MT
73     QMutex cacheMutex;
74 #endif
75     QRegion cachedArea;
76     CellView* defaultCellView;
77     // The maximum accessed cell range used for the scrollbar ranges.
78     QSize accessedCellRange;
79     FusionStorage* obscuredInfo;
80     QSize obscuredRange; // size of the bounding box of obscuredInfo
81 #ifdef CALLIGRA_SHEETS_MT
82     QReadWriteLock obscuredLock;
83 #endif
84 
85     PointStorage<bool> highlightedCells;
86     QPoint activeHighlight;
87 #ifdef CALLIGRA_SHEETS_MT
88     QReadWriteLock highlightLock;
89 #endif
90     QColor highlightColor;
91     QColor highlightMaskColor;
92     QColor activeHighlightColor;
93 public:
94     Cell cellToProcess(int col, int row, QPointF& coordinate, QSet<Cell>& processedMergedCells, const QRect& visRect);
95 #ifdef CALLIGRA_SHEETS_MT
96     CellView cellViewToProcess(Cell& cell, QPointF& coordinate, QSet<Cell>& processedObscuredCells,
97                                SheetView* sheetView, const QRect& visRect);
98 #else
99     const CellView& cellViewToProcess(Cell& cell, QPointF& coordinate, QSet<Cell>& processedObscuredCells,
100                                SheetView* sheetView, const QRect& visRect);
101 #endif
102 };
103 
cellToProcess(int col,int row,QPointF & coordinate,QSet<Cell> & processedMergedCells,const QRect & visRect)104 Cell SheetView::Private::cellToProcess(int col, int row, QPointF& coordinate,
105                                        QSet<Cell>& processedMergedCells,
106                                        const QRect& visRect)
107 {
108     Cell cell(sheet, col, row);
109     if (cell.isPartOfMerged()) {
110         cell = cell.masterCell();
111         // if the rect of visible cells contains this master cell, it was already painted
112         if (visRect.contains(cell.cellPosition())) {
113             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
114             return Cell(); // next row
115         }
116         // if the out of bounds master cell was already painted, there's nothing more to do
117         if (processedMergedCells.contains(cell)) {
118             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
119             return Cell(); // next row
120         }
121         processedMergedCells.insert(cell);
122         // take the coordinate of the master cell
123         if (sheet->layoutDirection() == Qt::RightToLeft) {
124             for (int c = cell.column()+1; c <= col; ++c)
125                 coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width());
126         } else {
127             for (int c = cell.column(); c < col; ++c)
128                 coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width());
129         }
130         for (int r = cell.row(); r < row; ++r)
131             coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r));
132     }
133     return cell;
134 }
135 
136 #ifdef CALLIGRA_SHEETS_MT
cellViewToProcess(Cell & cell,QPointF & coordinate,QSet<Cell> & processedObscuredCells,SheetView * sheetView,const QRect & visRect)137 CellView SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate,
138         QSet<Cell>& processedObscuredCells, SheetView* sheetView, const QRect& visRect)
139 #else
140 const CellView& SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate,
141         QSet<Cell>& processedObscuredCells, SheetView* sheetView, const QRect& visRect)
142 #endif
143 {
144     const int col = cell.column();
145     const int row = cell.row();
146     const QPoint cellPos = cell.cellPosition();
147 #ifdef CALLIGRA_SHEETS_MT
148     CellView cellView = sheetView->cellView(col, row);
149 #else
150     const CellView& cellView = sheetView->cellView(col, row);
151 #endif
152     if (sheetView->isObscured(cellPos)) {
153         // if the rect of visible cells contains the obscuring cell, it was already painted
154         if (visRect.contains(sheetView->obscuringCell(cellPos))) {
155             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
156             cell = Cell();
157             return cellView; // next row
158         }
159         cell = Cell(sheet, sheetView->obscuringCell(cellPos));
160         if (processedObscuredCells.contains(cell)) {
161             coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row));
162             cell = Cell();
163             return cellView; // next row
164         }
165         processedObscuredCells.insert(cell);
166         // take the coordinate of the obscuring cell
167         if (sheet->layoutDirection() == Qt::RightToLeft) {
168             for (int c = cell.column()+1; c <= col; ++c)
169                 coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width());
170         } else {
171             for (int c = cell.column(); c < col; ++c)
172                 coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width());
173         }
174         for (int r = cell.row(); r < row; ++r)
175             coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r));
176         // use the CellView of the obscuring cell
177         return sheetView->cellView(cell.column(), cell.row());
178     }
179     return cellView;
180 }
181 
182 
SheetView(const Sheet * sheet)183 SheetView::SheetView(const Sheet* sheet)
184         : QObject(const_cast<Sheet*>(sheet))
185         , d(new Private)
186 {
187     d->sheet = sheet;
188     d->viewConverter = 0;
189     d->visibleRect = QRect(1, 1, 0, 0);
190     d->cache.setMaxCost(10000);
191     d->defaultCellView = createDefaultCellView();
192     d->accessedCellRange =  sheet->usedArea().size().expandedTo(QSize(256, 256));
193     d->obscuredInfo = new FusionStorage(sheet->map());
194     d->obscuredRange = QSize(0, 0);
195     d->highlightMaskColor = QColor(0, 0, 0, 128);
196     d->activeHighlightColor = QColor(255, 127, 0, 128);
197 }
198 
~SheetView()199 SheetView::~SheetView()
200 {
201     delete d->defaultCellView;
202     delete d->obscuredInfo;
203     delete d;
204 }
205 
sheet() const206 const Sheet* SheetView::sheet() const
207 {
208     return d->sheet;
209 }
210 
setViewConverter(const KoViewConverter * viewConverter)211 void SheetView::setViewConverter(const KoViewConverter* viewConverter)
212 {
213     Q_ASSERT(viewConverter);
214     d->viewConverter = viewConverter;
215 }
216 
viewConverter() const217 const KoViewConverter* SheetView::viewConverter() const
218 {
219     Q_ASSERT(d->viewConverter);
220     return d->viewConverter;
221 }
222 
223 #ifdef CALLIGRA_SHEETS_MT
cellView(const QPoint & pos)224 CellView SheetView::cellView(const QPoint& pos)
225 #else
226 const CellView& SheetView::cellView(const QPoint& pos)
227 #endif
228 {
229     return cellView(pos.x(), pos.y());
230 }
231 
232 #ifdef CALLIGRA_SHEETS_MT
cellView(int col,int row)233 CellView SheetView::cellView(int col, int row)
234 #else
235 const CellView& SheetView::cellView(int col, int row)
236 #endif
237 {
238     Q_ASSERT(1 <= col && col <= KS_colMax);
239     Q_ASSERT(1 <= row && col <= KS_rowMax);
240 #ifdef CALLIGRA_SHEETS_MT
241     QMutexLocker ml(&d->cacheMutex);
242 #endif
243     CellView *v = d->cache.object(QPoint(col, row));
244     if (!v) {
245         v = createCellView(col, row);
246         d->cache.insert(QPoint(col, row), v);
247         d->cachedArea += QRect(col, row, 1, 1);
248     }
249 #ifdef CALLIGRA_SHEETS_MT
250     // create a copy as long as the mutex is locked
251     CellView cellViewCopy = *v;
252     return cellViewCopy;
253 #else
254     return *v;
255 #endif
256 }
257 
setPaintCellRange(const QRect & rect)258 void SheetView::setPaintCellRange(const QRect& rect)
259 {
260 #ifdef CALLIGRA_SHEETS_MT
261     QMutexLocker ml(&d->cacheMutex);
262 #endif
263     d->visibleRect = rect & QRect(1, 1, KS_colMax, KS_rowMax);
264     d->cache.setMaxCost(2 * rect.width() * rect.height());
265 }
266 
paintCellRange() const267 QRect SheetView::paintCellRange() const
268 {
269     return d->visibleRect;
270 }
271 
invalidateRegion(const Region & region)272 void SheetView::invalidateRegion(const Region& region)
273 {
274     QRegion qregion;
275     Region::ConstIterator end(region.constEnd());
276     for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
277         qregion += (*it)->rect();
278     }
279     // reduce to the cached area
280     qregion &= d->cachedArea;
281     QVector<QRect> rects = qregion.rects();
282     for (int i = 0; i < rects.count(); ++i)
283         invalidateRange(rects[i]);
284 }
285 
invalidate()286 void SheetView::invalidate()
287 {
288 #ifdef CALLIGRA_SHEETS_MT
289     QMutexLocker ml(&d->cacheMutex);
290 #endif
291     delete d->defaultCellView;
292     d->defaultCellView = createDefaultCellView();
293     d->cache.clear();
294     d->cachedArea = QRegion();
295     delete d->obscuredInfo;
296     d->obscuredInfo = new FusionStorage(d->sheet->map());
297     d->obscuredRange = QSize(0, 0);
298 }
299 
paintCells(QPainter & painter,const QRectF & paintRect,const QPointF & topLeft,CanvasBase *,const QRect & visibleRect)300 void SheetView::paintCells(QPainter& painter, const QRectF& paintRect, const QPointF& topLeft, CanvasBase*, const QRect& visibleRect)
301 {
302     const QRect& visRect = visibleRect.isValid() ? visibleRect : d->visibleRect;
303     // paintRect:   the canvas area, that should be painted; in document coordinates;
304     //              no layout direction consideration; scrolling offset applied;
305     //              independent from painter transformations
306     // topLeft:     the document coordinate of the top left cell's top left corner;
307     //              no layout direction consideration; independent from painter
308     //              transformations
309 
310     // NOTE Stefan: The painting is split into several steps. In each of these all cells in
311     //              d->visibleRect are traversed. This may appear suboptimal at the first look, but
312     //              ensures that the borders are not erased by the background of adjacent cells.
313 
314 // debugSheets << "paintRect:" << paintRect;
315 // debugSheets << "topLeft:" << topLeft;
316 
317     QRegion clipRect(painter.clipRegion());
318     // 0. Paint the sheet background
319     if (!sheet()->backgroundImage().isNull()) {
320         //TODO support all the different properties
321         Sheet::BackgroundImageProperties properties = sheet()->backgroundImageProperties();
322         if( properties.repeat == Sheet::BackgroundImageProperties::Repeat ) {
323             const int firstCol = visRect.left();
324             const int firstRow = visRect.top();
325             const int firstColPosition = d->sheet->columnPosition(firstCol);
326             const int firstRowPosition = d->sheet->rowPosition(firstRow);
327 
328             const int imageWidth = sheet()->backgroundImage().rect().width();
329             const int imageHeight = sheet()->backgroundImage().rect().height();
330 
331             int xBackground = firstColPosition - (firstColPosition % imageWidth);
332             int yBackground = firstRowPosition - (firstRowPosition % imageHeight);
333 
334             const int lastCol = visRect.right();
335             const int lastRow = visRect.bottom();
336             const int lastColPosition = d->sheet->columnPosition(lastCol);
337             const int lastRowPosition = d->sheet->rowPosition(lastRow);
338 
339             while( xBackground < lastColPosition ) {
340                 int y = yBackground;
341                 while( y < lastRowPosition ) {
342                     painter.drawImage(QRect(xBackground, y, imageWidth, imageHeight), sheet()->backgroundImage());
343                     y += imageHeight;
344                 }
345                 xBackground += imageWidth;
346             }
347         }
348     }
349 
350     // 1. Paint the cell background
351 
352     // Handle right-to-left layout.
353     // In an RTL sheet the cells have to be painted at their opposite horizontal
354     // location on the canvas, meaning that column A will be the rightmost column
355     // on screen, column B will be to the left of it and so on. Here we change
356     // the horizontal coordinate at which we start painting the cell in case the
357     // sheet's direction is RTL.
358     const bool rightToLeft = sheet()->layoutDirection() == Qt::RightToLeft;
359     const QPointF startCoordinate(rightToLeft ? paintRect.width() - topLeft.x() : topLeft.x(), topLeft.y());
360     QPointF coordinate(startCoordinate);
361 // debugSheets << "start coordinate:" << coordinate;
362     QSet<Cell> processedMergedCells;
363     QSet<Cell> processedObscuredCells;
364     QList<CellPaintData> cached_cells;
365     for (int col = visRect.left(); col <= visRect.right(); ++col) {
366         if (d->sheet->columnFormat(col)->isHiddenOrFiltered())
367             continue;
368         if (rightToLeft)
369             coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width());
370 // debugSheets <<"coordinate:" << coordinate;
371         for (int row = visRect.top(); row <= visRect.bottom(); ++row) {
372             int lastHiddenRow;
373             if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) {
374                 row = lastHiddenRow;
375                 continue;
376             }
377             // save the coordinate
378             const QPointF savedCoordinate = coordinate;
379             // figure out, if any and which cell has to be painted (may be a master cell)
380             Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect);
381             if (!cell)
382                 continue;
383             // figure out, which CellView to use (may be one for an obscuring cell)
384             CellPaintData cpd(d->cellViewToProcess(cell, coordinate, processedObscuredCells, this, visRect), cell, coordinate);
385             if (!cell)
386                 continue;
387             cpd.cellView.paintCellBackground(painter, clipRect, coordinate);
388             cached_cells.append(cpd);
389             // restore coordinate
390             coordinate = savedCoordinate;
391             coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row));
392         }
393         coordinate.setY(topLeft.y());
394         if (!rightToLeft)
395             coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width());
396     }
397 
398     // 2. Paint the cell content including markers (formula, comment, ...)
399     for (QList<CellPaintData>::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) {
400         it->cellView.paintCellContents(paintRect, painter, clipRect, it->coordinate, it->cell, this);
401     }
402 
403     // 3. Paint the default borders
404     coordinate = startCoordinate;
405     processedMergedCells.clear();
406     for (int col = visRect.left(); col <= visRect.right(); ++col) {
407         if (d->sheet->columnFormat(col)->isHiddenOrFiltered())
408             continue;
409         if (rightToLeft)
410             coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width());
411         for (int row = visRect.top(); row <= visRect.bottom(); ++row) {
412             int lastHiddenRow;
413             if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) {
414                 row = lastHiddenRow;
415                 continue;
416             }
417             // For borders even cells, that are merged in, need to be traversed.
418             // Think of a merged cell with a set border and one its neighbours has a thicker border.
419             // but: also the master cell of a merged cell always needs to be processed
420             const QPointF savedCoordinate = coordinate;
421             Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect);
422             if (!!cell && (cell.column() != col || cell.row() != row)) {
423                 const CellView cellView = this->cellView(cell.cellPosition());
424                 cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate,
425                                              CellView::LeftBorder | CellView::RightBorder |
426                                              CellView::TopBorder | CellView::BottomBorder,
427                                              visRect, cell, this);
428             }
429             coordinate = savedCoordinate;
430             const CellView cellView = this->cellView(col, row);
431             cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate,
432                                          CellView::LeftBorder | CellView::RightBorder |
433                                          CellView::TopBorder | CellView::BottomBorder,
434                                          visRect, Cell(d->sheet, col, row), this);
435             coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row));
436         }
437         coordinate.setY(topLeft.y());
438         if (!rightToLeft)
439             coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width());
440     }
441 
442     // 4. Paint the custom borders, diagonal lines and page borders
443     coordinate = startCoordinate;
444     processedMergedCells.clear();
445     processedObscuredCells.clear();
446     for (int col = visRect.left(); col <= visRect.right(); ++col) {
447         if (d->sheet->columnFormat(col)->isHiddenOrFiltered())
448             continue;
449         if (rightToLeft)
450             coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width());
451         for (int row = visRect.top(); row <= visRect.bottom(); ++row) {
452             int lastHiddenRow;
453             if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) {
454                 row = lastHiddenRow;
455                 continue;
456             }
457             // For borders even cells, that are merged in, need to be traversed.
458             // Think of a merged cell with a set border and one its neighbours has a thicker border.
459             // but: also the master cell of a merged cell always needs to be processed
460             const QPointF savedCoordinate = coordinate;
461             Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect);
462             if (!!cell && (cell.column() != col || cell.row() != row)) {
463                 const CellView cellView = this->cellView(cell.cellPosition());
464                 cellView.paintCellBorders(paintRect, painter, clipRect, coordinate,
465                                           visRect,
466                                           cell, this);
467             }
468             coordinate = savedCoordinate;
469             Cell theCell(sheet(), col, row);
470             const CellView cellView = d->cellViewToProcess(theCell, coordinate, processedObscuredCells, this, visRect);
471             if (!!theCell && (theCell.column() != col || theCell.row() != row)) {
472                 cellView.paintCellBorders(paintRect, painter, clipRect, coordinate,
473                                           visRect,
474                                           theCell, this);
475             }
476             const CellView cellView2 = this->cellView(col, row);
477             coordinate = savedCoordinate;
478             cellView2.paintCellBorders(paintRect, painter, clipRect, coordinate,
479                                       visRect,
480                                       Cell(sheet(), col, row), this);
481             coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row));
482         }
483         coordinate.setY(topLeft.y());
484         if (!rightToLeft)
485             coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width());
486     }
487 
488     // 5. Paint cell highlighting
489     if (hasHighlightedCells()) {
490         QPointF active = activeHighlight();
491         QPainterPath p;
492         const CellPaintData* activeData = 0;
493         for (QList<CellPaintData>::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) {
494             if (isHighlighted(it->cell.cellPosition())) {
495                 p.addRect(it->coordinate.x(), it->coordinate.y(), it->cellView.cellWidth(), it->cellView.cellHeight());
496                 if (it->cell.cellPosition() == active) {
497                     activeData = &*it;
498                 }
499             }
500         }
501         painter.setPen(Qt::NoPen);
502         if (d->highlightColor.isValid()) {
503             painter.setBrush(QBrush(d->highlightColor));
504             painter.drawPath(p);
505         }
506         if (d->highlightMaskColor.isValid()) {
507             QPainterPath base;
508             base.addRect(painter.clipPath().boundingRect().adjusted(-5, -5, 5, 5));
509             p = base.subtracted(p);
510             painter.setBrush(QBrush(d->highlightMaskColor));
511             painter.drawPath(p);
512         }
513 
514         if (activeData && d->activeHighlightColor.isValid()) {
515             painter.setBrush(QBrush(d->activeHighlightColor));
516             painter.setPen(QPen(Qt::black, 0));
517             painter.drawRect(QRectF(activeData->coordinate.x(), activeData->coordinate.y(), activeData->cellView.cellWidth(), activeData->cellView.cellHeight()));
518         }
519     }
520 }
521 
invalidateRange(const QRect & range)522 void SheetView::invalidateRange(const QRect& range)
523 {
524 #ifdef CALLIGRA_SHEETS_MT
525     QMutexLocker ml(&d->cacheMutex);
526 #endif
527     QRegion obscuredRegion;
528     const int right  = range.right();
529     for (int col = range.left(); col <= right; ++col) {
530         const int bottom = range.bottom();
531         for (int row = range.top(); row <= bottom; ++row) {
532             const QPoint p(col, row);
533             if (!d->cache.contains(p))
534                 continue;
535             if (obscuresCells(p) || isObscured(p)) {
536                 obscuredRegion += obscuredArea(p);
537                 obscureCells(p, 0, 0);
538             }
539             d->cache.remove(p);
540         }
541     }
542     d->cachedArea -= range;
543     obscuredRegion &= d->cachedArea;
544     foreach (const QRect& rect, obscuredRegion.rects()) {
545         invalidateRange(rect);
546     }
547 }
548 
obscureCells(const QPoint & position,int numXCells,int numYCells)549 void SheetView::obscureCells(const QPoint &position, int numXCells, int numYCells)
550 {
551 #ifdef CALLIGRA_SHEETS_MT
552     QWriteLocker(&d->obscuredLock);
553 #endif
554     // Start by un-obscuring cells that we might be obscuring right now
555     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(position);
556     if (!pair.first.isNull())
557         d->obscuredInfo->insert(Region(pair.first.toRect()), false);
558     // Obscure the cells
559     if (numXCells != 0 || numYCells != 0)
560         d->obscuredInfo->insert(Region(position.x(), position.y(), numXCells + 1, numYCells + 1), true);
561 
562     QRect obscuredArea = d->obscuredInfo->usedArea();
563     QSize newObscuredRange(obscuredArea.right(), obscuredArea.bottom());
564     if (newObscuredRange != d->obscuredRange) {
565         d->obscuredRange = newObscuredRange;
566         emit obscuredRangeChanged(d->obscuredRange);
567     }
568 }
569 
obscuringCell(const QPoint & obscuredCell) const570 QPoint SheetView::obscuringCell(const QPoint &obscuredCell) const
571 {
572 #ifdef CALLIGRA_SHEETS_MT
573     QReadLocker(&d->obscuredLock);
574 #endif
575     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(obscuredCell);
576     if (pair.first.isNull())
577         return obscuredCell;
578     if (pair.second == false)
579         return obscuredCell;
580     return pair.first.toRect().topLeft();
581 }
582 
obscuredRange(const QPoint & obscuringCell) const583 QSize SheetView::obscuredRange(const QPoint &obscuringCell) const
584 {
585 #ifdef CALLIGRA_SHEETS_MT
586     QReadLocker(&d->obscuredLock);
587 #endif
588     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(obscuringCell);
589     if (pair.first.isNull())
590         return QSize(0, 0);
591     if (pair.second == false)
592         return QSize(0, 0);
593     // Not the master cell?
594     if (pair.first.toRect().topLeft() != obscuringCell)
595         return QSize(0, 0);
596     return pair.first.toRect().size() - QSize(1, 1);
597 }
598 
obscuredArea(const QPoint & cell) const599 QRect SheetView::obscuredArea(const QPoint &cell) const
600 {
601 #ifdef CALLIGRA_SHEETS_MT
602     QReadLocker(&d->obscuredLock);
603 #endif
604     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(cell);
605     if (pair.first.isNull())
606         return QRect(cell, QSize(1, 1));
607     if (pair.second == false)
608         return QRect(cell, QSize(1, 1));
609     // Not the master cell?
610     return pair.first.toRect();
611 }
612 
isObscured(const QPoint & cell) const613 bool SheetView::isObscured(const QPoint &cell) const
614 {
615 #ifdef CALLIGRA_SHEETS_MT
616     QReadLocker(&d->obscuredLock);
617 #endif
618     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(cell);
619     if (pair.first.isNull())
620         return false;
621     if (pair.second == false)
622         return false;
623     // master cell?
624     if (pair.first.toRect().topLeft() == cell)
625         return false;
626     return true;
627 }
628 
obscuresCells(const QPoint & cell) const629 bool SheetView::obscuresCells(const QPoint &cell) const
630 {
631 #ifdef CALLIGRA_SHEETS_MT
632     QReadLocker(&d->obscuredLock);
633 #endif
634     const QPair<QRectF, bool> pair = d->obscuredInfo->containedPair(cell);
635     if (pair.first.isNull())
636         return false;
637     if (pair.second == false)
638         return false;
639     // master cell?
640     if (pair.first.toRect().topLeft() != cell)
641         return false;
642     return true;
643 }
644 
totalObscuredRange() const645 QSize SheetView::totalObscuredRange() const
646 {
647 #ifdef CALLIGRA_SHEETS_MT
648     QReadLocker(&d->obscuredLock);
649 #endif
650     return d->obscuredRange;
651 }
652 
653 #ifdef CALLIGRA_SHEETS_MT
defaultCellView() const654 CellView SheetView::defaultCellView() const
655 #else
656 const CellView& SheetView::defaultCellView() const
657 #endif
658 {
659     return *d->defaultCellView;
660 }
661 
updateAccessedCellRange(const QPoint & location)662 void SheetView::updateAccessedCellRange(const QPoint& location)
663 {
664     const QSize cellRange = d->accessedCellRange.expandedTo(QSize(location.x(), location.y()));
665     if (d->accessedCellRange != cellRange || location.isNull()) {
666         d->accessedCellRange = cellRange;
667         const int col = qMin(KS_colMax, cellRange.width() + 10);
668         const int row = qMin(KS_rowMax, cellRange.height() + 10);
669         const double width = sheet()->columnPosition(col) + sheet()->columnFormat(col)->width();
670         const double height = sheet()->rowPosition(row) + sheet()->rowFormats()->rowHeight(row);
671         emit visibleSizeChanged(QSizeF(width, height));
672     }
673 }
674 
createDefaultCellView()675 CellView* SheetView::createDefaultCellView()
676 {
677     return new CellView(this);
678 }
679 
createCellView(int col,int row)680 CellView* SheetView::createCellView(int col, int row)
681 {
682     return new CellView(this, col, row);
683 }
684 
isHighlighted(const QPoint & cell) const685 bool SheetView::isHighlighted(const QPoint &cell) const
686 {
687 #ifdef CALLIGRA_SHEETS_MT
688     QReadLocker(&d->highlightLock);
689 #endif
690     return d->highlightedCells.lookup(cell.x(), cell.y());
691 }
692 
setHighlighted(const QPoint & cell,bool isHighlighted)693 void SheetView::setHighlighted(const QPoint &cell, bool isHighlighted)
694 {
695 #ifdef CALLIGRA_SHEETS_MT
696     QWriteLocker(&d->highlightLock);
697 #endif
698     bool oldHadHighlights = d->highlightedCells.count() > 0;
699     bool oldVal;
700     if (isHighlighted) {
701         oldVal = d->highlightedCells.insert(cell.x(), cell.y(), true);
702     } else {
703         oldVal = d->highlightedCells.take(cell.x(), cell.y());
704     }
705     if (oldHadHighlights != (d->highlightedCells.count() > 0)) {
706         invalidate();
707     } else if (oldVal != isHighlighted) {
708         invalidateRegion(Region(cell));
709     }
710 }
711 
hasHighlightedCells() const712 bool SheetView::hasHighlightedCells() const
713 {
714 #ifdef CALLIGRA_SHEETS_MT
715     QReadLocker(&d->highlightLock);
716 #endif
717     return d->highlightedCells.count() > 0;
718 }
719 
clearHighlightedCells()720 void SheetView::clearHighlightedCells()
721 {
722 #ifdef CALLIGRA_SHEETS_MT
723     QWriteLocker(&d->highlightLock);
724 #endif
725     d->activeHighlight = QPoint();
726     if (d->highlightedCells.count()) {
727         d->highlightedCells.clear();
728         invalidate();
729     }
730 }
731 
activeHighlight() const732 QPoint SheetView::activeHighlight() const
733 {
734     return d->activeHighlight;
735 }
736 
setActiveHighlight(const QPoint & cell)737 void SheetView::setActiveHighlight(const QPoint &cell)
738 {
739     QPoint oldVal = d->activeHighlight;
740     d->activeHighlight = cell;
741     if (oldVal != cell) {
742         Region r;
743         if (!oldVal.isNull()) r.add(oldVal);
744         if (!cell.isNull()) r.add(cell);
745         invalidateRegion(r);
746     }
747 }
748 
setHighlightColor(const QColor & color)749 void SheetView::setHighlightColor(const QColor &color)
750 {
751     d->highlightColor = color;
752     if (hasHighlightedCells()) {
753         invalidate();
754     }
755 }
756 
setHighlightMaskColor(const QColor & color)757 void SheetView::setHighlightMaskColor(const QColor &color)
758 {
759     d->highlightMaskColor = color;
760     if (hasHighlightedCells()) {
761         invalidate();
762     }
763 }
764 
setActiveHighlightColor(const QColor & color)765 void SheetView::setActiveHighlightColor(const QColor &color)
766 {
767     d->activeHighlightColor = color;
768     if (hasHighlightedCells()) {
769         invalidate();
770     }
771 }
772