1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import QDir, Qt
46from PyQt5.QtGui import QImage, QPainter, QPalette, QPixmap
47from PyQt5.QtWidgets import (QAction, QApplication, QFileDialog, QLabel,
48        QMainWindow, QMenu, QMessageBox, QScrollArea, QSizePolicy)
49from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
50
51
52class ImageViewer(QMainWindow):
53    def __init__(self):
54        super(ImageViewer, self).__init__()
55
56        self.printer = QPrinter()
57        self.scaleFactor = 0.0
58
59        self.imageLabel = QLabel()
60        self.imageLabel.setBackgroundRole(QPalette.Base)
61        self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
62        self.imageLabel.setScaledContents(True)
63
64        self.scrollArea = QScrollArea()
65        self.scrollArea.setBackgroundRole(QPalette.Dark)
66        self.scrollArea.setWidget(self.imageLabel)
67        self.setCentralWidget(self.scrollArea)
68
69        self.createActions()
70        self.createMenus()
71
72        self.setWindowTitle("Image Viewer")
73        self.resize(500, 400)
74
75    def open(self):
76        fileName, _ = QFileDialog.getOpenFileName(self, "Open File",
77                QDir.currentPath())
78        if fileName:
79            image = QImage(fileName)
80            if image.isNull():
81                QMessageBox.information(self, "Image Viewer",
82                        "Cannot load %s." % fileName)
83                return
84
85            self.imageLabel.setPixmap(QPixmap.fromImage(image))
86            self.scaleFactor = 1.0
87
88            self.printAct.setEnabled(True)
89            self.fitToWindowAct.setEnabled(True)
90            self.updateActions()
91
92            if not self.fitToWindowAct.isChecked():
93                self.imageLabel.adjustSize()
94
95    def print_(self):
96        dialog = QPrintDialog(self.printer, self)
97        if dialog.exec_():
98            painter = QPainter(self.printer)
99            rect = painter.viewport()
100            size = self.imageLabel.pixmap().size()
101            size.scale(rect.size(), Qt.KeepAspectRatio)
102            painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
103            painter.setWindow(self.imageLabel.pixmap().rect())
104            painter.drawPixmap(0, 0, self.imageLabel.pixmap())
105
106    def zoomIn(self):
107        self.scaleImage(1.25)
108
109    def zoomOut(self):
110        self.scaleImage(0.8)
111
112    def normalSize(self):
113        self.imageLabel.adjustSize()
114        self.scaleFactor = 1.0
115
116    def fitToWindow(self):
117        fitToWindow = self.fitToWindowAct.isChecked()
118        self.scrollArea.setWidgetResizable(fitToWindow)
119        if not fitToWindow:
120            self.normalSize()
121
122        self.updateActions()
123
124    def about(self):
125        QMessageBox.about(self, "About Image Viewer",
126                "<p>The <b>Image Viewer</b> example shows how to combine "
127                "QLabel and QScrollArea to display an image. QLabel is "
128                "typically used for displaying text, but it can also display "
129                "an image. QScrollArea provides a scrolling view around "
130                "another widget. If the child widget exceeds the size of the "
131                "frame, QScrollArea automatically provides scroll bars.</p>"
132                "<p>The example demonstrates how QLabel's ability to scale "
133                "its contents (QLabel.scaledContents), and QScrollArea's "
134                "ability to automatically resize its contents "
135                "(QScrollArea.widgetResizable), can be used to implement "
136                "zooming and scaling features.</p>"
137                "<p>In addition the example shows how to use QPainter to "
138                "print an image.</p>")
139
140    def createActions(self):
141        self.openAct = QAction("&Open...", self, shortcut="Ctrl+O",
142                triggered=self.open)
143
144        self.printAct = QAction("&Print...", self, shortcut="Ctrl+P",
145                enabled=False, triggered=self.print_)
146
147        self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q",
148                triggered=self.close)
149
150        self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++",
151                enabled=False, triggered=self.zoomIn)
152
153        self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-",
154                enabled=False, triggered=self.zoomOut)
155
156        self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S",
157                enabled=False, triggered=self.normalSize)
158
159        self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False,
160                checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow)
161
162        self.aboutAct = QAction("&About", self, triggered=self.about)
163
164        self.aboutQtAct = QAction("About &Qt", self,
165                triggered=QApplication.instance().aboutQt)
166
167    def createMenus(self):
168        self.fileMenu = QMenu("&File", self)
169        self.fileMenu.addAction(self.openAct)
170        self.fileMenu.addAction(self.printAct)
171        self.fileMenu.addSeparator()
172        self.fileMenu.addAction(self.exitAct)
173
174        self.viewMenu = QMenu("&View", self)
175        self.viewMenu.addAction(self.zoomInAct)
176        self.viewMenu.addAction(self.zoomOutAct)
177        self.viewMenu.addAction(self.normalSizeAct)
178        self.viewMenu.addSeparator()
179        self.viewMenu.addAction(self.fitToWindowAct)
180
181        self.helpMenu = QMenu("&Help", self)
182        self.helpMenu.addAction(self.aboutAct)
183        self.helpMenu.addAction(self.aboutQtAct)
184
185        self.menuBar().addMenu(self.fileMenu)
186        self.menuBar().addMenu(self.viewMenu)
187        self.menuBar().addMenu(self.helpMenu)
188
189    def updateActions(self):
190        self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked())
191        self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked())
192        self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked())
193
194    def scaleImage(self, factor):
195        self.scaleFactor *= factor
196        self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size())
197
198        self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor)
199        self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor)
200
201        self.zoomInAct.setEnabled(self.scaleFactor < 3.0)
202        self.zoomOutAct.setEnabled(self.scaleFactor > 0.333)
203
204    def adjustScrollBar(self, scrollBar, factor):
205        scrollBar.setValue(int(factor * scrollBar.value()
206                                + ((factor - 1) * scrollBar.pageStep()/2)))
207
208
209if __name__ == '__main__':
210
211    import sys
212
213    app = QApplication(sys.argv)
214    imageViewer = ImageViewer()
215    imageViewer.show()
216    sys.exit(app.exec_())
217