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