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 }