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, QPoint, QRect, QSize, Qt 46from PyQt5.QtGui import QImage, QImageWriter, QPainter, QPen, qRgb 47from PyQt5.QtWidgets import (QAction, QApplication, QColorDialog, QFileDialog, 48 QInputDialog, QMainWindow, QMenu, QMessageBox, QWidget) 49from PyQt5.QtPrintSupport import QPrintDialog, QPrinter 50 51 52class ScribbleArea(QWidget): 53 def __init__(self, parent=None): 54 super(ScribbleArea, self).__init__(parent) 55 56 self.setAttribute(Qt.WA_StaticContents) 57 self.modified = False 58 self.scribbling = False 59 self.myPenWidth = 1 60 self.myPenColor = Qt.blue 61 self.image = QImage() 62 self.lastPoint = QPoint() 63 64 def openImage(self, fileName): 65 loadedImage = QImage() 66 if not loadedImage.load(fileName): 67 return False 68 69 newSize = loadedImage.size().expandedTo(self.size()) 70 self.resizeImage(loadedImage, newSize) 71 self.image = loadedImage 72 self.modified = False 73 self.update() 74 return True 75 76 def saveImage(self, fileName, fileFormat): 77 visibleImage = self.image 78 self.resizeImage(visibleImage, self.size()) 79 80 if visibleImage.save(fileName, fileFormat): 81 self.modified = False 82 return True 83 else: 84 return False 85 86 def setPenColor(self, newColor): 87 self.myPenColor = newColor 88 89 def setPenWidth(self, newWidth): 90 self.myPenWidth = newWidth 91 92 def clearImage(self): 93 self.image.fill(qRgb(255, 255, 255)) 94 self.modified = True 95 self.update() 96 97 def mousePressEvent(self, event): 98 if event.button() == Qt.LeftButton: 99 self.lastPoint = event.pos() 100 self.scribbling = True 101 102 def mouseMoveEvent(self, event): 103 if (event.buttons() & Qt.LeftButton) and self.scribbling: 104 self.drawLineTo(event.pos()) 105 106 def mouseReleaseEvent(self, event): 107 if event.button() == Qt.LeftButton and self.scribbling: 108 self.drawLineTo(event.pos()) 109 self.scribbling = False 110 111 def paintEvent(self, event): 112 painter = QPainter(self) 113 dirtyRect = event.rect() 114 painter.drawImage(dirtyRect, self.image, dirtyRect) 115 116 def resizeEvent(self, event): 117 if self.width() > self.image.width() or self.height() > self.image.height(): 118 newWidth = max(self.width() + 128, self.image.width()) 119 newHeight = max(self.height() + 128, self.image.height()) 120 self.resizeImage(self.image, QSize(newWidth, newHeight)) 121 self.update() 122 123 super(ScribbleArea, self).resizeEvent(event) 124 125 def drawLineTo(self, endPoint): 126 painter = QPainter(self.image) 127 painter.setPen(QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine, 128 Qt.RoundCap, Qt.RoundJoin)) 129 painter.drawLine(self.lastPoint, endPoint) 130 self.modified = True 131 132 rad = self.myPenWidth / 2 + 2 133 self.update(QRect(self.lastPoint, endPoint).normalized().adjusted(-rad, -rad, +rad, +rad)) 134 self.lastPoint = QPoint(endPoint) 135 136 def resizeImage(self, image, newSize): 137 if image.size() == newSize: 138 return 139 140 newImage = QImage(newSize, QImage.Format_RGB32) 141 newImage.fill(qRgb(255, 255, 255)) 142 painter = QPainter(newImage) 143 painter.drawImage(QPoint(0, 0), image) 144 self.image = newImage 145 146 def print_(self): 147 printer = QPrinter(QPrinter.HighResolution) 148 149 printDialog = QPrintDialog(printer, self) 150 if printDialog.exec_() == QPrintDialog.Accepted: 151 painter = QPainter(printer) 152 rect = painter.viewport() 153 size = self.image.size() 154 size.scale(rect.size(), Qt.KeepAspectRatio) 155 painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) 156 painter.setWindow(self.image.rect()) 157 painter.drawImage(0, 0, self.image) 158 painter.end() 159 160 def isModified(self): 161 return self.modified 162 163 def penColor(self): 164 return self.myPenColor 165 166 def penWidth(self): 167 return self.myPenWidth 168 169 170class MainWindow(QMainWindow): 171 def __init__(self): 172 super(MainWindow, self).__init__() 173 174 self.saveAsActs = [] 175 176 self.scribbleArea = ScribbleArea() 177 self.setCentralWidget(self.scribbleArea) 178 179 self.createActions() 180 self.createMenus() 181 182 self.setWindowTitle("Scribble") 183 self.resize(500, 500) 184 185 def closeEvent(self, event): 186 if self.maybeSave(): 187 event.accept() 188 else: 189 event.ignore() 190 191 def open(self): 192 if self.maybeSave(): 193 fileName, _ = QFileDialog.getOpenFileName(self, "Open File", 194 QDir.currentPath()) 195 if fileName: 196 self.scribbleArea.openImage(fileName) 197 198 def save(self): 199 action = self.sender() 200 fileFormat = action.data() 201 self.saveFile(fileFormat) 202 203 def penColor(self): 204 newColor = QColorDialog.getColor(self.scribbleArea.penColor()) 205 if newColor.isValid(): 206 self.scribbleArea.setPenColor(newColor) 207 208 def penWidth(self): 209 newWidth, ok = QInputDialog.getInt(self, "Scribble", 210 "Select pen width:", self.scribbleArea.penWidth(), 1, 50, 1) 211 if ok: 212 self.scribbleArea.setPenWidth(newWidth) 213 214 def about(self): 215 QMessageBox.about(self, "About Scribble", 216 "<p>The <b>Scribble</b> example shows how to use " 217 "QMainWindow as the base widget for an application, and how " 218 "to reimplement some of QWidget's event handlers to receive " 219 "the events generated for the application's widgets:</p>" 220 "<p> We reimplement the mouse event handlers to facilitate " 221 "drawing, the paint event handler to update the application " 222 "and the resize event handler to optimize the application's " 223 "appearance. In addition we reimplement the close event " 224 "handler to intercept the close events before terminating " 225 "the application.</p>" 226 "<p> The example also demonstrates how to use QPainter to " 227 "draw an image in real time, as well as to repaint " 228 "widgets.</p>") 229 230 def createActions(self): 231 self.openAct = QAction("&Open...", self, shortcut="Ctrl+O", 232 triggered=self.open) 233 234 for format in QImageWriter.supportedImageFormats(): 235 format = str(format) 236 237 text = format.upper() + "..." 238 239 action = QAction(text, self, triggered=self.save) 240 action.setData(format) 241 self.saveAsActs.append(action) 242 243 self.printAct = QAction("&Print...", self, 244 triggered=self.scribbleArea.print_) 245 246 self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", 247 triggered=self.close) 248 249 self.penColorAct = QAction("&Pen Color...", self, 250 triggered=self.penColor) 251 252 self.penWidthAct = QAction("Pen &Width...", self, 253 triggered=self.penWidth) 254 255 self.clearScreenAct = QAction("&Clear Screen", self, shortcut="Ctrl+L", 256 triggered=self.scribbleArea.clearImage) 257 258 self.aboutAct = QAction("&About", self, triggered=self.about) 259 260 self.aboutQtAct = QAction("About &Qt", self, 261 triggered=QApplication.instance().aboutQt) 262 263 def createMenus(self): 264 self.saveAsMenu = QMenu("&Save As", self) 265 for action in self.saveAsActs: 266 self.saveAsMenu.addAction(action) 267 268 fileMenu = QMenu("&File", self) 269 fileMenu.addAction(self.openAct) 270 fileMenu.addMenu(self.saveAsMenu) 271 fileMenu.addAction(self.printAct) 272 fileMenu.addSeparator() 273 fileMenu.addAction(self.exitAct) 274 275 optionMenu = QMenu("&Options", self) 276 optionMenu.addAction(self.penColorAct) 277 optionMenu.addAction(self.penWidthAct) 278 optionMenu.addSeparator() 279 optionMenu.addAction(self.clearScreenAct) 280 281 helpMenu = QMenu("&Help", self) 282 helpMenu.addAction(self.aboutAct) 283 helpMenu.addAction(self.aboutQtAct) 284 285 self.menuBar().addMenu(fileMenu) 286 self.menuBar().addMenu(optionMenu) 287 self.menuBar().addMenu(helpMenu) 288 289 def maybeSave(self): 290 if self.scribbleArea.isModified(): 291 ret = QMessageBox.warning(self, "Scribble", 292 "The image has been modified.\n" 293 "Do you want to save your changes?", 294 QMessageBox.Save | QMessageBox.Discard | 295 QMessageBox.Cancel) 296 if ret == QMessageBox.Save: 297 return self.saveFile('png') 298 elif ret == QMessageBox.Cancel: 299 return False 300 301 return True 302 303 def saveFile(self, fileFormat): 304 initialPath = QDir.currentPath() + '/untitled.' + fileFormat 305 306 fileName, _ = QFileDialog.getSaveFileName(self, "Save As", initialPath, 307 "%s Files (*.%s);;All Files (*)" % (fileFormat.upper(), fileFormat)) 308 if fileName: 309 return self.scribbleArea.saveImage(fileName, fileFormat) 310 311 return False 312 313 314if __name__ == '__main__': 315 316 import sys 317 318 app = QApplication(sys.argv) 319 window = MainWindow() 320 window.show() 321 sys.exit(app.exec_()) 322