1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2015 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 (QEasingCurve, QFileInfo, QLineF, QMimeData,
46        QParallelAnimationGroup, QPoint, QPointF, QPropertyAnimation, qrand,
47        QRectF, qsrand, Qt, QTime)
48from PyQt5.QtGui import (QBrush, QColor, QDrag, QImage, QPainter, QPen,
49        QPixmap, QTransform)
50from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsObject,
51        QGraphicsScene, QGraphicsView)
52
53
54class ColorItem(QGraphicsItem):
55    n = 0
56
57    def __init__(self):
58        super(ColorItem, self).__init__()
59
60        self.color = QColor(qrand() % 256, qrand() % 256, qrand() % 256)
61
62        self.setToolTip(
63            "QColor(%d, %d, %d)\nClick and drag this color onto the robot!" %
64              (self.color.red(), self.color.green(), self.color.blue())
65        )
66        self.setCursor(Qt.OpenHandCursor)
67        self.setAcceptedMouseButtons(Qt.LeftButton)
68
69    def boundingRect(self):
70        return QRectF(-15.5, -15.5, 34, 34)
71
72    def paint(self, painter, option, widget):
73        painter.setPen(Qt.NoPen)
74        painter.setBrush(Qt.darkGray)
75        painter.drawEllipse(-12, -12, 30, 30)
76        painter.setPen(QPen(Qt.black, 1))
77        painter.setBrush(QBrush(self.color))
78        painter.drawEllipse(-15, -15, 30, 30)
79
80    def mousePressEvent(self, event):
81        self.setCursor(Qt.ClosedHandCursor)
82
83    def mouseMoveEvent(self, event):
84        if QLineF(QPointF(event.screenPos()), QPointF(event.buttonDownScreenPos(Qt.LeftButton))).length() < QApplication.startDragDistance():
85            return
86
87        drag = QDrag(event.widget())
88        mime = QMimeData()
89        drag.setMimeData(mime)
90
91        ColorItem.n += 1
92        if ColorItem.n > 2 and qrand() % 3 == 0:
93            root = QFileInfo(__file__).absolutePath()
94
95            image = QImage(root + '/images/head.png')
96            mime.setImageData(image)
97            drag.setPixmap(QPixmap.fromImage(image).scaled(30,40))
98            drag.setHotSpot(QPoint(15, 30))
99        else:
100            mime.setColorData(self.color)
101            mime.setText("#%02x%02x%02x" % (self.color.red(), self.color.green(), self.color.blue()))
102
103            pixmap = QPixmap(34, 34)
104            pixmap.fill(Qt.white)
105
106            painter = QPainter(pixmap)
107            painter.translate(15, 15)
108            painter.setRenderHint(QPainter.Antialiasing)
109            self.paint(painter, None, None)
110            painter.end()
111
112            pixmap.setMask(pixmap.createHeuristicMask())
113
114            drag.setPixmap(pixmap)
115            drag.setHotSpot(QPoint(15, 20))
116
117        drag.exec_()
118        self.setCursor(Qt.OpenHandCursor)
119
120    def mouseReleaseEvent(self, event):
121        self.setCursor(Qt.OpenHandCursor)
122
123
124class RobotPart(QGraphicsObject):
125    def __init__(self, parent=None):
126        super(RobotPart, self).__init__(parent)
127
128        self.color = QColor(Qt.lightGray)
129        self.dragOver = False
130
131        self.setAcceptDrops(True)
132
133    def dragEnterEvent(self, event):
134        if event.mimeData().hasColor():
135            event.setAccepted(True)
136            self.dragOver = True
137            self.update()
138        else:
139            event.setAccepted(False)
140
141    def dragLeaveEvent(self, event):
142        self.dragOver = False
143        self.update()
144
145    def dropEvent(self, event):
146        self.dragOver = False
147        if event.mimeData().hasColor():
148            self.color = QColor(event.mimeData().colorData())
149
150        self.update()
151
152
153class RobotHead(RobotPart):
154    def __init__(self, parent=None):
155        super(RobotHead, self).__init__(parent)
156
157        self.pixmap = QPixmap()
158
159    def boundingRect(self):
160        return QRectF(-15, -50, 30, 50)
161
162    def paint(self, painter, option, widget=None):
163        if self.pixmap.isNull():
164            painter.setBrush(self.color.lighter(130) if self.dragOver else self.color)
165            painter.drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt.RelativeSize)
166            painter.setBrush(Qt.white)
167            painter.drawEllipse(-7, -3 - 20, 7, 7)
168            painter.drawEllipse(0, -3 - 20, 7, 7)
169            painter.setBrush(Qt.black)
170            painter.drawEllipse(-5, -1 - 20, 2, 2)
171            painter.drawEllipse(2, -1 - 20, 2, 2)
172            painter.setPen(QPen(Qt.black, 2))
173            painter.setBrush(Qt.NoBrush)
174            painter.drawArc(-6, -2 - 20, 12, 15, 190 * 16, 160 * 16)
175        else:
176            painter.scale(.2272, .2824)
177            painter.drawPixmap(QPointF(-15*4.4, -50*3.54), self.pixmap)
178
179    def dragEnterEvent(self, event):
180        if event.mimeData().hasImage():
181            event.setAccepted(True)
182            self.dragOver = True
183            self.update()
184        else:
185            super(RobotHead, self).dragEnterEvent(event)
186
187    def dropEvent(self, event):
188        if event.mimeData().hasImage():
189            self.dragOver = False
190            self.pixmap = QPixmap(event.mimeData().imageData())
191            self.update()
192        else:
193            super(RobotHead, self).dropEvent(event)
194
195
196class RobotTorso(RobotPart):
197    def boundingRect(self):
198        return QRectF(-30, -20, 60, 60)
199
200    def paint(self, painter, option, widget=None):
201        painter.setBrush(self.color.lighter(130) if self.dragOver else self.color)
202        painter.drawRoundedRect(-20, -20, 40, 60, 25, 25, Qt.RelativeSize)
203        painter.drawEllipse(-25, -20, 20, 20)
204        painter.drawEllipse(5, -20, 20, 20)
205        painter.drawEllipse(-20, 22, 20, 20)
206        painter.drawEllipse(0, 22, 20, 20)
207
208
209class RobotLimb(RobotPart):
210    def boundingRect(self):
211        return QRectF(-5, -5, 40, 10)
212
213    def paint(self, painter, option, widget=None):
214        painter.setBrush(self.color.lighter(130) if self.dragOver else  self.color)
215        painter.drawRoundedRect(self.boundingRect(), 50, 50, Qt.RelativeSize)
216        painter.drawEllipse(-5, -5, 10, 10)
217
218
219class Robot(RobotPart):
220    def __init__(self):
221        super(Robot, self).__init__()
222
223        self.setFlag(self.ItemHasNoContents)
224
225        self.torsoItem = RobotTorso(self)
226        self.headItem = RobotHead(self.torsoItem)
227        self.upperLeftArmItem = RobotLimb(self.torsoItem)
228        self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem)
229        self.upperRightArmItem = RobotLimb(self.torsoItem)
230        self.lowerRightArmItem = RobotLimb(self.upperRightArmItem)
231        self.upperRightLegItem = RobotLimb(self.torsoItem)
232        self.lowerRightLegItem = RobotLimb(self.upperRightLegItem)
233        self.upperLeftLegItem = RobotLimb(self.torsoItem)
234        self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem)
235
236        settings = (
237        #    Item                       Position        Rotation  Scale
238        #                                x     y    start    end
239            (self.headItem,              0,  -18,      20,   -20,   1.1),
240            (self.upperLeftArmItem,    -15,  -10,     190,   180,     0),
241            (self.lowerLeftArmItem,     30,    0,      50,    10,     0),
242            (self.upperRightArmItem,    15,  -10,     300,   310,     0),
243            (self.lowerRightArmItem,    30,    0,       0,   -70,     0),
244            (self.upperRightLegItem,    10,   32,      40,   120,     0),
245            (self.lowerRightLegItem,    30,    0,      10,    50,     0),
246            (self.upperLeftLegItem,    -10,   32,     150,    80,     0),
247            (self.lowerLeftLegItem,     30,    0,      70,    10,     0),
248            (self.torsoItem,             0,    0,       5,   -20,     0),
249        )
250
251        animation = QParallelAnimationGroup(self)
252        for item, pos_x, pos_y, start_rot, end_rot, scale in settings:
253            item.setPos(pos_x, pos_y)
254
255            rot_animation = QPropertyAnimation(item, b'rotation')
256            rot_animation.setStartValue(start_rot)
257            rot_animation.setEndValue(end_rot)
258            rot_animation.setEasingCurve(QEasingCurve.SineCurve)
259            rot_animation.setDuration(2000)
260            animation.addAnimation(rot_animation)
261
262            if scale > 0:
263                scale_animation = QPropertyAnimation(item, b'scale')
264                scale_animation.setEndValue(scale)
265                scale_animation.setEasingCurve(QEasingCurve.SineCurve)
266                scale_animation.setDuration(2000)
267                animation.addAnimation(scale_animation)
268
269        animation.setLoopCount(-1)
270        animation.start()
271
272    def boundingRect(self):
273        return QRectF()
274
275    def paint(self, painter, option, widget=None):
276        pass
277
278
279class GraphicsView(QGraphicsView):
280
281    def resizeEvent(self, e):
282        pass
283
284
285if __name__== '__main__':
286
287    import sys
288    import math
289
290    app = QApplication(sys.argv)
291
292    qsrand(QTime(0, 0, 0).secsTo(QTime.currentTime()))
293
294    scene = QGraphicsScene(-200, -200, 400, 400)
295
296    for i in range(10):
297        item = ColorItem()
298        angle = i*6.28 / 10.0
299        item.setPos(math.sin(angle)*150, math.cos(angle)*150)
300        scene.addItem(item)
301
302    robot = Robot()
303    robot.setTransform(QTransform.fromScale(1.2, 1.2), True)
304    robot.setPos(0, -20)
305    scene.addItem(robot)
306
307    view = GraphicsView(scene)
308    view.setRenderHint(QPainter.Antialiasing)
309    view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
310    view.setBackgroundBrush(QColor(230, 200, 167))
311    view.setWindowTitle("Drag and Drop Robot")
312    view.show()
313
314    sys.exit(app.exec_())
315