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