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 "birdeyeview.h"
23
24 // Local
25 #include "gwenview_lib_debug.h"
26 #include <lib/document/document.h>
27 #include <lib/documentview/documentview.h>
28
29 // KF
30
31 // Qt
32 #include <QApplication>
33 #include <QGraphicsSceneEvent>
34 #include <QPainter>
35 #include <QPropertyAnimation>
36 #include <QTimer>
37
38 namespace Gwenview
39 {
40 static qreal MIN_SIZE = 72;
41 static qreal VIEW_OFFSET = MIN_SIZE / 4;
42
43 static int AUTOHIDE_DELAY = 2000;
44
45 /**
46 * Returns a QRectF whose coordinates are rounded to completely contains rect
47 */
alignedRectF(const QRectF & rect)48 inline QRectF alignedRectF(const QRectF &rect)
49 {
50 return QRectF(rect.toAlignedRect());
51 }
52
53 struct BirdEyeViewPrivate {
54 BirdEyeView *q;
55 DocumentView *mDocView;
56 QPropertyAnimation *mOpacityAnim;
57 QTimer *mAutoHideTimer;
58 QRectF mVisibleRect;
59 QPointF mStartDragMousePos;
60 QPointF mStartDragViewPos;
61
updateCursorGwenview::BirdEyeViewPrivate62 void updateCursor(const QPointF &pos)
63 {
64 q->setCursor(mVisibleRect.contains(pos) ? Qt::OpenHandCursor : Qt::ArrowCursor);
65 }
66
updateVisibilityGwenview::BirdEyeViewPrivate67 void updateVisibility()
68 {
69 bool visible;
70 if (!mDocView->canZoom() || mDocView->zoomToFit()) {
71 // No need to show
72 visible = false;
73 } else if (mDocView->isAnimated()) {
74 // Do not show while animated
75 visible = false;
76 } else if (mVisibleRect == q->boundingRect()) {
77 // All of the image is visible
78 visible = false;
79 } else if (q->isUnderMouse() || !mStartDragMousePos.isNull()) {
80 // User is interacting or about to interact with birdeyeview
81 visible = true;
82 } else if (mAutoHideTimer->isActive()) {
83 // User triggered some activity recently (move mouse, scroll, zoom)
84 visible = true;
85 } else {
86 // No recent activity
87 visible = false;
88 }
89 qreal wantedOpacity = visible ? 1 : 0;
90 if (!qFuzzyCompare(wantedOpacity, q->opacity())) {
91 mOpacityAnim->setEndValue(wantedOpacity);
92 mOpacityAnim->start();
93 }
94
95 if (visible) {
96 mAutoHideTimer->start();
97 }
98 }
99 };
100
BirdEyeView(DocumentView * docView)101 BirdEyeView::BirdEyeView(DocumentView *docView)
102 : QGraphicsWidget(docView)
103 , d(new BirdEyeViewPrivate)
104 {
105 d->q = this;
106 d->mDocView = docView;
107 setFlag(ItemIsSelectable);
108 setCursor(Qt::ArrowCursor);
109 setAcceptHoverEvents(true);
110
111 d->mOpacityAnim = new QPropertyAnimation(this, "opacity", this);
112
113 d->mAutoHideTimer = new QTimer(this);
114 d->mAutoHideTimer->setSingleShot(true);
115 d->mAutoHideTimer->setInterval(AUTOHIDE_DELAY);
116 connect(d->mAutoHideTimer, &QTimer::timeout, this, &BirdEyeView::slotAutoHideTimeout);
117
118 // Hide ourself by default, to avoid startup flashes (if we let updateOpacity
119 // update opacity, it will do so through an animation)
120 setOpacity(0);
121 slotZoomOrSizeChanged();
122
123 connect(docView->document().data(), &Document::metaInfoUpdated, this, &BirdEyeView::slotZoomOrSizeChanged);
124 connect(docView, &DocumentView::zoomChanged, this, &BirdEyeView::slotZoomOrSizeChanged);
125 connect(docView, &DocumentView::zoomToFitChanged, this, &BirdEyeView::slotZoomOrSizeChanged);
126 connect(docView, &DocumentView::positionChanged, this, &BirdEyeView::slotPositionChanged);
127 }
128
~BirdEyeView()129 BirdEyeView::~BirdEyeView()
130 {
131 delete d;
132 }
133
adjustGeometry()134 void BirdEyeView::adjustGeometry()
135 {
136 if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) {
137 return;
138 }
139 QSizeF size = d->mDocView->document()->size() / qApp->devicePixelRatio();
140 size.scale(MIN_SIZE, MIN_SIZE, Qt::KeepAspectRatioByExpanding);
141 QRectF docViewRect = d->mDocView->boundingRect();
142 int maxBevHeight = docViewRect.height() - 2 * VIEW_OFFSET;
143 int maxBevWidth = docViewRect.width() - 2 * VIEW_OFFSET;
144 if (size.height() > maxBevHeight) {
145 size.scale(MIN_SIZE, maxBevHeight, Qt::KeepAspectRatio);
146 }
147 if (size.width() > maxBevWidth) {
148 size.scale(maxBevWidth, MIN_SIZE, Qt::KeepAspectRatio);
149 }
150 QRectF geom = QRectF(QApplication::isRightToLeft() ? docViewRect.left() + VIEW_OFFSET : docViewRect.right() - VIEW_OFFSET - size.width(),
151 docViewRect.bottom() - VIEW_OFFSET - size.height(),
152 size.width(),
153 size.height());
154 setGeometry(alignedRectF(geom));
155 adjustVisibleRect();
156 }
157
adjustVisibleRect()158 void BirdEyeView::adjustVisibleRect()
159 {
160 QSizeF docSize = d->mDocView->document()->size() / qApp->devicePixelRatio();
161 qreal viewZoom = d->mDocView->zoom();
162 qreal bevZoom;
163 if (docSize.height() > docSize.width()) {
164 bevZoom = size().height() / docSize.height();
165 } else {
166 bevZoom = size().width() / docSize.width();
167 }
168
169 if (qFuzzyIsNull(viewZoom) || qFuzzyIsNull(bevZoom)) {
170 // Prevent divide-by-zero crashes
171 return;
172 }
173
174 QRectF rect = QRectF(QPointF(d->mDocView->position()) / viewZoom * bevZoom, (d->mDocView->size() / viewZoom).boundedTo(docSize) * bevZoom);
175 d->mVisibleRect = rect;
176 }
177
slotAutoHideTimeout()178 void BirdEyeView::slotAutoHideTimeout()
179 {
180 d->updateVisibility();
181 }
182
slotZoomOrSizeChanged()183 void BirdEyeView::slotZoomOrSizeChanged()
184 {
185 if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) {
186 d->updateVisibility();
187 return;
188 }
189 adjustGeometry();
190 update();
191 d->mAutoHideTimer->start();
192 d->updateVisibility();
193 }
194
slotPositionChanged()195 void BirdEyeView::slotPositionChanged()
196 {
197 adjustVisibleRect();
198 update();
199 d->mAutoHideTimer->start();
200 d->updateVisibility();
201 }
202
drawTransparentRect(QPainter * painter,const QRectF & rect,const QColor & color)203 inline void drawTransparentRect(QPainter *painter, const QRectF &rect, const QColor &color)
204 {
205 QColor bg = color;
206 bg.setAlphaF(.33);
207 QColor fg = color;
208 fg.setAlphaF(.66);
209 painter->setPen(fg);
210 painter->setBrush(bg);
211 painter->drawRect(rect.adjusted(0, 0, -1, -1));
212 }
213
paint(QPainter * painter,const QStyleOptionGraphicsItem *,QWidget *)214 void BirdEyeView::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
215 {
216 static const QColor bgColor = QColor::fromHsvF(0, 0, .33);
217 drawTransparentRect(painter, boundingRect(), bgColor);
218 drawTransparentRect(painter, d->mVisibleRect, Qt::white);
219 }
220
onMouseMoved()221 void BirdEyeView::onMouseMoved()
222 {
223 d->mAutoHideTimer->start();
224 d->updateVisibility();
225 }
226
slotIsAnimatedChanged()227 void BirdEyeView::slotIsAnimatedChanged()
228 {
229 d->updateVisibility();
230 }
231
mousePressEvent(QGraphicsSceneMouseEvent * event)232 void BirdEyeView::mousePressEvent(QGraphicsSceneMouseEvent *event)
233 {
234 if (d->mVisibleRect.contains(event->pos()) && event->button() == Qt::LeftButton) {
235 setCursor(Qt::ClosedHandCursor);
236 d->mStartDragMousePos = event->pos();
237 d->mStartDragViewPos = d->mDocView->position();
238 }
239 }
240
mouseMoveEvent(QGraphicsSceneMouseEvent * event)241 void BirdEyeView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
242 {
243 QGraphicsItem::mouseMoveEvent(event);
244 if (d->mStartDragMousePos.isNull()) {
245 // Do not drag if mouse was pressed outside visible rect
246 return;
247 }
248 qreal ratio = qMin(d->mDocView->boundingRect().height() / d->mVisibleRect.height(), d->mDocView->boundingRect().width() / d->mVisibleRect.width());
249 QPointF mousePos = event->pos();
250 QPointF viewPos = d->mStartDragViewPos + (mousePos - d->mStartDragMousePos) * ratio;
251
252 d->mDocView->setPosition(viewPos.toPoint());
253 }
254
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)255 void BirdEyeView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
256 {
257 QGraphicsItem::mouseReleaseEvent(event);
258 if (d->mStartDragMousePos.isNull()) {
259 return;
260 }
261 d->updateCursor(event->pos());
262 d->mStartDragMousePos = QPointF();
263 d->mAutoHideTimer->start();
264 }
265
hoverEnterEvent(QGraphicsSceneHoverEvent *)266 void BirdEyeView::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
267 {
268 d->mAutoHideTimer->stop();
269 d->updateVisibility();
270 }
271
hoverMoveEvent(QGraphicsSceneHoverEvent * event)272 void BirdEyeView::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
273 {
274 if (d->mStartDragMousePos.isNull()) {
275 d->updateCursor(event->pos());
276 }
277 }
278
hoverLeaveEvent(QGraphicsSceneHoverEvent *)279 void BirdEyeView::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
280 {
281 d->mAutoHideTimer->start();
282 d->updateVisibility();
283 }
284
285 } // namespace
286