1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "imagewidget.h"
52
53#include <QDir>
54#include <QImageReader>
55#include <QGestureEvent>
56#include <QPainter>
57
58Q_LOGGING_CATEGORY(lcExample, "qt.examples.imagegestures")
59
60//! [constructor]
61ImageWidget::ImageWidget(QWidget *parent)
62 : QWidget(parent), position(0), horizontalOffset(0), verticalOffset(0)
63 , rotationAngle(0), scaleFactor(1), currentStepScaleFactor(1)
64{
65 setMinimumSize(QSize(100, 100));
66}
67//! [constructor]
68
69void ImageWidget::grabGestures(const QList<Qt::GestureType> &gestures)
70{
71 //! [enable gestures]
72 for (Qt::GestureType gesture : gestures)
73 grabGesture(gesture);
74 //! [enable gestures]
75}
76
77//! [event handler]
78bool ImageWidget::event(QEvent *event)
79{
80 if (event->type() == QEvent::Gesture)
81 return gestureEvent(static_cast<QGestureEvent*>(event));
82 return QWidget::event(event);
83}
84//! [event handler]
85
86//! [paint method]
87void ImageWidget::paintEvent(QPaintEvent*)
88{
89 QPainter p(this);
90
91 const qreal iw = currentImage.width();
92 const qreal ih = currentImage.height();
93 const qreal wh = height();
94 const qreal ww = width();
95
96 p.translate(ww / 2, wh / 2);
97 p.translate(horizontalOffset, verticalOffset);
98 p.rotate(rotationAngle);
99 p.scale(currentStepScaleFactor * scaleFactor, currentStepScaleFactor * scaleFactor);
100 p.translate(-iw / 2, -ih / 2);
101 p.drawImage(0, 0, currentImage);
102}
103//! [paint method]
104
105void ImageWidget::mouseDoubleClickEvent(QMouseEvent *)
106{
107 rotationAngle = 0;
108 scaleFactor = 1;
109 currentStepScaleFactor = 1;
110 verticalOffset = 0;
111 horizontalOffset = 0;
112 update();
113 qCDebug(lcExample) << "reset on mouse double click";
114}
115
116//! [gesture event handler]
117bool ImageWidget::gestureEvent(QGestureEvent *event)
118{
119 qCDebug(lcExample) << "gestureEvent():" << event;
120 if (QGesture *swipe = event->gesture(Qt::SwipeGesture))
121 swipeTriggered(static_cast<QSwipeGesture *>(swipe));
122 else if (QGesture *pan = event->gesture(Qt::PanGesture))
123 panTriggered(static_cast<QPanGesture *>(pan));
124 if (QGesture *pinch = event->gesture(Qt::PinchGesture))
125 pinchTriggered(static_cast<QPinchGesture *>(pinch));
126 return true;
127}
128//! [gesture event handler]
129
130void ImageWidget::panTriggered(QPanGesture *gesture)
131{
132#ifndef QT_NO_CURSOR
133 switch (gesture->state()) {
134 case Qt::GestureStarted:
135 case Qt::GestureUpdated:
136 setCursor(Qt::SizeAllCursor);
137 break;
138 default:
139 setCursor(Qt::ArrowCursor);
140 }
141#endif
142 QPointF delta = gesture->delta();
143 qCDebug(lcExample) << "panTriggered():" << gesture;
144 horizontalOffset += delta.x();
145 verticalOffset += delta.y();
146 update();
147}
148
149//! [pinch function]
150void ImageWidget::pinchTriggered(QPinchGesture *gesture)
151{
152 QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags();
153 if (changeFlags & QPinchGesture::RotationAngleChanged) {
154 qreal rotationDelta = gesture->rotationAngle() - gesture->lastRotationAngle();
155 rotationAngle += rotationDelta;
156 qCDebug(lcExample) << "pinchTriggered(): rotate by" <<
157 rotationDelta << "->" << rotationAngle;
158 }
159 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
160 currentStepScaleFactor = gesture->totalScaleFactor();
161 qCDebug(lcExample) << "pinchTriggered(): zoom by" <<
162 gesture->scaleFactor() << "->" << currentStepScaleFactor;
163 }
164 if (gesture->state() == Qt::GestureFinished) {
165 scaleFactor *= currentStepScaleFactor;
166 currentStepScaleFactor = 1;
167 }
168 update();
169}
170//! [pinch function]
171
172//! [swipe function]
173void ImageWidget::swipeTriggered(QSwipeGesture *gesture)
174{
175 if (gesture->state() == Qt::GestureFinished) {
176 if (gesture->horizontalDirection() == QSwipeGesture::Left
177 || gesture->verticalDirection() == QSwipeGesture::Up) {
178 qCDebug(lcExample) << "swipeTriggered(): swipe to previous";
179 goPrevImage();
180 } else {
181 qCDebug(lcExample) << "swipeTriggered(): swipe to next";
182 goNextImage();
183 }
184 update();
185 }
186}
187//! [swipe function]
188
189void ImageWidget::resizeEvent(QResizeEvent*)
190{
191 update();
192}
193
194void ImageWidget::openDirectory(const QString &path)
195{
196 this->path = path;
197 QDir dir(path);
198 const QStringList nameFilters{"*.jpg", "*.png"};
199 files = dir.entryList(nameFilters, QDir::Files|QDir::Readable, QDir::Name);
200
201 position = 0;
202 goToImage(0);
203 update();
204}
205
206QImage ImageWidget::loadImage(const QString &fileName) const
207{
208 QImageReader reader(fileName);
209 reader.setAutoTransform(true);
210 qCDebug(lcExample) << "loading" << QDir::toNativeSeparators(fileName) << position << '/' << files.size();
211 if (!reader.canRead()) {
212 qCWarning(lcExample) << QDir::toNativeSeparators(fileName) << ": can't load image";
213 return QImage();
214 }
215
216 QImage image;
217 if (!reader.read(&image)) {
218 qCWarning(lcExample) << QDir::toNativeSeparators(fileName) << ": corrupted image: " << reader.errorString();
219 return QImage();
220 }
221 const QSize maximumSize(2000, 2000); // Reduce in case someone has large photo images.
222 if (image.size().width() > maximumSize.width() || image.height() > maximumSize.height())
223 image = image.scaled(maximumSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
224 return image;
225}
226
227void ImageWidget::goNextImage()
228{
229 if (files.isEmpty())
230 return;
231
232 if (position < files.size()-1) {
233 ++position;
234 prevImage = currentImage;
235 currentImage = nextImage;
236 if (position+1 < files.size())
237 nextImage = loadImage(path + QLatin1Char('/') + files.at(position+1));
238 else
239 nextImage = QImage();
240 }
241 update();
242}
243
244void ImageWidget::goPrevImage()
245{
246 if (files.isEmpty())
247 return;
248
249 if (position > 0) {
250 --position;
251 nextImage = currentImage;
252 currentImage = prevImage;
253 if (position > 0)
254 prevImage = loadImage(path + QLatin1Char('/') + files.at(position-1));
255 else
256 prevImage = QImage();
257 }
258 update();
259}
260
261void ImageWidget::goToImage(int index)
262{
263 if (files.isEmpty())
264 return;
265
266 if (index < 0 || index >= files.size()) {
267 qCWarning(lcExample) << "goToImage: invalid index: " << index;
268 return;
269 }
270
271 if (index == position+1) {
272 goNextImage();
273 return;
274 }
275
276 if (position > 0 && index == position-1) {
277 goPrevImage();
278 return;
279 }
280
281 position = index;
282
283 if (index > 0)
284 prevImage = loadImage(path + QLatin1Char('/') + files.at(position-1));
285 else
286 prevImage = QImage();
287 currentImage = loadImage(path + QLatin1Char('/') + files.at(position));
288 if (position+1 < files.size())
289 nextImage = loadImage(path + QLatin1Char('/') + files.at(position+1));
290 else
291 nextImage = QImage();
292 update();
293}
294