1
/* ============================================================
3
* This file is a part of digiKam project
4
* http://www.digikam.org
7
* Description : a widget to display an image preview
9
* Copyright (C) 2006-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
11
* This program is free software; you can redistribute it
12
* and/or modify it under the terms of the GNU General
13
* Public License as published by the Free Software Foundation;
14
* either version 2, or (at your option)
17
* This program is distributed in the hope that it will be useful,
18
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
* GNU General Public License for more details.
22
* ============================================================ */
37
#include <qguardedptr.h>
47
#include "previewwidget.h"
48
#include "previewwidget.moc"
53
class PreviewWidgetPriv
58
tileSize(128), zoomMultiplier(1.2)
69
tileTmpPix = new QPixmap(tileSize, tileSize);
71
tileCache.setMaxCost((10*1024*1024)/(tileSize*tileSize*4));
72
tileCache.setAutoDelete(true);
87
const double zoomMultiplier;
89
QPoint centerZoomPoint;
93
QCache<QPixmap> tileCache;
100
PreviewWidget::PreviewWidget(QWidget *parent)
101
: QScrollView(parent, 0, Qt::WDestructiveClose)
103
d = new PreviewWidgetPriv;
104
d->bgColor.setRgb(0, 0, 0);
105
m_movingInProgress = false;
107
viewport()->setBackgroundMode(Qt::NoBackground);
108
viewport()->setMouseTracking(false);
110
horizontalScrollBar()->setLineStep( 1 );
111
horizontalScrollBar()->setPageStep( 1 );
112
verticalScrollBar()->setLineStep( 1 );
113
verticalScrollBar()->setPageStep( 1 );
115
setFrameStyle(QFrame::GroupBoxPanel|QFrame::Plain);
120
PreviewWidget::~PreviewWidget()
122
delete d->tileTmpPix;
126
void PreviewWidget::setBackgroundColor(const QColor& color)
128
if (d->bgColor == color)
132
viewport()->update();
135
void PreviewWidget::slotReset()
137
d->tileCache.clear();
141
QRect PreviewWidget::previewRect()
143
return d->pixmapRect;
146
int PreviewWidget::tileSize()
151
int PreviewWidget::zoomWidth()
156
int PreviewWidget::zoomHeight()
158
return d->zoomHeight;
161
double PreviewWidget::zoomMax()
166
double PreviewWidget::zoomMin()
171
void PreviewWidget::setZoomMax(double z)
173
d->maxZoom = ceilf(z * 10000.0) / 10000.0;
176
void PreviewWidget::setZoomMin(double z)
178
d->minZoom = floor(z * 10000.0) / 10000.0;
181
bool PreviewWidget::maxZoom()
183
return (d->zoom >= d->maxZoom);
186
bool PreviewWidget::minZoom()
188
return (d->zoom <= d->minZoom);
191
double PreviewWidget::snapZoom(double zoom)
193
// If the zoom value gets changed from d->zoom to zoom
194
// across 50%, 100% or fit-to-window, then return the
195
// the corresponding special value. Otherwise zoom is returned unchanged.
196
double fit = calcAutoZoomFactor(ZoomInOrOut);
197
QValueList<double> snapValues;
198
snapValues.append(0.5);
199
snapValues.append(1.0);
200
snapValues.append(fit);
201
qHeapSort(snapValues);
202
QValueList<double>::const_iterator it;
206
for(it = snapValues.constBegin(); it != snapValues.constEnd(); ++it)
209
if ((d->zoom < z) && (zoom > z))
218
for(it = snapValues.constEnd(); it != snapValues.constBegin(); --it)
221
if ((d->zoom > z) && (zoom < z))
232
void PreviewWidget::slotIncreaseZoom()
234
double zoom = d->zoom * d->zoomMultiplier;
235
zoom = snapZoom(zoom > zoomMax() ? zoomMax() : zoom);
239
void PreviewWidget::slotDecreaseZoom()
241
double zoom = d->zoom / d->zoomMultiplier;
242
zoom = snapZoom(zoom < zoomMin() ? zoomMin() : zoom);
246
void PreviewWidget::setZoomFactorSnapped(double zoom)
248
double fit = calcAutoZoomFactor(ZoomInOrOut);
249
if (fabs(zoom-1.0) < 0.05)
253
if (fabs(zoom-0.5) < 0.05)
257
if (fabs(zoom-fit) < 0.05)
265
void PreviewWidget::setZoomFactor(double zoom)
267
setZoomFactor(zoom, false);
270
void PreviewWidget::setZoomFactor(double zoom, bool centerView)
272
// Zoom using center of canvas and given zoom factor.
274
double oldZoom = d->zoom;
277
if (d->centerZoomPoint.isNull())
279
// center on current center
280
// store old center pos
281
cpx = contentsX() + visibleWidth() / 2.0;
282
cpy = contentsY() + visibleHeight() / 2.0;
284
cpx = ( cpx / d->tileSize ) * floor(d->tileSize / d->zoom);
285
cpy = ( cpy / d->tileSize ) * floor(d->tileSize / d->zoom);
289
// keep mouse pointer position constant
290
// store old content pos
295
// To limit precision of zoom value and reduce error with check of max/min zoom.
296
d->zoom = floor(zoom * 10000.0) / 10000.0;
297
d->zoomWidth = (int)(previewWidth() * d->zoom);
298
d->zoomHeight = (int)(previewHeight() * d->zoom);
300
updateContentsSize();
302
// adapt step size to zoom factor. Overall, using a finer step size than scrollbar default.
303
int step = QMAX(2, 2*lround(d->zoom));
304
horizontalScrollBar()->setLineStep( step );
305
horizontalScrollBar()->setPageStep( step * 10 );
306
verticalScrollBar()->setLineStep( step );
307
verticalScrollBar()->setPageStep( step * 10 );
309
viewport()->setUpdatesEnabled(false);
310
if (d->centerZoomPoint.isNull())
312
cpx = ( cpx * d->tileSize ) / floor(d->tileSize / d->zoom);
313
cpy = ( cpy * d->tileSize ) / floor(d->tileSize / d->zoom);
317
cpx = d->zoomWidth/2.0;
318
cpy = d->zoomHeight/2.0;
321
center((int)cpx, (int)(cpy));
325
cpx = d->zoom * d->centerZoomPoint.x() / oldZoom - d->centerZoomPoint.x() + cpx;
326
cpy = d->zoom * d->centerZoomPoint.y() / oldZoom - d->centerZoomPoint.y() + cpy;
328
setContentsPos((int)cpx, (int)(cpy));
330
viewport()->setUpdatesEnabled(true);
331
viewport()->update();
333
zoomFactorChanged(d->zoom);
336
double PreviewWidget::zoomFactor()
341
bool PreviewWidget::isFitToWindow()
346
void PreviewWidget::fitToWindow()
349
updateContentsSize();
350
zoomFactorChanged(d->zoom);
351
viewport()->update();
354
void PreviewWidget::toggleFitToWindow()
356
d->autoZoom = !d->autoZoom;
365
zoomFactorChanged(d->zoom);
368
updateContentsSize();
369
viewport()->update();
372
void PreviewWidget::toggleFitToWindowOr100()
374
// If the current zoom is 100%, then fit to window.
381
setZoomFactor(1.0, true);
385
void PreviewWidget::updateAutoZoom(AutoZoomMode mode)
387
d->zoom = calcAutoZoomFactor(mode);
388
d->zoomWidth = (int)(previewWidth() * d->zoom);
389
d->zoomHeight = (int)(previewHeight() * d->zoom);
391
zoomFactorChanged(d->zoom);
394
double PreviewWidget::calcAutoZoomFactor(AutoZoomMode mode)
396
if (previewIsNull()) return d->zoom;
398
double srcWidth = previewWidth();
399
double srcHeight = previewHeight();
400
double dstWidth = contentsRect().width();
401
double dstHeight = contentsRect().height();
403
double zoom = QMIN(dstWidth/srcWidth, dstHeight/srcHeight);
404
// limit precision as above
405
zoom = floor(zoom * 10000.0) / 10000.0;
406
if (mode == ZoomInOrOut)
407
// fit to available space, scale up or down
410
// ZoomInOnly: accept that an image is smaller than available space, dont scale up
411
return QMIN(1.0, zoom);
414
void PreviewWidget::updateContentsSize()
416
viewport()->setUpdatesEnabled(false);
418
if (visibleWidth() > d->zoomWidth || visibleHeight() > d->zoomHeight)
421
int centerx = contentsRect().width()/2;
422
int centery = contentsRect().height()/2;
423
int xoffset = int(centerx - d->zoomWidth/2);
424
int yoffset = int(centery - d->zoomHeight/2);
425
xoffset = QMAX(xoffset, 0);
426
yoffset = QMAX(yoffset, 0);
428
d->pixmapRect = QRect(xoffset, yoffset, d->zoomWidth, d->zoomHeight);
432
d->pixmapRect = QRect(0, 0, d->zoomWidth, d->zoomHeight);
435
d->tileCache.clear();
437
viewport()->setUpdatesEnabled(true);
440
void PreviewWidget::setContentsSize()
442
resizeContents(d->zoomWidth, d->zoomHeight);
445
void PreviewWidget::resizeEvent(QResizeEvent* e)
449
QScrollView::resizeEvent(e);
454
updateContentsSize();
456
// No need to repaint. its called
457
// automatically after resize
459
// To be sure than corner widget used to pan image will be hide/show
460
// accordinly with resize event.
461
zoomFactorChanged(d->zoom);
464
void PreviewWidget::viewportPaintEvent(QPaintEvent *e)
467
er = QRect(QMAX(er.x() - 1, 0),
469
QMIN(er.width() + 2, contentsRect().width()),
470
QMIN(er.height() + 2, contentsRect().height()));
472
bool antialias = (d->zoom <= 1.0) ? true : false;
474
QRect o_cr(viewportToContents(er.topLeft()), viewportToContents(er.bottomRight()));
477
QRegion clipRegion(er);
478
cr = d->pixmapRect.intersect(cr);
480
if (!cr.isEmpty() && !previewIsNull())
482
clipRegion -= QRect(contentsToViewport(cr.topLeft()), cr.size());
484
QRect pr = QRect(cr.x() - d->pixmapRect.x(), cr.y() - d->pixmapRect.y(),
485
cr.width(), cr.height());
487
int x1 = (int)floor((double)pr.x() / (double)d->tileSize) * d->tileSize;
488
int y1 = (int)floor((double)pr.y() / (double)d->tileSize) * d->tileSize;
489
int x2 = (int)ceilf((double)pr.right() / (double)d->tileSize) * d->tileSize;
490
int y2 = (int)ceilf((double)pr.bottom() / (double)d->tileSize) * d->tileSize;
492
QPixmap pix(d->tileSize, d->tileSize);
494
int step = (int)floor(d->tileSize / d->zoom);
496
for (int j = y1 ; j < y2 ; j += d->tileSize)
498
for (int i = x1 ; i < x2 ; i += d->tileSize)
500
QString key = QString("%1,%2").arg(i).arg(j);
501
QPixmap *pix = d->tileCache.find(key);
507
pix = new QPixmap(d->tileSize, d->tileSize);
508
d->tileCache.insert(key, pix);
515
pix->fill(d->bgColor);
517
sx = (int)floor((double)i / d->tileSize ) * step;
518
sy = (int)floor((double)j / d->tileSize ) * step;
522
paintPreview(pix, sx, sy, sw, sh);
525
QRect r(i, j, d->tileSize, d->tileSize);
526
QRect ir = pr.intersect(r);
527
QPoint pt(contentsToViewport(QPoint(ir.x() + d->pixmapRect.x(),
528
ir.y() + d->pixmapRect.y())));
530
bitBlt(viewport(), pt.x(), pt.y(),
532
ir.x()-r.x(), ir.y()-r.y(),
533
ir.width(), ir.height());
538
QPainter p(viewport());
539
p.setClipRegion(clipRegion);
540
p.fillRect(er, d->bgColor);
543
viewportPaintExtraData();
546
void PreviewWidget::contentsMousePressEvent(QMouseEvent *e)
548
if (!e || e->button() == Qt::RightButton)
551
m_movingInProgress = false;
553
if (e->button() == Qt::LeftButton)
555
emit signalLeftButtonClicked();
557
else if (e->button() == Qt::MidButton)
559
if (visibleWidth() < d->zoomWidth ||
560
visibleHeight() < d->zoomHeight)
562
m_movingInProgress = true;
563
d->midButtonX = e->x();
564
d->midButtonY = e->y();
565
viewport()->repaint(false);
566
viewport()->setCursor(Qt::SizeAllCursor);
571
viewport()->setMouseTracking(false);
574
void PreviewWidget::contentsMouseMoveEvent(QMouseEvent *e)
578
if (e->state() & Qt::MidButton)
580
if (m_movingInProgress)
582
scrollBy(d->midButtonX - e->x(),
583
d->midButtonY - e->y());
584
emit signalContentsMovedEvent(false);
589
void PreviewWidget::contentsMouseReleaseEvent(QMouseEvent *e)
593
m_movingInProgress = false;
595
if (e->button() == Qt::MidButton)
597
emit signalContentsMovedEvent(true);
598
viewport()->unsetCursor();
599
viewport()->repaint(false);
602
if (e->button() == Qt::RightButton)
604
emit signalRightButtonClicked();
608
void PreviewWidget::contentsWheelEvent(QWheelEvent *e)
612
if (e->state() & Qt::ShiftButton)
615
emit signalShowNextImage();
616
else if (e->delta() > 0)
617
emit signalShowPrevImage();
620
else if (e->state() & Qt::ControlButton)
622
// When zooming with the mouse-wheel, the image center is kept fixed.
623
d->centerZoomPoint = e->pos();
624
if (e->delta() < 0 && !minZoom())
626
else if (e->delta() > 0 && !maxZoom())
628
d->centerZoomPoint = QPoint();
632
QScrollView::contentsWheelEvent(e);
635
void PreviewWidget::zoomFactorChanged(double zoom)
637
emit signalZoomFactorChanged(zoom);
640
} // NameSpace Digikam