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

0001 /* This file is part of the KDE project
0002    Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
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 #include "PixmapCachingSheetView.h"
0021 
0022 #include "CellView.h"
0023 #include "SheetsDebug.h"
0024 
0025 #include "../Sheet.h"
0026 #include "../part/CanvasBase.h"
0027 
0028 #include <QCache>
0029 #include <QPainter>
0030 
0031 
0032 #ifdef CALLIGRA_SHEETS_MT
0033 #include <ThreadWeaver/Job>
0034 #include <ThreadWeaver/Weaver>
0035 #endif
0036 
0037 using namespace Calligra::Sheets;
0038 
0039 #define TILESIZE 256
0040 
0041 class PixmapCachingSheetView::Private
0042 {
0043 public:
0044     Private(PixmapCachingSheetView* q) : q(q) {}
0045     PixmapCachingSheetView* q;
0046     QCache<int, QPixmap> tileCache;
0047     QPointF lastScale;
0048 
0049     QPixmap* getTile(const Sheet* sheet, int x, int y, CanvasBase* canvas);
0050 };
0051 
0052 #ifdef CALLIGRA_SHEETS_MT
0053 class TileDrawingJob : public ThreadWeaver::Job
0054 #else
0055 class TileDrawingJob
0056 #endif
0057 {
0058 public:
0059     TileDrawingJob(const Sheet* sheet, SheetView* sheetView, CanvasBase* canvas, const QPointF& scale, int x, int y);
0060     ~TileDrawingJob();
0061     void run();
0062 private:
0063     const Sheet* m_sheet;
0064     SheetView* m_sheetView;
0065 public:
0066     CanvasBase* m_canvas;
0067     QPointF m_scale;
0068     int m_x;
0069     int m_y;
0070     QImage m_image;
0071 };
0072 
0073 TileDrawingJob::TileDrawingJob(const Sheet *sheet, SheetView* sheetView, CanvasBase* canvas, const QPointF& scale, int x, int y)
0074     : m_sheet(sheet), m_sheetView(sheetView), m_canvas(canvas), m_scale(scale), m_x(x), m_y(y)
0075     , m_image(TILESIZE, TILESIZE, QImage::Format_ARGB32)
0076 {
0077     debugSheets << "new job for " << x << "," << y << " " << m_scale;
0078 }
0079 
0080 TileDrawingJob::~TileDrawingJob()
0081 {
0082     debugSheets << "end job for " << m_x << "," << m_y << " " << m_scale;
0083 }
0084 
0085 void TileDrawingJob::run()
0086 {
0087     debugSheets << "start draw for " << m_x << "," << m_y << " " << m_scale;
0088     const bool rtl = m_sheet->layoutDirection() == Qt::RightToLeft;
0089 
0090     m_image.fill(QColor(255, 255, 255, 0).rgba());
0091     QPainter pixmapPainter(&m_image);
0092     pixmapPainter.setClipRect(m_image.rect());
0093     pixmapPainter.scale(m_scale.x(), m_scale.y());
0094 
0095     QRect globalPixelRect(QPoint(m_x * TILESIZE, m_y * TILESIZE), QSize(TILESIZE, TILESIZE));
0096     QRectF docRect(
0097             globalPixelRect.x() / m_scale.x(),
0098             globalPixelRect.y() / m_scale.y(),
0099             globalPixelRect.width() / m_scale.x(),
0100             globalPixelRect.height() / m_scale.y()
0101     );
0102 
0103     if (rtl) {
0104         pixmapPainter.translate(docRect.x(), -docRect.y());
0105     } else {
0106         pixmapPainter.translate(-docRect.x(), -docRect.y());
0107     }
0108 
0109     qreal loffset, toffset;
0110     const int left = m_sheet->leftColumn(docRect.left(), loffset);
0111     const int right = m_sheet->rightColumn(docRect.right());
0112     const int top = m_sheet->topRow(docRect.top(), toffset);
0113     const int bottom = m_sheet->bottomRow(docRect.bottom());
0114     QRect cellRect(left, top, right - left + 1, bottom - top + 1);
0115 
0116     debugSheets << globalPixelRect << docRect;
0117     debugSheets << cellRect;
0118 
0119     m_sheetView->SheetView::paintCells(pixmapPainter, docRect, QPointF(loffset, toffset), 0, cellRect);
0120 
0121     //m_image.save(QString("/tmp/tile%1_%2.png").arg(m_x).arg(m_y));
0122     debugSheets << "end draw for " << m_x << "," << m_y << " " << m_scale;
0123 }
0124 
0125 
0126 PixmapCachingSheetView::PixmapCachingSheetView(const Sheet* sheet)
0127     : SheetView(sheet), d(new Private(this))
0128 {
0129     d->tileCache.setMaxCost(128); // number of tiles to cache
0130 }
0131 
0132 PixmapCachingSheetView::~PixmapCachingSheetView()
0133 {
0134     delete d;
0135 }
0136 
0137 void PixmapCachingSheetView::jobDone(ThreadWeaver::Job *tjob)
0138 {
0139 #ifdef CALLIGRA_SHEETS_MT
0140     TileDrawingJob* job = static_cast<TileDrawingJob*>(tjob);
0141     if (job->m_scale == d->lastScale) {
0142         int idx = job->m_x << 16 | job->m_y;
0143         d->tileCache.insert(idx, new QPixmap(QPixmap::fromImage(job->m_image)));
0144         // TODO: figure out what area to repaint
0145         job->m_canvas->update();
0146     }
0147     job->deleteLater();
0148 #else
0149     Q_UNUSED(tjob);
0150 #endif
0151 }
0152 
0153 QPixmap* PixmapCachingSheetView::Private::getTile(const Sheet* sheet, int x, int y, CanvasBase* canvas)
0154 {
0155     int idx = x << 16 | y;
0156     if (tileCache.contains(idx)) return tileCache.object(idx);
0157 
0158 #ifdef CALLIGRA_SHEETS_MT
0159     TileDrawingJob* job = new TileDrawingJob(sheet, q, canvas, lastScale, x, y);
0160     QObject::connect(job, SIGNAL(done(ThreadWeaver::Job*)), q, SLOT(jobDone(ThreadWeaver::Job*)), Qt::QueuedConnection);
0161     ThreadWeaver::Weaver::instance()->enqueue(job);
0162     QPixmap* pm = new QPixmap(TILESIZE, TILESIZE);
0163     pm->fill(QColor(255, 255, 255, 0));
0164     if (tileCache.insert(idx, pm)) {
0165         return pm;
0166     }
0167 #else
0168     TileDrawingJob job(sheet, q, canvas, lastScale, x, y);
0169     job.run();
0170     QPixmap *pm = new QPixmap(QPixmap::fromImage(job.m_image));
0171     if (tileCache.insert(idx, pm)) {
0172         tileCache.insert(idx, pm);
0173         return pm;
0174     }
0175 #endif
0176     return 0;
0177 }
0178 
0179 void PixmapCachingSheetView::paintCells(QPainter& painter, const QRectF& paintRect, const QPointF& topLeft, CanvasBase* canvas, const QRect& visibleRect)
0180 {
0181     if (!canvas) {
0182         SheetView::paintCells(painter, paintRect, topLeft, canvas, visibleRect);
0183         return;
0184     }
0185     // paintRect:   the canvas area, that should be painted; in document coordinates;
0186     //              no layout direction consideration; scrolling offset applied;
0187     //              independent from painter transformations
0188     // topLeft:     the document coordinate of the top left cell's top left corner;
0189     //              no layout direction consideration; independent from painter
0190     //              transformations
0191 
0192     QTransform t = painter.transform();
0193 
0194     // figure out scaling from the transformation... not really perfect, but should work as long as rotation is in 90 degree steps I think
0195     const qreal cos_sx = t.m11();
0196     const qreal sin_sx = t.m12();
0197     const qreal msin_sy = t.m21();
0198     const qreal cos_sy = t.m22();
0199 
0200     const qreal sx = sqrt(cos_sx*cos_sx + sin_sx*sin_sx);
0201     const qreal sy = sqrt(cos_sy*cos_sy + msin_sy*msin_sy);
0202     //const qreal cost = (sx > 1e-10 ? cos_sx / sx : cos_sy / sy);
0203     //const qreal ang = acos(cost);
0204 
0205     QPointF scale = QPointF(sx, sy);
0206     if (scale != d->lastScale) {
0207         d->tileCache.clear();
0208     }
0209     d->lastScale = scale;
0210 
0211     QRect tiles;
0212     const QRect visibleCells = paintCellRange();
0213     const Sheet * s = sheet();
0214     const QPointF bottomRight(s->columnPosition(visibleCells.right() + 1), s->rowPosition(visibleCells.bottom() + 1));
0215     tiles.setLeft(topLeft.x() * sx / TILESIZE);
0216     tiles.setTop(topLeft.y() * sy / TILESIZE);
0217     tiles.setRight((bottomRight.x() * sx + TILESIZE-1) / TILESIZE);
0218     tiles.setBottom((bottomRight.y() * sy + TILESIZE-1) / TILESIZE);
0219 
0220     bool rtl = s->layoutDirection() == Qt::RightToLeft;
0221 
0222     if (rtl) {
0223         for (int x = qMax(0, tiles.left()); x < tiles.right(); x++) {
0224             for (int y = qMax(0, tiles.top()); y < tiles.bottom(); y++) {
0225                 QPixmap *p = d->getTile(s, x, y, canvas);
0226                 if (p) {
0227                     QPointF pt(paintRect.width() - (x+1) * TILESIZE / scale.x(), y * TILESIZE / scale.y());
0228                     QRectF r(pt, QSizeF(TILESIZE / sx, TILESIZE / sy));
0229                     painter.drawPixmap(r, *p, p->rect());
0230                 }
0231             }
0232         }
0233     } else {
0234         for (int x = qMax(0, tiles.left()); x < tiles.right(); x++) {
0235             for (int y = qMax(0, tiles.top()); y < tiles.bottom(); y++) {
0236                 QPixmap *p = d->getTile(s, x, y, canvas);
0237                 if (p) {
0238                     QPointF pt(x * TILESIZE / scale.x(), y * TILESIZE / scale.y());
0239                     QRectF r(pt, QSizeF(TILESIZE / sx, TILESIZE / sy));
0240                     painter.drawPixmap(r, *p, p->rect());
0241                 }
0242             }
0243         }
0244     }
0245 }
0246 
0247 void PixmapCachingSheetView::invalidateRange(const QRect &rect)
0248 {
0249     // TODO: figure out which tiles to invalidate
0250     d->tileCache.clear();
0251 
0252     SheetView::invalidateRange(rect);
0253 }
0254 
0255 void PixmapCachingSheetView::invalidate()
0256 {
0257     d->tileCache.clear();
0258 
0259     SheetView::invalidate();
0260 }