~ubuntu-branches/ubuntu/precise/gwenview/precise-proposed

« back to all changes in this revision

Viewing changes to lib/documentview/abstractimageview.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2011-12-15 14:17:54 UTC
  • mto: This revision was merged to the branch mainline in revision 12.
  • Revision ID: package-import@ubuntu.com-20111215141754-z043hyx69dulbggf
Tags: upstream-4.7.90
ImportĀ upstreamĀ versionĀ 4.7.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// vim: set tabstop=4 shiftwidth=4 expandtab:
 
2
/*
 
3
Gwenview: an image viewer
 
4
Copyright 2011 AurĆ©lien GĆ¢teau <agateau@kde.org>
 
5
 
 
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.
 
10
 
 
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.
 
15
 
 
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.
 
19
 
 
20
*/
 
21
// Self
 
22
#include "abstractimageview.moc"
 
23
 
 
24
// Local
 
25
 
 
26
// KDE
 
27
#include <kdebug.h>
 
28
#include <kmodifierkeyinfo.h>
 
29
#include <kstandarddirs.h>
 
30
#include <kurl.h>
 
31
 
 
32
// Qt
 
33
#include <QCursor>
 
34
#include <QGraphicsSceneMouseEvent>
 
35
 
 
36
namespace Gwenview
 
