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 "imageviewer.h" |
52 | |
53 | #include <QApplication> |
54 | #include <QClipboard> |
55 | #include <QColorSpace> |
56 | #include <QDir> |
57 | #include <QFileDialog> |
58 | #include <QImageReader> |
59 | #include <QImageWriter> |
60 | #include <QLabel> |
61 | #include <QMenuBar> |
62 | #include <QMessageBox> |
63 | #include <QMimeData> |
64 | #include <QPainter> |
65 | #include <QScreen> |
66 | #include <QScrollArea> |
67 | #include <QScrollBar> |
68 | #include <QStandardPaths> |
69 | #include <QStatusBar> |
70 | |
71 | #if defined(QT_PRINTSUPPORT_LIB) |
72 | # include <QtPrintSupport/qtprintsupportglobal.h> |
73 | |
74 | # if QT_CONFIG(printdialog) |
75 | # include <QPrintDialog> |
76 | # endif |
77 | #endif |
78 | |
79 | //! [0] |
80 | ImageViewer::ImageViewer(QWidget *parent) |
81 | : QMainWindow(parent), imageLabel(new QLabel) |
82 | , scrollArea(new QScrollArea) |
83 | { |
84 | imageLabel->setBackgroundRole(QPalette::Base); |
85 | imageLabel->setSizePolicy(hor: QSizePolicy::Ignored, ver: QSizePolicy::Ignored); |
86 | imageLabel->setScaledContents(true); |
87 | |
88 | scrollArea->setBackgroundRole(QPalette::Dark); |
89 | scrollArea->setWidget(imageLabel); |
90 | scrollArea->setVisible(false); |
91 | setCentralWidget(scrollArea); |
92 | |
93 | createActions(); |
94 | |
95 | resize(QGuiApplication::primaryScreen()->availableSize() * 3 / 5); |
96 | } |
97 | |
98 | //! [0] |
99 | //! [2] |
100 | |
101 | bool ImageViewer::loadFile(const QString &fileName) |
102 | { |
103 | QImageReader reader(fileName); |
104 | reader.setAutoTransform(true); |
105 | const QImage newImage = reader.read(); |
106 | if (newImage.isNull()) { |
107 | QMessageBox::information(parent: this, title: QGuiApplication::applicationDisplayName(), |
108 | text: tr(s: "Cannot load %1: %2" ) |
109 | .arg(args: QDir::toNativeSeparators(pathName: fileName), args: reader.errorString())); |
110 | return false; |
111 | } |
112 | //! [2] |
113 | |
114 | setImage(newImage); |
115 | |
116 | setWindowFilePath(fileName); |
117 | |
118 | const QString message = tr(s: "Opened \"%1\", %2x%3, Depth: %4" ) |
119 | .arg(a: QDir::toNativeSeparators(pathName: fileName)).arg(a: image.width()).arg(a: image.height()).arg(a: image.depth()); |
120 | statusBar()->showMessage(text: message); |
121 | return true; |
122 | } |
123 | |
124 | void ImageViewer::setImage(const QImage &newImage) |
125 | { |
126 | image = newImage; |
127 | if (image.colorSpace().isValid()) |
128 | image.convertToColorSpace(QColorSpace::SRgb); |
129 | imageLabel->setPixmap(QPixmap::fromImage(image)); |
130 | //! [4] |
131 | scaleFactor = 1.0; |
132 | |
133 | scrollArea->setVisible(true); |
134 | printAct->setEnabled(true); |
135 | fitToWindowAct->setEnabled(true); |
136 | updateActions(); |
137 | |
138 | if (!fitToWindowAct->isChecked()) |
139 | imageLabel->adjustSize(); |
140 | } |
141 | |
142 | //! [4] |
143 | |
144 | bool ImageViewer::saveFile(const QString &fileName) |
145 | { |
146 | QImageWriter writer(fileName); |
147 | |
148 | if (!writer.write(image)) { |
149 | QMessageBox::information(parent: this, title: QGuiApplication::applicationDisplayName(), |
150 | text: tr(s: "Cannot write %1: %2" ) |
151 | .arg(a: QDir::toNativeSeparators(pathName: fileName)), button0Text: writer.errorString()); |
152 | return false; |
153 | } |
154 | const QString message = tr(s: "Wrote \"%1\"" ).arg(a: QDir::toNativeSeparators(pathName: fileName)); |
155 | statusBar()->showMessage(text: message); |
156 | return true; |
157 | } |
158 | |
159 | //! [1] |
160 | |
161 | static void initializeImageFileDialog(QFileDialog &dialog, QFileDialog::AcceptMode acceptMode) |
162 | { |
163 | static bool firstDialog = true; |
164 | |
165 | if (firstDialog) { |
166 | firstDialog = false; |
167 | const QStringList picturesLocations = QStandardPaths::standardLocations(type: QStandardPaths::PicturesLocation); |
168 | dialog.setDirectory(picturesLocations.isEmpty() ? QDir::currentPath() : picturesLocations.last()); |
169 | } |
170 | |
171 | QStringList mimeTypeFilters; |
172 | const QByteArrayList supportedMimeTypes = acceptMode == QFileDialog::AcceptOpen |
173 | ? QImageReader::supportedMimeTypes() : QImageWriter::supportedMimeTypes(); |
174 | for (const QByteArray &mimeTypeName : supportedMimeTypes) |
175 | mimeTypeFilters.append(t: mimeTypeName); |
176 | mimeTypeFilters.sort(); |
177 | dialog.setMimeTypeFilters(mimeTypeFilters); |
178 | dialog.selectMimeTypeFilter(filter: "image/jpeg" ); |
179 | dialog.setAcceptMode(acceptMode); |
180 | if (acceptMode == QFileDialog::AcceptSave) |
181 | dialog.setDefaultSuffix("jpg" ); |
182 | } |
183 | |
184 | void ImageViewer::open() |
185 | { |
186 | QFileDialog dialog(this, tr(s: "Open File" )); |
187 | initializeImageFileDialog(dialog, acceptMode: QFileDialog::AcceptOpen); |
188 | |
189 | while (dialog.exec() == QDialog::Accepted && !loadFile(fileName: dialog.selectedFiles().constFirst())) {} |
190 | } |
191 | //! [1] |
192 | |
193 | void ImageViewer::saveAs() |
194 | { |
195 | QFileDialog dialog(this, tr(s: "Save File As" )); |
196 | initializeImageFileDialog(dialog, acceptMode: QFileDialog::AcceptSave); |
197 | |
198 | while (dialog.exec() == QDialog::Accepted && !saveFile(fileName: dialog.selectedFiles().constFirst())) {} |
199 | } |
200 | |
201 | //! [5] |
202 | void ImageViewer::print() |
203 | //! [5] //! [6] |
204 | { |
205 | Q_ASSERT(!imageLabel->pixmap(Qt::ReturnByValue).isNull()); |
206 | #if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog) |
207 | //! [6] //! [7] |
208 | QPrintDialog dialog(&printer, this); |
209 | //! [7] //! [8] |
210 | if (dialog.exec()) { |
211 | QPainter painter(&printer); |
212 | QPixmap pixmap = imageLabel->pixmap(Qt::ReturnByValue); |
213 | QRect rect = painter.viewport(); |
214 | QSize size = pixmap.size(); |
215 | size.scale(s: rect.size(), mode: Qt::KeepAspectRatio); |
216 | painter.setViewport(x: rect.x(), y: rect.y(), w: size.width(), h: size.height()); |
217 | painter.setWindow(pixmap.rect()); |
218 | painter.drawPixmap(x: 0, y: 0, pm: pixmap); |
219 | } |
220 | #endif |
221 | } |
222 | //! [8] |
223 | |
224 | void ImageViewer::copy() |
225 | { |
226 | #ifndef QT_NO_CLIPBOARD |
227 | QGuiApplication::clipboard()->setImage(image); |
228 | #endif // !QT_NO_CLIPBOARD |
229 | } |
230 | |
231 | #ifndef QT_NO_CLIPBOARD |
232 | static QImage clipboardImage() |
233 | { |
234 | if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData()) { |
235 | if (mimeData->hasImage()) { |
236 | const QImage image = qvariant_cast<QImage>(v: mimeData->imageData()); |
237 | if (!image.isNull()) |
238 | return image; |
239 | } |
240 | } |
241 | return QImage(); |
242 | } |
243 | #endif // !QT_NO_CLIPBOARD |
244 | |
245 | void ImageViewer::paste() |
246 | { |
247 | #ifndef QT_NO_CLIPBOARD |
248 | const QImage newImage = clipboardImage(); |
249 | if (newImage.isNull()) { |
250 | statusBar()->showMessage(text: tr(s: "No image in clipboard" )); |
251 | } else { |
252 | setImage(newImage); |
253 | setWindowFilePath(QString()); |
254 | const QString message = tr(s: "Obtained image from clipboard, %1x%2, Depth: %3" ) |
255 | .arg(a: newImage.width()).arg(a: newImage.height()).arg(a: newImage.depth()); |
256 | statusBar()->showMessage(text: message); |
257 | } |
258 | #endif // !QT_NO_CLIPBOARD |
259 | } |
260 | |
261 | //! [9] |
262 | void ImageViewer::zoomIn() |
263 | //! [9] //! [10] |
264 | { |
265 | scaleImage(factor: 1.25); |
266 | } |
267 | |
268 | void ImageViewer::zoomOut() |
269 | { |
270 | scaleImage(factor: 0.8); |
271 | } |
272 | |
273 | //! [10] //! [11] |
274 | void ImageViewer::normalSize() |
275 | //! [11] //! [12] |
276 | { |
277 | imageLabel->adjustSize(); |
278 | scaleFactor = 1.0; |
279 | } |
280 | //! [12] |
281 | |
282 | //! [13] |
283 | void ImageViewer::fitToWindow() |
284 | //! [13] //! [14] |
285 | { |
286 | bool fitToWindow = fitToWindowAct->isChecked(); |
287 | scrollArea->setWidgetResizable(fitToWindow); |
288 | if (!fitToWindow) |
289 | normalSize(); |
290 | updateActions(); |
291 | } |
292 | //! [14] |
293 | |
294 | |
295 | //! [15] |
296 | void ImageViewer::about() |
297 | //! [15] //! [16] |
298 | { |
299 | QMessageBox::about(parent: this, title: tr(s: "About Image Viewer" ), |
300 | text: tr(s: "<p>The <b>Image Viewer</b> example shows how to combine QLabel " |
301 | "and QScrollArea to display an image. QLabel is typically used " |
302 | "for displaying a text, but it can also display an image. " |
303 | "QScrollArea provides a scrolling view around another widget. " |
304 | "If the child widget exceeds the size of the frame, QScrollArea " |
305 | "automatically provides scroll bars. </p><p>The example " |
306 | "demonstrates how QLabel's ability to scale its contents " |
307 | "(QLabel::scaledContents), and QScrollArea's ability to " |
308 | "automatically resize its contents " |
309 | "(QScrollArea::widgetResizable), can be used to implement " |
310 | "zooming and scaling features. </p><p>In addition the example " |
311 | "shows how to use QPainter to print an image.</p>" )); |
312 | } |
313 | //! [16] |
314 | |
315 | //! [17] |
316 | void ImageViewer::createActions() |
317 | //! [17] //! [18] |
318 | { |
319 | QMenu * = menuBar()->addMenu(title: tr(s: "&File" )); |
320 | |
321 | QAction *openAct = fileMenu->addAction(text: tr(s: "&Open..." ), object: this, slot: &ImageViewer::open); |
322 | openAct->setShortcut(QKeySequence::Open); |
323 | |
324 | saveAsAct = fileMenu->addAction(text: tr(s: "&Save As..." ), object: this, slot: &ImageViewer::saveAs); |
325 | saveAsAct->setEnabled(false); |
326 | |
327 | printAct = fileMenu->addAction(text: tr(s: "&Print..." ), object: this, slot: &ImageViewer::print); |
328 | printAct->setShortcut(QKeySequence::Print); |
329 | printAct->setEnabled(false); |
330 | |
331 | fileMenu->addSeparator(); |
332 | |
333 | QAction *exitAct = fileMenu->addAction(text: tr(s: "E&xit" ), object: this, slot: &QWidget::close); |
334 | exitAct->setShortcut(tr(s: "Ctrl+Q" )); |
335 | |
336 | QMenu * = menuBar()->addMenu(title: tr(s: "&Edit" )); |
337 | |
338 | copyAct = editMenu->addAction(text: tr(s: "&Copy" ), object: this, slot: &ImageViewer::copy); |
339 | copyAct->setShortcut(QKeySequence::Copy); |
340 | copyAct->setEnabled(false); |
341 | |
342 | QAction *pasteAct = editMenu->addAction(text: tr(s: "&Paste" ), object: this, slot: &ImageViewer::paste); |
343 | pasteAct->setShortcut(QKeySequence::Paste); |
344 | |
345 | QMenu * = menuBar()->addMenu(title: tr(s: "&View" )); |
346 | |
347 | zoomInAct = viewMenu->addAction(text: tr(s: "Zoom &In (25%)" ), object: this, slot: &ImageViewer::zoomIn); |
348 | zoomInAct->setShortcut(QKeySequence::ZoomIn); |
349 | zoomInAct->setEnabled(false); |
350 | |
351 | zoomOutAct = viewMenu->addAction(text: tr(s: "Zoom &Out (25%)" ), object: this, slot: &ImageViewer::zoomOut); |
352 | zoomOutAct->setShortcut(QKeySequence::ZoomOut); |
353 | zoomOutAct->setEnabled(false); |
354 | |
355 | normalSizeAct = viewMenu->addAction(text: tr(s: "&Normal Size" ), object: this, slot: &ImageViewer::normalSize); |
356 | normalSizeAct->setShortcut(tr(s: "Ctrl+S" )); |
357 | normalSizeAct->setEnabled(false); |
358 | |
359 | viewMenu->addSeparator(); |
360 | |
361 | fitToWindowAct = viewMenu->addAction(text: tr(s: "&Fit to Window" ), object: this, slot: &ImageViewer::fitToWindow); |
362 | fitToWindowAct->setEnabled(false); |
363 | fitToWindowAct->setCheckable(true); |
364 | fitToWindowAct->setShortcut(tr(s: "Ctrl+F" )); |
365 | |
366 | QMenu * = menuBar()->addMenu(title: tr(s: "&Help" )); |
367 | |
368 | helpMenu->addAction(text: tr(s: "&About" ), object: this, slot: &ImageViewer::about); |
369 | helpMenu->addAction(text: tr(s: "About &Qt" ), object: this, slot: &QApplication::aboutQt); |
370 | } |
371 | //! [18] |
372 | |
373 | //! [21] |
374 | void ImageViewer::updateActions() |
375 | //! [21] //! [22] |
376 | { |
377 | saveAsAct->setEnabled(!image.isNull()); |
378 | copyAct->setEnabled(!image.isNull()); |
379 | zoomInAct->setEnabled(!fitToWindowAct->isChecked()); |
380 | zoomOutAct->setEnabled(!fitToWindowAct->isChecked()); |
381 | normalSizeAct->setEnabled(!fitToWindowAct->isChecked()); |
382 | } |
383 | //! [22] |
384 | |
385 | //! [23] |
386 | void ImageViewer::scaleImage(double factor) |
387 | //! [23] //! [24] |
388 | { |
389 | scaleFactor *= factor; |
390 | imageLabel->resize(scaleFactor * imageLabel->pixmap(Qt::ReturnByValue).size()); |
391 | |
392 | adjustScrollBar(scrollBar: scrollArea->horizontalScrollBar(), factor); |
393 | adjustScrollBar(scrollBar: scrollArea->verticalScrollBar(), factor); |
394 | |
395 | zoomInAct->setEnabled(scaleFactor < 3.0); |
396 | zoomOutAct->setEnabled(scaleFactor > 0.333); |
397 | } |
398 | //! [24] |
399 | |
400 | //! [25] |
401 | void ImageViewer::adjustScrollBar(QScrollBar *scrollBar, double factor) |
402 | //! [25] //! [26] |
403 | { |
404 | scrollBar->setValue(int(factor * scrollBar->value() |
405 | + ((factor - 1) * scrollBar->pageStep()/2))); |
406 | } |
407 | //! [26] |
408 | |