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