37
{
 
38
 
 
39
static const int UNIT_STEP = 16;
 
40
 
 
41
struct AbstractImageViewPrivate {
 
42
    enum Verbosity {
 
43
        Silent,
 
44
        Notify
 
45
    };
 
46
    AbstractImageView* q;
 
47
    KModifierKeyInfo* mModifierKeyInfo;
 
48
    QCursor mZoomCursor;
 
49
    Document::Ptr mDocument;
 
50
 
 
51
    bool mEnlargeSmallerImages;
 
52
 
 
53
    qreal mZoom;
 
54
    bool mZoomToFit;
 
55
    QPointF mImageOffset;
 
56
    QPointF mScrollPos;
 
57
    QPointF mLastDragPos;
 
58
 
 
59
    void adjustImageOffset(Verbosity verbosity = Notify)
 
60
    {
 
61
        QSizeF zoomedDocSize = q->documentSize() * mZoom;
 
62
        QSizeF viewSize = q->boundingRect().size();
 
63
        QPointF offset(
 
64
            qMax((viewSize.width() - zoomedDocSize.width()) / 2, 0.),
 
65
            qMax((viewSize.height() - zoomedDocSize.height()) / 2, 0.)
 
66
        );
 
67
        if (offset != mImageOffset) {
 
68
            mImageOffset = offset;
 
69
            if (verbosity == Notify) {
 
70
                q->onImageOffsetChanged();
 
71
            }
 
72
        }
 
73
    }
 
74
 
 
75
    void adjustScrollPos(Verbosity verbosity = Notify)
 
76
    {
 
77
        setScrollPos(mScrollPos, verbosity);
 
78
    }
 
79
 
 
80
    void setScrollPos(const QPointF& _newPos, Verbosity verbosity = Notify)
 
81
    {
 
82
        QSizeF zoomedDocSize = q->documentSize() * mZoom;
 
83
        QSizeF viewSize = q->boundingRect().size();
 
84
        QPointF newPos(
 
85
            qBound(0., _newPos.x(), zoomedDocSize.width() - viewSize.width()),
 
86
            qBound(0., _newPos.y(), zoomedDocSize.height() - viewSize.height())
 
87
        );
 
88
        if (newPos != mScrollPos) {
 
89
            QPointF oldPos = mScrollPos;
 
90
            mScrollPos = newPos;
 
91
            if (verbosity == Notify) {
 
92
                q->onScrollPosChanged(oldPos);
 
93
            }
 
94
            // No verbosity test: we always notify the outside world about
 
95
            // scrollPos changes
 
96
            QMetaObject::invokeMethod(q, "scrollPosChanged");
 
97
        }
 
98
    }
 
99
 
 
100
    void setupZoomCursor()
 
101
    {
 
102
        QString path = KStandardDirs::locate("appdata", "cursors/zoom.png");
 
103
        QPixmap cursorPixmap = QPixmap(path);
 
104
        mZoomCursor = QCursor(cursorPixmap, 11, 11);
 
105
    }
 
106
};
 
107
 
 
108
AbstractImageView::AbstractImageView(QGraphicsItem* parent)
 
109
: QGraphicsWidget(parent)
 
110
, d(new AbstractImageViewPrivate)
 
111
{
 
112
    d->q = this;
 
113
    d->mEnlargeSmallerImages = false;
 
114
    d->mZoom = 1;
 
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();
 
123
    updateCursor();
 
124
}
 
125
 
 
126
AbstractImageView::~AbstractImageView()
 
127
{
 
128
    delete d;
 
129
}
 
130
 
 
131
Document::Ptr AbstractImageView::document() const
 
132
{
 
133
    return d->mDocument;
 
134
}
 
135
 
 
136
void AbstractImageView::setDocument(Document::Ptr doc)
 
137
{
 
138
    d->mDocument = doc;
 
139
    loadFromDocument();
 
140
    if (d->mZoomToFit) {
 
141
        setZoom(computeZoomToFit());
 
142
    }
 
143
}
 
144
 
 
145
QSizeF AbstractImageView::documentSize() const
 
146
{
 
147
    return d->mDocument ? d->mDocument->size() : QSizeF();
 
148
}
 
149
 
 
150
qreal AbstractImageView::zoom() const
 
151
{
 
152
    return d->mZoom;
 
153
}
 
154
 
 
155
void AbstractImageView::setZoom(qreal zoom, const QPointF& _center, AbstractImageView::UpdateType updateType)
 
156
{
 
157
    if (updateType == UpdateIfNecessary && qFuzzyCompare(zoom, d->mZoom)) {
 
158
        return;
 
159
    }
 
160
    qreal oldZoom = d->mZoom;
 
161
    d->mZoom = zoom;
 
162
 
 
163
    QPointF center;
 
164
    if (_center == QPointF(-1, -1)) {
 
165
        center = boundingRect().center();
 
166
    } else {
 
167
        center = _center;
 
168
    }
 
169
 
 
170
    /*
 
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:
 
174
 
 
175
                          oldScroll + center
 
176
    imagePointAtOldZoom = ------------------
 
177
                               oldZoom
 
178
 
 
179
                       scroll + center
 
180
    imagePointAtZoom = ---------------
 
181
                            zoom
 
182
 
 
183
    So we want:
 
184
 
 
185
        imagePointAtOldZoom = imagePointAtZoom
 
186
 
 
187
        oldScroll + center   scroll + center
 
188
    <=> ------------------ = ---------------
 
189
              oldZoom             zoom
 
190
 
 
191
                  zoom
 
192
    <=> scroll = ------- (oldScroll + center) - center
 
193
                 oldZoom
 
194
    */
 
195
 
 
196
    /*
 
197
    Compute oldScroll
 
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
 
200
    in that direction.
 
201
    */
 
202
    QPointF oldScroll = scrollPos() - imageOffset();
 
203
 
 
204
    QPointF scroll = (zoom / oldZoom) * (oldScroll + center) - center;
 
205
 
 
206
    d->adjustImageOffset(AbstractImageViewPrivate::Silent);
 
207
    d->setScrollPos(scroll, AbstractImageViewPrivate::Silent);
 
208
    onZoomChanged();
 
209
    zoomChanged(d->mZoom);
 
210
}
 
211
 
 
212
bool AbstractImageView::zoomToFit() const
 
213
{
 
214
    return d->mZoomToFit;
 
215
}
 
216
 
 
217
void AbstractImageView::setZoomToFit(bool on)
 
218
{
 
219
    d->mZoomToFit = on;
 
220
    if (on) {
 
221
        setZoom(computeZoomToFit());
 
222
    }
 
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);
 
227
}
 
