1
// vim: set tabstop=4 shiftwidth=4 expandtab:
3
Gwenview: an image viewer
4
Copyright 2011 AurĆ©lien GĆ¢teau <agateau@kde.org>
6
This program is free software; you can redistribute it and/or
7
modify it under the terms of the GNU General Public License
8
as published by the Free Software Foundation; either version 2
9
of the License, or (at your option) any later version.
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
GNU General Public License for more details.
16
You should have received a copy of the GNU General Public License
17
along with this program; if not, write to the Free Software
18
Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
22
#include "abstractimageview.moc"
28
#include <kmodifierkeyinfo.h>
29
#include <kstandarddirs.h>
34
#include <QGraphicsSceneMouseEvent>
39
static const int UNIT_STEP = 16;
41
struct AbstractImageViewPrivate {
47
KModifierKeyInfo* mModifierKeyInfo;
49
Document::Ptr mDocument;
51
bool mEnlargeSmallerImages;
59
void adjustImageOffset(Verbosity verbosity = Notify)
61
QSizeF zoomedDocSize = q->documentSize() * mZoom;
62
QSizeF viewSize = q->boundingRect().size();
64
qMax((viewSize.width() - zoomedDocSize.width()) / 2, 0.),
65
qMax((viewSize.height() - zoomedDocSize.height()) / 2, 0.)
67
if (offset != mImageOffset) {
68
mImageOffset = offset;
69
if (verbosity == Notify) {
70
q->onImageOffsetChanged();
75
void adjustScrollPos(Verbosity verbosity = Notify)
77
setScrollPos(mScrollPos, verbosity);
80
void setScrollPos(const QPointF& _newPos, Verbosity verbosity = Notify)
82
QSizeF zoomedDocSize = q->documentSize() * mZoom;
83
QSizeF viewSize = q->boundingRect().size();
85
qBound(0., _newPos.x(), zoomedDocSize.width() - viewSize.width()),
86
qBound(0., _newPos.y(), zoomedDocSize.height() - viewSize.height())
88
if (newPos != mScrollPos) {
89
QPointF oldPos = mScrollPos;
91
if (verbosity == Notify) {
92
q->onScrollPosChanged(oldPos);
94
// No verbosity test: we always notify the outside world about
96
QMetaObject::invokeMethod(q, "scrollPosChanged");
100
void setupZoomCursor()
102
QString path = KStandardDirs::locate("appdata", "cursors/zoom.png");
103
QPixmap cursorPixmap = QPixmap(path);
104
mZoomCursor = QCursor(cursorPixmap, 11, 11);
108
AbstractImageView::AbstractImageView(QGraphicsItem* parent)
109
: QGraphicsWidget(parent)
110
, d(new AbstractImageViewPrivate)
113
d->mEnlargeSmallerImages = false;
115
d->mZoomToFit = true;
116
d->mImageOffset = QPointF(0, 0);
117
d->mScrollPos = QPointF(0, 0);
118
d->mModifierKeyInfo = new KModifierKeyInfo(this);
119
connect(d->mModifierKeyInfo, SIGNAL(keyPressed(Qt::Key, bool)), SLOT(updateCursor()));
120
setFocusPolicy(Qt::WheelFocus);
121
setFlag(ItemIsSelectable);
122
d->setupZoomCursor();
126
AbstractImageView::~AbstractImageView()
131
Document::Ptr AbstractImageView::document() const
136
void AbstractImageView::setDocument(Document::Ptr doc)
141
setZoom(computeZoomToFit());
145
QSizeF AbstractImageView::documentSize() const
147
return d->mDocument ? d->mDocument->size() : QSizeF();
150
qreal AbstractImageView::zoom() const
155
void AbstractImageView::setZoom(qreal zoom, const QPointF& _center, AbstractImageView::UpdateType updateType)
157
if (updateType == UpdateIfNecessary && qFuzzyCompare(zoom, d->mZoom)) {
160
qreal oldZoom = d->mZoom;
164
if (_center == QPointF(-1, -1)) {
165
center = boundingRect().center();
171
We want to keep the point at viewport coordinates "center" at the same
172
position after zooming. The coordinates of this point in image coordinates
173
can be expressed like this:
176
imagePointAtOldZoom = ------------------
180
imagePointAtZoom = ---------------
185
imagePointAtOldZoom = imagePointAtZoom
187
oldScroll + center scroll + center
188
<=> ------------------ = ---------------
192
<=> scroll = ------- (oldScroll + center) - center
198
It's useless to take the new offset in consideration because if a direction
199
of the new offset is not 0, we won't be able to center on a specific point
202
QPointF oldScroll = scrollPos() - imageOffset();
204
QPointF scroll = (zoom / oldZoom) * (oldScroll + center) - center;
206
d->adjustImageOffset(AbstractImageViewPrivate::Silent);
207
d->setScrollPos(scroll, AbstractImageViewPrivate::Silent);
209
zoomChanged(d->mZoom);
212
bool AbstractImageView::zoomToFit() const
214
return d->mZoomToFit;
217
void AbstractImageView::setZoomToFit(bool on)
221
setZoom(computeZoomToFit());
223
// We do not set zoom to 1 if zoomToFit is off, this is up to the code
224
// calling us. It may went to zoom to some other level and/or to zoom on
225
// a particular position
226
zoomToFitChanged(d->mZoomToFit);
229
void AbstractImageView::resizeEvent(QGraphicsSceneResizeEvent* event)
231
QGraphicsWidget::resizeEvent(event);
233
// Set zoom calls adjustImageOffset(), but only if the zoom changes.
234
// If the view is resized but does not cause a zoom change we want the
235
// offset to be adjusted so we call adjustImageOffset() from there as
237
d->adjustImageOffset(AbstractImageViewPrivate::Silent);
238
setZoom(computeZoomToFit());
240
d->adjustImageOffset();
241
d->adjustScrollPos();
245
qreal AbstractImageView::computeZoomToFit() const
247
QSizeF docSize = documentSize();
248
if (docSize.isEmpty()) {
251
QSizeF viewSize = boundingRect().size();
252
qreal fitWidth = viewSize.width() / docSize.width();
253
qreal fitHeight = viewSize.height() / docSize.height();
254
qreal fit = qMin(fitWidth, fitHeight);
255
if (!d->mEnlargeSmallerImages) {
261
void AbstractImageView::mousePressEvent(QGraphicsSceneMouseEvent* event)
263
QGraphicsItem::mousePressEvent(event);
264
if (event->button() == Qt::MiddleButton) {
265
bool value = !zoomToFit();
273
if (event->modifiers() & Qt::ControlModifier) {
274
if (event->button() == Qt::LeftButton) {
275
zoomInRequested(event->pos());
277
} else if (event->button() == Qt::RightButton) {
278
zoomOutRequested(event->pos());
283
d->mLastDragPos = event->pos();
287
void AbstractImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
289
QGraphicsItem::mouseMoveEvent(event);
291
QPointF mousePos = event->pos();
292
QPointF newScrollPos = d->mScrollPos + d->mLastDragPos - mousePos;
295
qreal maxWidth = boundingRect().width();
296
qreal maxHeight = boundingRect().height();
297
// We need a margin because if the window is maximized, the mouse may not
298
// be able to go past the bounding rect.
299
// The mouse get placed 1 pixel before/after the margin to avoid getting
300
// considered as needing to wrap the other way in next mouseMoveEvent
301
// (because we don't check the move vector)
302
const int margin = 5;
303
if (mousePos.x() <= margin) {
304
mousePos.setX(maxWidth - margin - 1);
305
} else if (mousePos.x() >= maxWidth - margin) {
306
mousePos.setX(margin + 1);
308
if (mousePos.y() <= margin) {
309
mousePos.setY(maxHeight - margin - 1);
310
} else if (mousePos.y() >= maxHeight - margin) {
311
mousePos.setY(margin + 1);
314
// Set mouse pos (Hackish translation to screen coords!)
315
QPointF screenDelta = event->screenPos() - event->pos();
316
QCursor::setPos((mousePos + screenDelta).toPoint());
318
d->mLastDragPos = mousePos;
319
d->setScrollPos(newScrollPos);
323
void AbstractImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
325
QGraphicsItem::mouseReleaseEvent(event);
326
if (!d->mLastDragPos.isNull()) {
327
d->mLastDragPos = QPointF();
332
void AbstractImageView::keyPressEvent(QKeyEvent* event)
335
qreal pageStep = boundingRect().height();
337
if (event->modifiers() & Qt::ShiftModifier) {
338
unitStep = pageStep / 2;
340
unitStep = UNIT_STEP;
342
switch (event->key()) {
344
delta.setX(-unitStep);
347
delta.setX(unitStep);
350
delta.setY(-unitStep);
353
delta.setY(unitStep);
356
delta.setY(-pageStep);
358
case Qt::Key_PageDown:
359
delta.setY(pageStep);
362
d->setScrollPos(QPointF(d->mScrollPos.x(), 0));
365
d->setScrollPos(QPointF(d->mScrollPos.x(), documentSize().height() * zoom()));
370
d->setScrollPos(d->mScrollPos + delta);
373
QPointF AbstractImageView::imageOffset() const
375
return d->mImageOffset;
378
QPointF AbstractImageView::scrollPos() const
380
return d->mScrollPos;
383
void AbstractImageView::setScrollPos(const QPointF& pos)
385
d->setScrollPos(pos);
388
QPointF AbstractImageView::mapToView(const QPointF& imagePos) const
390
return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos;
393
QPoint AbstractImageView::mapToView(const QPoint& imagePos) const
395
return mapToView(QPointF(imagePos)).toPoint();
398
QRectF AbstractImageView::mapToView(const QRectF& imageRect) const
401
mapToView(imageRect.topLeft()),
402
imageRect.size() * zoom()
406
QRect AbstractImageView::mapToView(const QRect& imageRect) const
409
mapToView(imageRect.topLeft()),
410
imageRect.size() * zoom()
414
QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const
416
return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom;
419
QPoint AbstractImageView::mapToImage(const QPoint& viewPos) const
421
return mapToImage(QPointF(viewPos)).toPoint();
424
QRectF AbstractImageView::mapToImage(const QRectF& viewRect) const
427
mapToImage(viewRect.topLeft()),
428
viewRect.size() / zoom()
432
QRect AbstractImageView::mapToImage(const QRect& viewRect) const
435
mapToImage(viewRect.topLeft()),
436
viewRect.size() / zoom()
440
void AbstractImageView::setEnlargeSmallerImages(bool value)
442
d->mEnlargeSmallerImages = value;
444
setZoom(computeZoomToFit());
448
void AbstractImageView::updateCursor()
450
if (d->mModifierKeyInfo->isKeyPressed(Qt::Key_Control)) {
451
setCursor(d->mZoomCursor);
453
if (d->mLastDragPos.isNull()) {
454
setCursor(Qt::OpenHandCursor);
456
setCursor(Qt::ClosedHandCursor);