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 QFile, QSize, Qt
46from PyQt5.QtGui import QBrush, QColor, QImage, QPainter, QPixmap, QPen
47from PyQt5.QtWidgets import (QActionGroup, QApplication, QFileDialog,
48        QGraphicsItem, QGraphicsRectItem, QGraphicsScene, QGraphicsView,
49        QMainWindow, QMenu, QMessageBox, QWidget)
50from PyQt5.QtOpenGL import QGL, QGLFormat, QGLWidget
51from PyQt5.QtSvg import QGraphicsSvgItem
52
53import svgviewer_rc
54
55
56class MainWindow(QMainWindow):
57    def __init__(self):
58        super(MainWindow, self).__init__()
59
60        self.currentPath = ''
61
62        self.view = SvgView()
63
64        fileMenu = QMenu("&File", self)
65        openAction = fileMenu.addAction("&Open...")
66        openAction.setShortcut("Ctrl+O")
67        quitAction = fileMenu.addAction("E&xit")
68        quitAction.setShortcut("Ctrl+Q")
69
70        self.menuBar().addMenu(fileMenu)
71
72        viewMenu = QMenu("&View", self)
73        self.backgroundAction = viewMenu.addAction("&Background")
74        self.backgroundAction.setEnabled(False)
75        self.backgroundAction.setCheckable(True)
76        self.backgroundAction.setChecked(False)
77        self.backgroundAction.toggled.connect(self.view.setViewBackground)
78
79        self.outlineAction = viewMenu.addAction("&Outline")
80        self.outlineAction.setEnabled(False)
81        self.outlineAction.setCheckable(True)
82        self.outlineAction.setChecked(True)
83        self.outlineAction.toggled.connect(self.view.setViewOutline)
84
85        self.menuBar().addMenu(viewMenu)
86
87        rendererMenu = QMenu("&Renderer", self)
88        self.nativeAction = rendererMenu.addAction("&Native")
89        self.nativeAction.setCheckable(True)
90        self.nativeAction.setChecked(True)
91
92        if QGLFormat.hasOpenGL():
93            self.glAction = rendererMenu.addAction("&OpenGL")
94            self.glAction.setCheckable(True)
95
96        self.imageAction = rendererMenu.addAction("&Image")
97        self.imageAction.setCheckable(True)
98
99        if QGLFormat.hasOpenGL():
100            rendererMenu.addSeparator()
101            self.highQualityAntialiasingAction = rendererMenu.addAction("&High Quality Antialiasing")
102            self.highQualityAntialiasingAction.setEnabled(False)
103            self.highQualityAntialiasingAction.setCheckable(True)
104            self.highQualityAntialiasingAction.setChecked(False)
105            self.highQualityAntialiasingAction.toggled.connect(self.view.setHighQualityAntialiasing)
106
107        rendererGroup = QActionGroup(self)
108        rendererGroup.addAction(self.nativeAction)
109
110        if QGLFormat.hasOpenGL():
111            rendererGroup.addAction(self.glAction)
112
113        rendererGroup.addAction(self.imageAction)
114
115        self.menuBar().addMenu(rendererMenu)
116
117        openAction.triggered.connect(self.openFile)
118        quitAction.triggered.connect(QApplication.instance().quit)
119        rendererGroup.triggered.connect(self.setRenderer)
120
121        self.setCentralWidget(self.view)
122        self.setWindowTitle("SVG Viewer")
123
124    def openFile(self, path=None):
125        if not path:
126            path, _ = QFileDialog.getOpenFileName(self, "Open SVG File",
127                    self.currentPath, "SVG files (*.svg *.svgz *.svg.gz)")
128
129        if path:
130            svg_file = QFile(path)
131            if not svg_file.exists():
132                QMessageBox.critical(self, "Open SVG File",
133                        "Could not open file '%s'." % path)
134
135                self.outlineAction.setEnabled(False)
136                self.backgroundAction.setEnabled(False)
137                return
138
139            self.view.openFile(svg_file)
140
141            if not path.startswith(':/'):
142                self.currentPath = path
143                self.setWindowTitle("%s - SVGViewer" % self.currentPath)
144
145            self.outlineAction.setEnabled(True)
146            self.backgroundAction.setEnabled(True)
147
148            self.resize(self.view.sizeHint() + QSize(80, 80 + self.menuBar().height()))
149
150    def setRenderer(self, action):
151        if QGLFormat.hasOpenGL():
152            self.highQualityAntialiasingAction.setEnabled(False)
153
154        if action == self.nativeAction:
155            self.view.setRenderer(SvgView.Native)
156        elif action == self.glAction:
157            if QGLFormat.hasOpenGL():
158                self.highQualityAntialiasingAction.setEnabled(True)
159                self.view.setRenderer(SvgView.OpenGL)
160        elif action == self.imageAction:
161            self.view.setRenderer(SvgView.Image)
162
163
164class SvgView(QGraphicsView):
165    Native, OpenGL, Image = range(3)
166
167    def __init__(self, parent=None):
168        super(SvgView, self).__init__(parent)
169
170        self.renderer = SvgView.Native
171        self.svgItem = None
172        self.backgroundItem = None
173        self.outlineItem = None
174        self.image = QImage()
175
176        self.setScene(QGraphicsScene(self))
177        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
178        self.setDragMode(QGraphicsView.ScrollHandDrag)
179        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
180
181        # Prepare background check-board pattern.
182        tilePixmap = QPixmap(64, 64)
183        tilePixmap.fill(Qt.white)
184        tilePainter = QPainter(tilePixmap)
185        color = QColor(220, 220, 220)
186        tilePainter.fillRect(0, 0, 32, 32, color)
187        tilePainter.fillRect(32, 32, 32, 32, color)
188        tilePainter.end()
189
190        self.setBackgroundBrush(QBrush(tilePixmap))
191
192    def drawBackground(self, p, rect):
193        p.save()
194        p.resetTransform()
195        p.drawTiledPixmap(self.viewport().rect(),
196                self.backgroundBrush().texture())
197        p.restore()
198
199    def openFile(self, svg_file):
200        if not svg_file.exists():
201            return
202
203        s = self.scene()
204
205        if self.backgroundItem:
206            drawBackground = self.backgroundItem.isVisible()
207        else:
208            drawBackground = False
209
210        if self.outlineItem:
211            drawOutline = self.outlineItem.isVisible()
212        else:
213            drawOutline = True
214
215        s.clear()
216        self.resetTransform()
217
218        self.svgItem = QGraphicsSvgItem(svg_file.fileName())
219        self.svgItem.setFlags(QGraphicsItem.ItemClipsToShape)
220        self.svgItem.setCacheMode(QGraphicsItem.NoCache)
221        self.svgItem.setZValue(0)
222
223        self.backgroundItem = QGraphicsRectItem(self.svgItem.boundingRect())
224        self.backgroundItem.setBrush(Qt.white)
225        self.backgroundItem.setPen(QPen(Qt.NoPen))
226        self.backgroundItem.setVisible(drawBackground)
227        self.backgroundItem.setZValue(-1)
228
229        self.outlineItem = QGraphicsRectItem(self.svgItem.boundingRect())
230        outline = QPen(Qt.black, 2, Qt.DashLine)
231        outline.setCosmetic(True)
232        self.outlineItem.setPen(outline)
233        self.outlineItem.setBrush(QBrush(Qt.NoBrush))
234        self.outlineItem.setVisible(drawOutline)
235        self.outlineItem.setZValue(1)
236
237        s.addItem(self.backgroundItem)
238        s.addItem(self.svgItem)
239        s.addItem(self.outlineItem)
240
241        s.setSceneRect(self.outlineItem.boundingRect().adjusted(-10, -10, 10, 10))
242
243    def setRenderer(self, renderer):
244        self.renderer = renderer
245
246        if self.renderer == SvgView.OpenGL:
247            if QGLFormat.hasOpenGL():
248                self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers)))
249        else:
250            self.setViewport(QWidget())
251
252    def setHighQualityAntialiasing(self, highQualityAntialiasing):
253        if QGLFormat.hasOpenGL():
254            self.setRenderHint(QPainter.HighQualityAntialiasing,
255                    highQualityAntialiasing)
256
257    def setViewBackground(self, enable):
258        if self.backgroundItem:
259            self.backgroundItem.setVisible(enable)
260
261    def setViewOutline(self, enable):
262        if self.outlineItem:
263            self.outlineItem.setVisible(enable)
264
265    def paintEvent(self, event):
266        if self.renderer == SvgView.Image:
267            if self.image.size() != self.viewport().size():
268                self.image = QImage(self.viewport().size(),
269                        QImage.Format_ARGB32_Premultiplied)
270
271            imagePainter = QPainter(self.image)
272            QGraphicsView.render(self, imagePainter)
273            imagePainter.end()
274
275            p = QPainter(self.viewport())
276            p.drawImage(0, 0, self.image)
277        else:
278            super(SvgView, self).paintEvent(event)
279
280    def wheelEvent(self, event):
281        factor = pow(1.2, event.delta() / 240.0)
282        self.scale(factor, factor)
283        event.accept()
284
285
286if __name__ == '__main__':
287
288    import sys
289
290    app = QApplication(sys.argv)
291
292    window = MainWindow()
293    if len(sys.argv) == 2:
294        window.openFile(sys.argv[1])
295    else:
296        window.openFile(':/files/bubbles.svg')
297    window.show()
298    sys.exit(app.exec_())
299