1#############################################################################
2##
3## Copyright (C) 2013 Riverbank Computing Limited.
4## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
5## All rights reserved.
6##
7## This file is part of the examples of PyQt.
8##
9## $QT_BEGIN_LICENSE:LGPL$
10## Commercial Usage
11## Licensees holding valid Qt Commercial licenses may use this file in
12## accordance with the Qt Commercial License Agreement provided with the
13## Software or, alternatively, in accordance with the terms contained in
14## a written agreement between you and Nokia.
15##
16## GNU Lesser General Public License Usage
17## Alternatively, this file may be used under the terms of the GNU Lesser
18## General Public License version 2.1 as published by the Free Software
19## Foundation and appearing in the file LICENSE.LGPL included in the
20## packaging of this file.  Please review the following information to
21## ensure the GNU Lesser General Public License version 2.1 requirements
22## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23##
24## In addition, as a special exception, Nokia gives you certain additional
25## rights.  These rights are described in the Nokia Qt LGPL Exception
26## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27##
28## GNU General Public License Usage
29## Alternatively, this file may be used under the terms of the GNU
30## General Public License version 3.0 as published by the Free Software
31## Foundation and appearing in the file LICENSE.GPL included in the
32## packaging of this file.  Please review the following information to
33## ensure the GNU General Public License version 3.0 requirements will be
34## met: http://www.gnu.org/copyleft/gpl.html.
35##
36## If you have questions regarding the use of this file, please contact
37## Nokia at qt-info@nokia.com.
38## $QT_END_LICENSE$
39##
40#############################################################################
41
42
43from PyQt5.QtCore import QPointF, QRectF, qRound
44from PyQt5.QtGui import QColor, QPainter, QPixmap, QTransform
45from PyQt5.QtWidgets import QGraphicsObject
46
47from colors import Colors
48
49
50class SharedImage(object):
51    def __init__(self):
52        self.refCount = 0
53        self.image = None
54        self.pixmap = None
55        self.transform = QTransform()
56        self.unscaledBoundingRect = QRectF()
57
58
59class DemoItem(QGraphicsObject):
60    _sharedImageHash = {}
61
62    _transform = QTransform()
63
64    def __init__(self, parent=None):
65        super(DemoItem, self).__init__(parent)
66
67        self.noSubPixeling = False
68        self.currentAnimation = None
69        self.currGuide = None
70        self.guideFrame = 0.0
71
72        self._sharedImage = SharedImage()
73        self._sharedImage.refCount += 1
74        self._hashKey = ''
75
76    def __del__(self):
77        self._sharedImage.refCount -= 1
78        if self._sharedImage.refCount == 0:
79            if self._hashKey:
80                del DemoItem._sharedImageHash[self._hashKey]
81
82    def animationStarted(self, id=0):
83        pass
84
85    def animationStopped(self, id=0):
86        pass
87
88    def setRecursiveVisible(self, visible):
89        self.setVisible(visible)
90        for c in self.childItems():
91            c.setVisible(visible)
92
93    def useGuide(self, guide, startFrame):
94        self.guideFrame = startFrame
95        while self.guideFrame > guide.startLength + guide.length():
96            if guide.nextGuide == guide.firstGuide:
97                break
98
99            guide = guide.nextGuide
100
101        self.currGuide = guide
102
103    def guideAdvance(self, distance):
104        self.guideFrame += distance
105        while self.guideFrame > self.currGuide.startLength + self.currGuide.length():
106            self.currGuide = self.currGuide.nextGuide
107            if self.currGuide == self.currGuide.firstGuide:
108                self.guideFrame -= self.currGuide.lengthAll()
109
110    def guideMove(self, moveSpeed):
111        self.currGuide.guide(self, moveSpeed)
112
113    def setPosUsingSheepDog(self, dest, sceneFence):
114        self.setPos(dest)
115        if sceneFence.isNull():
116            return
117
118        itemWidth = self.boundingRect().width()
119        itemHeight = self.boundingRect().height()
120        fenceRight = sceneFence.x() + sceneFence.width()
121        fenceBottom = sceneFence.y() + sceneFence.height()
122
123        if self.scenePos().x() < sceneFence.x():
124            self.moveBy(self.mapFromScene(QPointF(sceneFence.x(), 0)).x(), 0)
125
126        if self.scenePos().x() > fenceRight - itemWidth:
127            self.moveBy(self.mapFromScene(QPointF(fenceRight - itemWidth, 0)).x(), 0)
128
129        if self.scenePos().y() < sceneFence.y():
130            self.moveBy(0, self.mapFromScene(QPointF(0, sceneFence.y())).y())
131
132        if self.scenePos().y() > fenceBottom - itemHeight:
133            self.moveBy(0, self.mapFromScene(QPointF(0, fenceBottom - itemHeight)).y())
134
135    def setGuidedPos(self, pos):
136        # Make sure we have a copy.
137        self.guidedPos = QPointF(pos)
138
139    def getGuidedPos(self):
140        # Return a copy so that it can be changed.
141        return QPointF(self.guidedPos)
142
143    @staticmethod
144    def setTransform(transform):
145        DemoItem._transform = transform
146
147    def useSharedImage(self, hashKey):
148        self._hashKey = hashKey
149        if hashKey not in DemoItem._sharedImageHash:
150            DemoItem._sharedImageHash[hashKey] = self._sharedImage
151        else:
152            self._sharedImage.refCount -= 1
153            self._sharedImage = DemoItem._sharedImageHash[hashKey]
154            self._sharedImage.refCount += 1
155
156    def createImage(self, transform):
157        return None
158
159    def _validateImage(self):
160        if (self._sharedImage.transform != DemoItem._transform and not Colors.noRescale) or (self._sharedImage.image is None and self._sharedImage.pixmap is None):
161            # (Re)create image according to new transform.
162            self._sharedImage.image = None
163            self._sharedImage.pixmap = None
164            self._sharedImage.transform = DemoItem._transform
165
166            # Let subclass create and draw a new image according to the new
167            # transform.
168            if Colors.noRescale:
169                transform = QTransform()
170            else:
171                transform = DemoItem._transform
172            image = self.createImage(transform)
173            if image is not None:
174                if Colors.showBoundingRect:
175                    # Draw red transparent rect.
176                    painter = QPainter(image)
177                    painter.fillRect(image.rect(), QColor(255, 0, 0, 50))
178                    painter.end()
179
180                self._sharedImage.unscaledBoundingRect = self._sharedImage.transform.inverted()[0].mapRect(QRectF(image.rect()))
181
182                if Colors.usePixmaps:
183                    if image.isNull():
184                        self._sharedImage.pixmap = QPixmap(1, 1)
185                    else:
186                        self._sharedImage.pixmap = QPixmap(image.size())
187
188                    self._sharedImage.pixmap.fill(QColor(0, 0, 0, 0))
189                    painter = QPainter(self._sharedImage.pixmap)
190                    painter.drawImage(0, 0, image)
191                else:
192                    self._sharedImage.image = image
193
194                return True
195            else:
196                return False
197
198        return True
199
200    def boundingRect(self):
201        self._validateImage()
202        return self._sharedImage.unscaledBoundingRect
203
204    def paint(self, painter, option=None, widget=None):
205        if self._validateImage():
206            wasSmoothPixmapTransform = painter.testRenderHint(QPainter.SmoothPixmapTransform)
207            painter.setRenderHint(QPainter.SmoothPixmapTransform)
208
209            if Colors.noRescale:
210                # Let the painter scale the image for us.  This may degrade
211                # both quality and performance.
212                if self._sharedImage.image is not None:
213                    painter.drawImage(self.pos(), self._sharedImage.image)
214                else:
215                    painter.drawPixmap(self.pos(), self._sharedImage.pixmap)
216            else:
217                m = painter.worldTransform()
218                painter.setWorldTransform(QTransform())
219
220                x = m.dx()
221                y = m.dy()
222                if self.noSubPixeling:
223                    x = qRound(x)
224                    y = qRound(y)
225
226                if self._sharedImage.image is not None:
227                    painter.drawImage(QPointF(x, y), self._sharedImage.image)
228                else:
229                    painter.drawPixmap(QPointF(x, y), self._sharedImage.pixmap)
230
231            if not wasSmoothPixmapTransform:
232                painter.setRenderHint(QPainter.SmoothPixmapTransform,
233                        False)
234
235    def collidesWithItem(self, item, mode):
236        return False
237