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 QPointF, QSize, Qt 46from PyQt5.QtGui import QBrush, QFont, QFontMetrics, QPainter, QPainterPath 47from PyQt5.QtWidgets import QApplication, QComboBox, QGridLayout, QWidget 48 49 50NoTransformation, Translate, Rotate, Scale = range(4) 51 52class RenderArea(QWidget): 53 def __init__(self, parent=None): 54 super(RenderArea, self).__init__(parent) 55 56 newFont = self.font() 57 newFont.setPixelSize(12) 58 self.setFont(newFont) 59 60 fontMetrics = QFontMetrics(newFont) 61 self.xBoundingRect = fontMetrics.boundingRect("x") 62 self.yBoundingRect = fontMetrics.boundingRect("y") 63 self.shape = QPainterPath() 64 self.operations = [] 65 66 def setOperations(self, operations): 67 self.operations = operations 68 self.update() 69 70 def setShape(self, shape): 71 self.shape = shape 72 self.update() 73 74 def minimumSizeHint(self): 75 return QSize(182, 182) 76 77 def sizeHint(self): 78 return QSize(232, 232) 79 80 def paintEvent(self, event): 81 painter = QPainter(self) 82 painter.setRenderHint(QPainter.Antialiasing) 83 painter.fillRect(event.rect(), QBrush(Qt.white)) 84 85 painter.translate(66, 66) 86 87 painter.save() 88 self.transformPainter(painter) 89 self.drawShape(painter) 90 painter.restore() 91 92 self.drawOutline(painter) 93 94 self.transformPainter(painter) 95 self.drawCoordinates(painter) 96 97 def drawCoordinates(self, painter): 98 painter.setPen(Qt.red) 99 100 painter.drawLine(0, 0, 50, 0) 101 painter.drawLine(48, -2, 50, 0) 102 painter.drawLine(48, 2, 50, 0) 103 painter.drawText(60 - self.xBoundingRect.width() / 2, 104 0 + self.xBoundingRect.height() / 2, "x") 105 106 painter.drawLine(0, 0, 0, 50) 107 painter.drawLine(-2, 48, 0, 50) 108 painter.drawLine(2, 48, 0, 50) 109 painter.drawText(0 - self.yBoundingRect.width() / 2, 110 60 + self.yBoundingRect.height() / 2, "y") 111 112 def drawOutline(self, painter): 113 painter.setPen(Qt.darkGreen) 114 painter.setPen(Qt.DashLine) 115 painter.setBrush(Qt.NoBrush) 116 painter.drawRect(0, 0, 100, 100) 117 118 def drawShape(self, painter): 119 painter.fillPath(self.shape, Qt.blue) 120 121 def transformPainter(self, painter): 122 for operation in self.operations: 123 if operation == Translate: 124 painter.translate(50, 50) 125 126 elif operation == Scale: 127 painter.scale(0.75, 0.75) 128 129 elif operation == Rotate: 130 painter.rotate(60) 131 132 133class Window(QWidget): 134 135 operationTable = (NoTransformation, Rotate, Scale, Translate) 136 NumTransformedAreas = 3 137 138 def __init__(self): 139 super(Window, self).__init__() 140 141 self.originalRenderArea = RenderArea() 142 143 self.shapeComboBox = QComboBox() 144 self.shapeComboBox.addItem("Clock") 145 self.shapeComboBox.addItem("House") 146 self.shapeComboBox.addItem("Text") 147 self.shapeComboBox.addItem("Truck") 148 149 layout = QGridLayout() 150 layout.addWidget(self.originalRenderArea, 0, 0) 151 layout.addWidget(self.shapeComboBox, 1, 0) 152 153 self.transformedRenderAreas = list(range(Window.NumTransformedAreas)) 154 self.operationComboBoxes = list(range(Window.NumTransformedAreas)) 155 156 for i in range(Window.NumTransformedAreas): 157 self.transformedRenderAreas[i] = RenderArea() 158 159 self.operationComboBoxes[i] = QComboBox() 160 self.operationComboBoxes[i].addItem("No transformation") 161 self.operationComboBoxes[i].addItem(u"Rotate by 60\N{DEGREE SIGN}") 162 self.operationComboBoxes[i].addItem("Scale to 75%") 163 self.operationComboBoxes[i].addItem("Translate by (50, 50)") 164 165 self.operationComboBoxes[i].activated.connect(self.operationChanged) 166 167 layout.addWidget(self.transformedRenderAreas[i], 0, i + 1) 168 layout.addWidget(self.operationComboBoxes[i], 1, i + 1) 169 170 self.setLayout(layout) 171 self.setupShapes() 172 self.shapeSelected(0) 173 174 self.setWindowTitle("Transformations") 175 176 def setupShapes(self): 177 truck = QPainterPath() 178 truck.setFillRule(Qt.WindingFill) 179 truck.moveTo(0.0, 87.0) 180 truck.lineTo(0.0, 60.0) 181 truck.lineTo(10.0, 60.0) 182 truck.lineTo(35.0, 35.0) 183 truck.lineTo(100.0, 35.0) 184 truck.lineTo(100.0, 87.0) 185 truck.lineTo(0.0, 87.0) 186 truck.moveTo(17.0, 60.0) 187 truck.lineTo(55.0, 60.0) 188 truck.lineTo(55.0, 40.0) 189 truck.lineTo(37.0, 40.0) 190 truck.lineTo(17.0, 60.0) 191 truck.addEllipse(17.0, 75.0, 25.0, 25.0) 192 truck.addEllipse(63.0, 75.0, 25.0, 25.0) 193 194 clock = QPainterPath() 195 clock.addEllipse(-50.0, -50.0, 100.0, 100.0) 196 clock.addEllipse(-48.0, -48.0, 96.0, 96.0) 197 clock.moveTo(0.0, 0.0) 198 clock.lineTo(-2.0, -2.0) 199 clock.lineTo(0.0, -42.0) 200 clock.lineTo(2.0, -2.0) 201 clock.lineTo(0.0, 0.0) 202 clock.moveTo(0.0, 0.0) 203 clock.lineTo(2.732, -0.732) 204 clock.lineTo(24.495, 14.142) 205 clock.lineTo(0.732, 2.732) 206 clock.lineTo(0.0, 0.0) 207 208 house = QPainterPath() 209 house.moveTo(-45.0, -20.0) 210 house.lineTo(0.0, -45.0) 211 house.lineTo(45.0, -20.0) 212 house.lineTo(45.0, 45.0) 213 house.lineTo(-45.0, 45.0) 214 house.lineTo(-45.0, -20.0) 215 house.addRect(15.0, 5.0, 20.0, 35.0) 216 house.addRect(-35.0, -15.0, 25.0, 25.0) 217 218 text = QPainterPath() 219 font = QFont() 220 font.setPixelSize(50) 221 fontBoundingRect = QFontMetrics(font).boundingRect("Qt") 222 text.addText(-QPointF(fontBoundingRect.center()), font, "Qt") 223 224 self.shapes = (clock, house, text, truck) 225 226 self.shapeComboBox.activated.connect(self.shapeSelected) 227 228 def operationChanged(self): 229 operations = [] 230 for i in range(Window.NumTransformedAreas): 231 index = self.operationComboBoxes[i].currentIndex() 232 operations.append(Window.operationTable[index]) 233 self.transformedRenderAreas[i].setOperations(operations[:]) 234 235 def shapeSelected(self, index): 236 shape = self.shapes[index] 237 self.originalRenderArea.setShape(shape) 238 for i in range(Window.NumTransformedAreas): 239 self.transformedRenderAreas[i].setShape(shape) 240 241 242if __name__ == '__main__': 243 244 import sys 245 246 app = QApplication(sys.argv) 247 window = Window() 248 window.show() 249 sys.exit(app.exec_()) 250