228
 
 
229
void AbstractImageView::resizeEvent(QGraphicsSceneResizeEvent* event)
 
230
{
 
231
    QGraphicsWidget::resizeEvent(event);
 
232
    if (d->mZoomToFit) {
 
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
 
236
        // well.
 
237
        d->adjustImageOffset(AbstractImageViewPrivate::Silent);
 
238
        setZoom(computeZoomToFit());
 
239
    } else {
 
240
        d->adjustImageOffset();
 
241
        d->adjustScrollPos();
 
242
    }
 
243
}
 
244
 
 
245
qreal AbstractImageView::computeZoomToFit() const
 
246
{
 
247
    QSizeF docSize = documentSize();
 
248
    if (docSize.isEmpty()) {
 
249
        return 1;
 
250
    }
 
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) {
 
256
        fit = qMin(fit, 1.);
 
257
    }
 
258
    return fit;
 
259
}
 
260
 
 
261
void AbstractImageView::mousePressEvent(QGraphicsSceneMouseEvent* event)
 
262
{
 
263
    QGraphicsItem::mousePressEvent(event);
 
264
    if (event->button() == Qt::MiddleButton) {
 
265
        bool value = !zoomToFit();
 
266
        setZoomToFit(value);
 
267
        if (!value) {
 
268
            setZoom(1.);
 
269
        }
 
270
        return;
 
271
    }
 
272
 
 
273
    if (event->modifiers() & Qt::ControlModifier) {
 
274
        if (event->button() == Qt::LeftButton) {
 
275
            zoomInRequested(event->pos());
 
276
            return;
 
277
        } else if (event->button() == Qt::RightButton) {
 
278
            zoomOutRequested(event->pos());
 
279
            return;
 
280
        }
 
281
    }
 
282
 
 
283
    d->mLastDragPos = event->pos();
 
284
    updateCursor();
 
285
}
 
286
 
 
287
void AbstractImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
 
288
{
 
289
    QGraphicsItem::mouseMoveEvent(event);
 
290
 
 
291
    QPointF mousePos = event->pos();
 
292
    QPointF newScrollPos = d->mScrollPos + d->mLastDragPos - mousePos;
 
293
 
 
294
    // Wrap mouse pos
 
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);
 
307
    }
 
308
    if (mousePos.y() <= margin) {
 
309
        mousePos.setY(maxHeight - margin - 1);
 
310
    } else if (mousePos.y() >= maxHeight - margin) {
 
311
        mousePos.setY(margin + 1);
 
312
    }
 
313
 
 
314
    // Set mouse pos (Hackish translation to screen coords!)
 
315
    QPointF screenDelta = event->screenPos() - event->pos();
 
316
    QCursor::setPos((mousePos + screenDelta).toPoint());
 
317
 
 
318
    d->mLastDragPos = mousePos;
 
319
    d->setScrollPos(newScrollPos);
 
320
 
 
321
}
 
322
 
 
323
void AbstractImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
 
324
{
 
325
    QGraphicsItem::mouseReleaseEvent(event);
 
326
    if (!d->mLastDragPos.isNull()) {
 
327
        d->mLastDragPos = QPointF();
 
328
    }
 
329
    updateCursor();
 
330
}
 
331
 
 
332
void AbstractImageView::keyPressEvent(QKeyEvent* event)
 
