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