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
43import math
44import random
45
46from PyQt5.QtCore import QLineF, QPointF, QRectF, Qt, QTime
47
48from colors import Colors
49from demoitem import DemoItem
50from demoitemanimation import DemoItemAnimation
51from guidecircle import GuideCircle
52from guideline import GuideLine
53from letteritem import LetterItem
54
55
56class TickerPostEffect(object):
57    def tick(self, adjust):
58        pass
59
60    def transform(self, item, pos):
61        pass
62
63
64class PostRotateXY(TickerPostEffect):
65    def __init__(self, speedx, speedy, curvx, curvy):
66        super(PostRotateXY, self).__init__()
67
68        self.currRotX = 0.0
69        self.currRotY = 0.0
70
71        self.speedx = speedx
72        self.speedy = speedy
73        self.curvx = curvx
74        self.curvy = curvy
75
76    def tick(self, adjust):
77        self.currRotX += self.speedx * adjust
78        self.currRotY += self.speedy * adjust
79
80    def transform(self, item, pos):
81        parent = item.parentItem()
82        center = parent.boundingRect().center()
83        pos.setX(center.x() + (pos.x() - center.x()) * math.cos(self.currRotX + pos.x() * self.curvx))
84        pos.setY(center.y() + (pos.y() - center.y()) * math.cos(self.currRotY + pos.y() * self.curvy))
85
86
87class PostRotateXYTwist(PostRotateXY):
88    def transform(self, item, pos):
89        parent = item.parentItem()
90        center = parent.boundingRect().center()
91        pos.setX(center.x() + (pos.x() - center.x()) * math.cos(self.currRotX + pos.y() * self.curvx))
92        pos.setY(center.y() + (pos.y() - center.y()) * math.cos(self.currRotY + pos.x() * self.curvy))
93
94
95class TickerEffect(object):
96    Normal, Intro, Outro = range(3)
97
98    def __init__(self, letters):
99        self.postEffect = TickerPostEffect()
100        self.status = TickerEffect.Intro
101        self.letters = letters
102        self.morphSpeed = self.normalMorphSpeed = Colors.tickerMorphSpeed
103        self.moveSpeed = self.normalMoveSpeed = Colors.tickerMoveSpeed
104        self.useSheepDog = True
105        self.morphBetweenModels = not Colors.noTickerMorph
106
107    def setPostEffect(self, effect):
108        self.postEffect = effect
109
110    def slowDownAfterIntro(self, adjust):
111        if self.morphBetweenModels:
112            if self.status == TickerEffect.Intro:
113                dec = 0.1 * adjust
114                self.moveSpeed -= dec
115                if self.moveSpeed < Colors.tickerMoveSpeed:
116                    self.moveSpeed = self.normalMoveSpeed
117                    self.morphSpeed = self.normalMorphSpeed
118                    self.status = TickerEffect.Normal
119
120    def moveLetters(self, adjust):
121        adaptedMoveSpeed = self.moveSpeed * adjust
122        adaptedMorphSpeed = self.morphSpeed * adjust
123        self.postEffect.tick(adjust)
124
125        if self.morphBetweenModels:
126            move_speed = adaptedMoveSpeed
127            morph_speed = adaptedMorphSpeed
128        else:
129            move_speed = Colors.tickerMoveSpeed
130            morph_speed = -1
131
132        for letter in self.letters:
133            letter.guideAdvance(move_speed)
134            letter.guideMove(morph_speed)
135
136            pos = letter.getGuidedPos()
137            self.postEffect.transform(letter, pos)
138
139            if self.useSheepDog:
140                letter.setPosUsingSheepDog(pos, QRectF(0, 0, 800, 600))
141            else:
142                letter.setPos(pos)
143
144    def tick(self, adjust):
145        self.slowDownAfterIntro(adjust)
146        self.moveLetters(adjust)
147
148
149class EffectWhirlWind(TickerEffect):
150    def __init__(self, letters):
151        super(EffectWhirlWind, self).__init__(letters)
152
153        self.moveSpeed = 50
154
155        for letter in self.letters:
156            letter.setGuidedPos(QPointF(0, 100))
157
158
159class EffectSnake(TickerEffect):
160    def __init__(self, letters):
161        super(EffectSnake, self).__init__(letters)
162
163        self.moveSpeed = 40
164
165        for i, letter in enumerate(self.letters):
166            letter.setGuidedPos(QPointF(0, -250 - (i * 5)))
167
168
169class EffectScan(TickerEffect):
170    def __init__(self, letters):
171        super(EffectScan, self).__init__(letters)
172
173        for letter in self.letters:
174            letter.setGuidedPos(QPointF(100, -300))
175
176
177class EffectRaindrops(TickerEffect):
178    def __init__(self, letters):
179        super(EffectRaindrops, self).__init__(letters)
180
181        for letter in self.letters:
182            letter.setGuidedPos(QPointF(random.randint(-100, 100),
183                    random.randint(-200, 1100)))
184
185
186class EffectLine(TickerEffect):
187    def __init__(self, letters):
188        super(EffectLine, self).__init__(letters)
189
190        for i, letter in enumerate(self.letters):
191            letter.setGuidedPos(QPointF(100, 500 + i * 20))
192
193
194class ItemCircleAnimation(DemoItem):
195    def __init__(self, parent=None):
196        super(ItemCircleAnimation, self).__init__(parent)
197
198        self.letterList = []
199        self.letterCount = Colors.tickerLetterCount
200        self.scale = 1.0
201        self.showCount = -1
202        self.tickOnPaint = False
203        self.paused = False
204        self.doIntroTransitions = True
205        self.setAcceptHoverEvents(True)
206        self.setCursor(Qt.OpenHandCursor)
207        self.setupGuides()
208        self.setupLetters()
209        self.useGuideQt()
210        self.effect = None
211
212        self.mouseMoveLastPosition = QPointF()
213        self.tickTimer = QTime()
214
215    def createLetter(self, c):
216        letter = LetterItem(c, self)
217        self.letterList.append(letter)
218
219    def setupLetters(self):
220        self.letterList = []
221
222        s = Colors.tickerText
223        tlen = len(s)
224        room = self.letterCount
225        while room >= tlen:
226            for c in s:
227                self.createLetter(c)
228
229            room -= tlen
230
231        # Fill in with blanks.
232        while room > 0:
233            self.createLetter(' ')
234            room -= 1
235
236    def setupGuides(self):
237        x = 0
238        y = 20
239
240        self.qtGuide1 = GuideCircle(QRectF(x, y, 260, 260), -36, 342)
241        GuideLine(QPointF(x + 240, y + 268), self.qtGuide1)
242        GuideLine(QPointF(x + 265, y + 246), self.qtGuide1)
243        GuideLine(QPointF(x + 158, y + 134), self.qtGuide1)
244        GuideLine(QPointF(x + 184, y + 109), self.qtGuide1)
245        GuideLine(QPointF(x + 160, y +  82), self.qtGuide1)
246        GuideLine(QPointF(x +  77, y + 163), self.qtGuide1)
247        GuideLine(QPointF(x + 100, y + 190), self.qtGuide1)
248        GuideLine(QPointF(x + 132, y + 159), self.qtGuide1)
249        GuideLine(QPointF(x + 188, y + 211), self.qtGuide1)
250        GuideCircle(QRectF(x + 30, y + 30, 200, 200), -30, 336, GuideCircle.CW, self.qtGuide1)
251        GuideLine(QPointF(x + 238, y + 201), self.qtGuide1)
252
253        y = 30
254        self.qtGuide2 = GuideCircle(QRectF(x + 30, y + 30, 200, 200), 135, 270, GuideCircle.CCW)
255        GuideLine(QPointF(x + 222, y + 38), self.qtGuide2)
256        GuideCircle(QRectF(x, y, 260, 260), 135, 270, GuideCircle.CW, self.qtGuide2)
257        GuideLine(QPointF(x + 59, y + 59), self.qtGuide2)
258
259        x = 115
260        y = 10
261        self.qtGuide3 = GuideLine(QLineF(x, y, x + 30, y))
262        GuideLine(QPointF(x + 30, y + 170), self.qtGuide3)
263        GuideLine(QPointF(x, y + 170), self.qtGuide3)
264        GuideLine(QPointF(x, y), self.qtGuide3)
265
266        self.qtGuide1.setFence(QRectF(0, 0, 800, 600))
267        self.qtGuide2.setFence(QRectF(0, 0, 800, 600))
268        self.qtGuide3.setFence(QRectF(0, 0, 800, 600))
269
270    def useGuide(self, guide, firstLetter, lastLetter):
271        padding = guide.lengthAll() / float(lastLetter - firstLetter)
272
273        for i, letter in enumerate(self.letterList[firstLetter:lastLetter]):
274            letter.useGuide(guide, i * padding)
275
276    def useGuideQt(self):
277        if self.currGuide is not self.qtGuide1:
278            self.useGuide(self.qtGuide1, 0, self.letterCount)
279            self.currGuide = self.qtGuide1
280
281    def useGuideTt(self):
282        if self.currGuide is not self.qtGuide2:
283            split = int(self.letterCount * 5.0 / 7.0)
284            self.useGuide(self.qtGuide2, 0, split)
285            self.useGuide(self.qtGuide3, split, self.letterCount)
286            self.currGuide = self.qtGuide2
287
288    def boundingRect(self):
289        return QRectF(0, 0, 300, 320)
290
291    def prepare(self):
292        pass
293
294    def switchToNextEffect(self):
295        self.showCount += 1
296
297        if self.showCount == 1:
298            self.effect = EffectSnake(self.letterList)
299        elif self.showCount == 2:
300            self.effect = EffectLine(self.letterList)
301            self.effect.setPostEffect(PostRotateXYTwist(0.01, 0.0, 0.003, 0.0))
302        elif self.showCount == 3:
303            self.effect = EffectRaindrops(self.letterList)
304            self.effect.setPostEffect(PostRotateXYTwist(0.01, 0.005, 0.003, 0.003))
305        elif self.showCount == 4:
306            self.effect = EffectScan(self.letterList)
307            self.effect.normalMoveSpeed = 0
308            self.effect.setPostEffect(PostRotateXY(0.008, 0.0, 0.005, 0.0))
309        else:
310            self.showCount = 0
311            self.effect = EffectWhirlWind(self.letterList)
312
313    def animationStarted(self, id):
314        if id == DemoItemAnimation.ANIM_IN:
315            if self.doIntroTransitions:
316                # Make all letters disappear.
317                for letter in self.letterList:
318                    letter.setPos(1000, 0)
319
320                self.switchToNextEffect()
321                self.useGuideQt()
322                self.scale = 1.0
323
324                # The first time we run, we have a rather large delay to
325                # perform benchmark before the ticker shows.  But now, since we
326                # are showing, use a more appropriate value.
327                self.currentAnimation.setStartDelay(1500)
328        elif self.effect is not None:
329            self.effect.useSheepDog = False
330
331        self.tickTimer = QTime.currentTime()
332
333    def swapModel(self):
334        if self.currGuide is self.qtGuide2:
335            self.useGuideQt()
336        else:
337            self.useGuideTt()
338
339    def hoverEnterEvent(self, event):
340        # Skip swap here to enhance ticker dragging.
341        pass
342
343    def hoverLeaveEvent(self, event):
344        self.swapModel()
345
346    def setTickerScale(self, s):
347        self.scale = s
348        self.qtGuide1.setScale(self.scale, self.scale)
349        self.qtGuide2.setScale(self.scale, self.scale)
350        self.qtGuide3.setScale(self.scale, self.scale)
351
352    def mousePressEvent(self, event):
353        self.mouseMoveLastPosition = event.scenePos();
354
355        if event.button() == Qt.LeftButton:
356            self.setCursor(Qt.ClosedHandCursor)
357        else:
358            self.switchToNextEffect()
359
360    def mouseReleaseEvent(self, event):
361        if event.button() == Qt.LeftButton:
362            self.setCursor(Qt.OpenHandCursor)
363
364    def mouseMoveEvent(self, event):
365        newPosition = event.scenePos()
366        self.setPosUsingSheepDog(self.pos() + newPosition - self.mouseMoveLastPosition, QRectF(-260, -280, 1350, 1160))
367        self.mouseMoveLastPosition = newPosition
368
369    def wheelEvent(self, event):
370        if event.angleDelta().y() > 0:
371            self.effect.moveSpeed -= 0.20
372        else:
373            self.effect.moveSpeed += 0.20
374
375        if self.effect.moveSpeed < 0:
376            self.effect.moveSpeed = 0.0
377
378    def pause(self, on):
379        self.paused = on
380        self.tickTimer = QTime.currentTime()
381
382    def tick(self):
383        if self.paused or not self.effect:
384            return
385
386        t = self.tickTimer.msecsTo(QTime.currentTime())
387        self.tickTimer = QTime.currentTime()
388        self.effect.tick(t / 10.0)
389
390    def paint(self, painter, opt, widget):
391        if self.tickOnPaint:
392            self.tick()
393