333
{
 
334
    QPointF delta(0, 0);
 
335
    qreal pageStep = boundingRect().height();
 
336
    qreal unitStep;
 
337
    if (event->modifiers() & Qt::ShiftModifier) {
 
338
        unitStep = pageStep / 2;
 
339
    } else {
 
340
        unitStep = UNIT_STEP;
 
341
    }
 
342
    switch (event->key()) {
 
343
    case Qt::Key_Left:
 
344
        delta.setX(-unitStep);
 
345
        break;
 
346
    case Qt::Key_Right:
 
347
        delta.setX(unitStep);
 
348
        break;
 
349
    case Qt::Key_Up:
 
350
        delta.setY(-unitStep);
 
351
        break;
 
352
    case Qt::Key_Down:
 
353
        delta.setY(unitStep);
 
354
        break;
 
355
    case Qt::Key_PageUp:
 
356
        delta.setY(-pageStep);
 
357
        break;
 
358
    case Qt::Key_PageDown:
 
359
        delta.setY(pageStep);
 
360
        break;
 
361
    case Qt::Key_Home:
 
362
        d->setScrollPos(QPointF(d->mScrollPos.x(), 0));
 
363
        return;
 
364
    case Qt::Key_End:
 
365
        d->setScrollPos(QPointF(d->mScrollPos.x(), documentSize().height() * zoom()));
 
366
        return;
 
367
    default:
 
368
        return;
 
369
    }
 
370
    d->setScrollPos(d->mScrollPos + delta);
 
371
}
 
372
 
 
373
QPointF AbstractImageView::imageOffset() const
 
374
{
 
375
    return d->mImageOffset;
 
376
}
 
377
 
 
378
QPointF AbstractImageView::scrollPos() const
 
379
{
 
380
    return d->mScrollPos;
 
381
}
 
382
 
 
383
void AbstractImageView::setScrollPos(const QPointF& pos)
 
384
{
 
385
    d->setScrollPos(pos);
 
386
}
 
387
 
 
388
QPointF AbstractImageView::mapToView(const QPointF& imagePos) const
 
389
{
 
390
    return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos;
 
391
}
 
392
 
 
393
QPoint AbstractImageView::mapToView(const QPoint& imagePos) const
 
394
{
 
395
    return mapToView(QPointF(imagePos)).toPoint();
 
396
}
 
397
 
 
398
QRectF AbstractImageView::mapToView(const QRectF& imageRect) const
 
399
{
 
400
    return QRectF(
 
401
               mapToView(imageRect.topLeft()),
 
402
               imageRect.size() * zoom()
 
403
           );
 
404
}
 
405
 
 
406
QRect AbstractImageView::mapToView(const QRect& imageRect) const
 
407
{
 
408
    return QRect(
 
409
               mapToView(imageRect.topLeft()),
 
410
               imageRect.size() * zoom()
 
411
           );
 
412
}
 
413
 
 
414
QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const
 
415
{
 
416
    return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom;
 
417
}
 
418
 
 
419
QPoint AbstractImageView::mapToImage(const QPoint& viewPos) const
 
420
{
 
421
    return mapToImage(QPointF(viewPos)).toPoint();
 
422
}
 
423
 
 
424
QRectF AbstractImageView::mapToImage(const QRectF& viewRect) const
 
425
{
 
426
    return QRectF(
 
427
               mapToImage(viewRect.topLeft()),
 
428
               viewRect.size() / zoom()
 
429
           );
 
430
}
 
431
 
 
432
QRect AbstractImageView::mapToImage(const QRect& viewRect) const
 
433
{
 
434
    return QRect(
 
435
               mapToImage(viewRect.topLeft()),
 
436
               viewRect.size() / zoom()
 
437
           );
 
438
}
 
439
 
 
440
void AbstractImageView::setEnlargeSmallerImages(bool value)
 
441
{
 
442
    d->mEnlargeSmallerImages = value;
 
443
    if (zoomToFit()) {
 
444
        setZoom(computeZoomToFit());
 
445
    }
 
446
}
 
447
 
 
448
void AbstractImageView::updateCursor()
 
449
{
 
450
    if (d->mModifierKeyInfo->isKeyPressed(Qt::Key_Control)) {
 
451
        setCursor(d->mZoomCursor);
 
452
    } else {
 
453
        if (d->mLastDragPos.isNull()) {
 
454
            setCursor(Qt::OpenHandCursor);
 
455
        } else {
 
456
            setCursor(Qt::ClosedHandCursor);
 
457
        }
 
458
    }
 
459
}
 
460
 
 
461
} // namespace