Change copyrights from Nokia to Digia
[profile/ivi/qtdeclarative.git] / src / qml / animations / qabstractanimationjob.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QtCore/qthreadstorage.h>
43
44 #include "private/qabstractanimationjob_p.h"
45 #include "private/qanimationgroupjob_p.h"
46 #include "private/qanimationjobutil_p.h"
47
48 #define DEFAULT_TIMER_INTERVAL 16
49
50 QT_BEGIN_NAMESPACE
51
52 #ifndef QT_NO_THREAD
53 Q_GLOBAL_STATIC(QThreadStorage<QQmlAnimationTimer *>, animationTimer)
54 #endif
55
56 QQmlAnimationTimer::QQmlAnimationTimer() :
57     QAbstractAnimationTimer(), lastTick(0), lastDelta(0),
58     currentAnimationIdx(0), insideTick(false),
59     startAnimationPending(false), stopTimerPending(false),
60     runningLeafAnimations(0)
61 {
62 }
63
64 QQmlAnimationTimer *QQmlAnimationTimer::instance(bool create)
65 {
66     QQmlAnimationTimer *inst;
67 #ifndef QT_NO_THREAD
68     if (create && !animationTimer()->hasLocalData()) {
69         inst = new QQmlAnimationTimer;
70         animationTimer()->setLocalData(inst);
71     } else {
72         inst = animationTimer() ? animationTimer()->localData() : 0;
73     }
74 #else
75     static QAnimationTimer unifiedTimer;
76     inst = &unifiedTimer;
77 #endif
78     return inst;
79 }
80
81 QQmlAnimationTimer *QQmlAnimationTimer::instance()
82 {
83     return instance(true);
84 }
85
86 void QQmlAnimationTimer::ensureTimerUpdate()
87 {
88     QQmlAnimationTimer *inst = QQmlAnimationTimer::instance(false);
89     QUnifiedTimer *instU = QUnifiedTimer::instance(false);
90     if (instU && inst && inst->isPaused)
91         instU->updateAnimationTimers(-1);
92 }
93
94 void QQmlAnimationTimer::updateAnimationsTime(qint64 delta)
95 {
96     //setCurrentTime can get this called again while we're the for loop. At least with pauseAnimations
97     if (insideTick)
98         return;
99
100     lastTick += delta;
101     lastDelta = delta;
102
103     //we make sure we only call update time if the time has actually changed
104     //it might happen in some cases that the time doesn't change because events are delayed
105     //when the CPU load is high
106     if (delta) {
107         insideTick = true;
108         for (currentAnimationIdx = 0; currentAnimationIdx < animations.count(); ++currentAnimationIdx) {
109             QAbstractAnimationJob *animation = animations.at(currentAnimationIdx);
110             int elapsed = animation->m_totalCurrentTime
111                           + (animation->direction() == QAbstractAnimationJob::Forward ? delta : -delta);
112             animation->setCurrentTime(elapsed);
113         }
114         insideTick = false;
115         currentAnimationIdx = 0;
116     }
117 }
118
119 void QQmlAnimationTimer::updateAnimationTimer()
120 {
121     QQmlAnimationTimer *inst = QQmlAnimationTimer::instance(false);
122     if (inst)
123         inst->restartAnimationTimer();
124 }
125
126 void QQmlAnimationTimer::restartAnimationTimer()
127 {
128     if (runningLeafAnimations == 0 && !runningPauseAnimations.isEmpty())
129         QUnifiedTimer::pauseAnimationTimer(this, closestPauseAnimationTimeToFinish());
130     else if (isPaused)
131         QUnifiedTimer::resumeAnimationTimer(this);
132     else if (!isRegistered)
133         QUnifiedTimer::startAnimationTimer(this);
134 }
135
136 void QQmlAnimationTimer::startAnimations()
137 {
138     startAnimationPending = false;
139     //force timer to update, which prevents large deltas for our newly added animations
140     if (!animations.isEmpty())
141         QUnifiedTimer::instance()->maybeUpdateAnimationsToCurrentTime();
142
143     //we transfer the waiting animations into the "really running" state
144     animations += animationsToStart;
145     animationsToStart.clear();
146     if (!animations.isEmpty())
147         restartAnimationTimer();
148 }
149
150 void QQmlAnimationTimer::stopTimer()
151 {
152     stopTimerPending = false;
153     if (animations.isEmpty()) {
154         QUnifiedTimer::resumeAnimationTimer(this);
155         QUnifiedTimer::stopAnimationTimer(this);
156         // invalidate the start reference time
157         lastTick = 0;
158         lastDelta = 0;
159     }
160 }
161
162 void QQmlAnimationTimer::registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel)
163 {
164     if (animation->userControlDisabled())
165         return;
166
167     QQmlAnimationTimer *inst = instance(true); //we create the instance if needed
168     inst->registerRunningAnimation(animation);
169     if (isTopLevel) {
170         Q_ASSERT(!animation->m_hasRegisteredTimer);
171         animation->m_hasRegisteredTimer = true;
172         inst->animationsToStart << animation;
173         if (!inst->startAnimationPending) {
174             inst->startAnimationPending = true;
175             QMetaObject::invokeMethod(inst, "startAnimations", Qt::QueuedConnection);
176         }
177     }
178 }
179
180 void QQmlAnimationTimer::unregisterAnimation(QAbstractAnimationJob *animation)
181 {
182     QQmlAnimationTimer *inst = QQmlAnimationTimer::instance(false);
183     if (inst) {
184         //at this point the unified timer should have been created
185         //but it might also have been already destroyed in case the application is shutting down
186
187         inst->unregisterRunningAnimation(animation);
188
189         if (!animation->m_hasRegisteredTimer)
190             return;
191
192         int idx = inst->animations.indexOf(animation);
193         if (idx != -1) {
194             inst->animations.removeAt(idx);
195             // this is needed if we unregister an animation while its running
196             if (idx <= inst->currentAnimationIdx)
197                 --inst->currentAnimationIdx;
198
199             if (inst->animations.isEmpty() && !inst->stopTimerPending) {
200                 inst->stopTimerPending = true;
201                 QMetaObject::invokeMethod(inst, "stopTimer", Qt::QueuedConnection);
202             }
203         } else {
204             inst->animationsToStart.removeOne(animation);
205         }
206     }
207     animation->m_hasRegisteredTimer = false;
208 }
209
210 void QQmlAnimationTimer::registerRunningAnimation(QAbstractAnimationJob *animation)
211 {
212     Q_ASSERT(!animation->userControlDisabled());
213
214     if (animation->m_isGroup)
215         return;
216
217     if (animation->m_isPause) {
218         runningPauseAnimations << animation;
219     } else
220         runningLeafAnimations++;
221 }
222
223 void QQmlAnimationTimer::unregisterRunningAnimation(QAbstractAnimationJob *animation)
224 {
225     if (animation->userControlDisabled())
226         return;
227
228     if (animation->m_isGroup)
229         return;
230
231     if (animation->m_isPause)
232         runningPauseAnimations.removeOne(animation);
233     else
234         runningLeafAnimations--;
235     Q_ASSERT(runningLeafAnimations >= 0);
236 }
237
238 int QQmlAnimationTimer::closestPauseAnimationTimeToFinish()
239 {
240     int closestTimeToFinish = INT_MAX;
241     for (int i = 0; i < runningPauseAnimations.size(); ++i) {
242         QAbstractAnimationJob *animation = runningPauseAnimations.at(i);
243         int timeToFinish;
244
245         if (animation->direction() == QAbstractAnimationJob::Forward)
246             timeToFinish = animation->duration() - animation->currentLoopTime();
247         else
248             timeToFinish = animation->currentLoopTime();
249
250         if (timeToFinish < closestTimeToFinish)
251             closestTimeToFinish = timeToFinish;
252     }
253     return closestTimeToFinish;
254 }
255
256 /////////////////////////////////////////////////////////////////////////////////////////////////////////
257
258 QAbstractAnimationJob::QAbstractAnimationJob()
259     : m_loopCount(1)
260     , m_group(0)
261     , m_direction(QAbstractAnimationJob::Forward)
262     , m_state(QAbstractAnimationJob::Stopped)
263     , m_totalCurrentTime(0)
264     , m_currentTime(0)
265     , m_currentLoop(0)
266     , m_uncontrolledFinishTime(-1)
267     , m_nextSibling(0)
268     , m_previousSibling(0)
269     , m_wasDeleted(0)
270     , m_hasRegisteredTimer(false)
271     , m_isPause(false)
272     , m_isGroup(false)
273     , m_disableUserControl(false)
274     , m_hasCurrentTimeChangeListeners(false)
275
276 {
277 }
278
279 QAbstractAnimationJob::~QAbstractAnimationJob()
280 {
281     if (m_wasDeleted)
282         *m_wasDeleted = true;
283
284     //we can't call stop here. Otherwise we get pure virtual calls
285     if (m_state != Stopped) {
286         State oldState = m_state;
287         m_state = Stopped;
288         stateChanged(oldState, m_state);
289
290         Q_ASSERT(m_state == Stopped);
291         if (oldState == Running)
292             QQmlAnimationTimer::unregisterAnimation(this);
293         Q_ASSERT(!m_hasRegisteredTimer);
294     }
295
296     if (m_group)
297         m_group->removeAnimation(this);
298 }
299
300 void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState)
301 {
302     if (m_state == newState)
303         return;
304
305     if (m_loopCount == 0)
306         return;
307
308     State oldState = m_state;
309     int oldCurrentTime = m_currentTime;
310     int oldCurrentLoop = m_currentLoop;
311     Direction oldDirection = m_direction;
312
313     // check if we should Rewind
314     if ((newState == Paused || newState == Running) && oldState == Stopped) {
315         //here we reset the time if needed
316         //we don't call setCurrentTime because this might change the way the animation
317         //behaves: changing the state or changing the current value
318         m_totalCurrentTime = m_currentTime = (m_direction == Forward) ?
319             0 : (m_loopCount == -1 ? duration() : totalDuration());
320     }
321
322     m_state = newState;
323     //(un)registration of the animation must always happen before calls to
324     //virtual function (updateState) to ensure a correct state of the timer
325     bool isTopLevel = !m_group || m_group->isStopped();
326     if (oldState == Running) {
327         if (newState == Paused && m_hasRegisteredTimer)
328             QQmlAnimationTimer::ensureTimerUpdate();
329         //the animation, is not running any more
330         QQmlAnimationTimer::unregisterAnimation(this);
331     } else if (newState == Running) {
332         QQmlAnimationTimer::registerAnimation(this, isTopLevel);
333     }
334
335     //starting an animation qualifies as a top level loop change
336     if (newState == Running && oldState == Stopped && !m_group)
337         topLevelAnimationLoopChanged();
338
339     RETURN_IF_DELETED(updateState(newState, oldState));
340
341     if (newState != m_state) //this is to be safe if updateState changes the state
342         return;
343
344     // Notify state change
345     RETURN_IF_DELETED(stateChanged(newState, oldState));
346     if (newState != m_state) //this is to be safe if updateState changes the state
347         return;
348
349     switch (m_state) {
350     case Paused:
351         break;
352     case Running:
353         {
354             // this ensures that the value is updated now that the animation is running
355             if (oldState == Stopped) {
356                 if (isTopLevel) {
357                     // currentTime needs to be updated if pauseTimer is active
358                     RETURN_IF_DELETED(QQmlAnimationTimer::ensureTimerUpdate());
359                     RETURN_IF_DELETED(setCurrentTime(m_totalCurrentTime));
360                 }
361             }
362         }
363         break;
364     case Stopped:
365         // Leave running state.
366         int dura = duration();
367
368         if (dura == -1 || m_loopCount < 0
369             || (oldDirection == Forward && (oldCurrentTime * (oldCurrentLoop + 1)) == (dura * m_loopCount))
370             || (oldDirection == Backward && oldCurrentTime == 0)) {
371                finished();
372         }
373         break;
374     }
375 }
376
377 void QAbstractAnimationJob::setDirection(Direction direction)
378 {
379     if (m_direction == direction)
380         return;
381
382     if (m_state == Stopped) {
383         if (m_direction == Backward) {
384             m_currentTime = duration();
385             m_currentLoop = m_loopCount - 1;
386         } else {
387             m_currentTime = 0;
388             m_currentLoop = 0;
389         }
390     }
391
392     // the commands order below is important: first we need to setCurrentTime with the old direction,
393     // then update the direction on this and all children and finally restart the pauseTimer if needed
394     if (m_hasRegisteredTimer)
395         QQmlAnimationTimer::ensureTimerUpdate();
396
397     m_direction = direction;
398     updateDirection(direction);
399
400     if (m_hasRegisteredTimer)
401         // needed to update the timer interval in case of a pause animation
402         QQmlAnimationTimer::updateAnimationTimer();
403 }
404
405 void QAbstractAnimationJob::setLoopCount(int loopCount)
406 {
407     m_loopCount = loopCount;
408 }
409
410 int QAbstractAnimationJob::totalDuration() const
411 {
412     int dura = duration();
413     if (dura <= 0)
414         return dura;
415     int loopcount = loopCount();
416     if (loopcount < 0)
417         return -1;
418     return dura * loopcount;
419 }
420
421 void QAbstractAnimationJob::setCurrentTime(int msecs)
422 {
423     msecs = qMax(msecs, 0);
424     // Calculate new time and loop.
425     int dura = duration();
426     int totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount);
427     if (totalDura != -1)
428         msecs = qMin(totalDura, msecs);
429     m_totalCurrentTime = msecs;
430
431     // Update new values.
432     int oldLoop = m_currentLoop;
433     m_currentLoop = ((dura <= 0) ? 0 : (msecs / dura));
434     if (m_currentLoop == m_loopCount) {
435         //we're at the end
436         m_currentTime = qMax(0, dura);
437         m_currentLoop = qMax(0, m_loopCount - 1);
438     } else {
439         if (m_direction == Forward) {
440             m_currentTime = (dura <= 0) ? msecs : (msecs % dura);
441         } else {
442             m_currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1;
443             if (m_currentTime == dura)
444                 --m_currentLoop;
445         }
446     }
447
448     if (m_currentLoop != oldLoop && !m_group)   //### verify Running as well?
449         topLevelAnimationLoopChanged();
450
451     RETURN_IF_DELETED(updateCurrentTime(m_currentTime));
452
453     if (m_currentLoop != oldLoop)
454         currentLoopChanged();
455
456     // All animations are responsible for stopping the animation when their
457     // own end state is reached; in this case the animation is time driven,
458     // and has reached the end.
459     if ((m_direction == Forward && m_totalCurrentTime == totalDura)
460         || (m_direction == Backward && m_totalCurrentTime == 0)) {
461         stop();
462     }
463
464     if (m_hasCurrentTimeChangeListeners)
465         currentTimeChanged(m_currentTime);
466 }
467
468 void QAbstractAnimationJob::start()
469 {
470     if (m_state == Running)
471         return;
472     setState(Running);
473 }
474
475 void QAbstractAnimationJob::stop()
476 {
477     if (m_state == Stopped)
478         return;
479     setState(Stopped);
480 }
481
482 void QAbstractAnimationJob::pause()
483 {
484     if (m_state == Stopped) {
485         qWarning("QAbstractAnimationJob::pause: Cannot pause a stopped animation");
486         return;
487     }
488
489     setState(Paused);
490 }
491
492 void QAbstractAnimationJob::resume()
493 {
494     if (m_state != Paused) {
495         qWarning("QAbstractAnimationJob::resume: "
496                  "Cannot resume an animation that is not paused");
497         return;
498     }
499     setState(Running);
500 }
501
502 void QAbstractAnimationJob::setEnableUserControl()
503 {
504     m_disableUserControl = false;
505 }
506
507 bool QAbstractAnimationJob::userControlDisabled() const
508 {
509     return m_disableUserControl;
510 }
511
512 void QAbstractAnimationJob::setDisableUserControl()
513 {
514     m_disableUserControl = true;
515     start();
516     pause();
517 }
518
519 void QAbstractAnimationJob::updateState(QAbstractAnimationJob::State newState,
520                                      QAbstractAnimationJob::State oldState)
521 {
522     Q_UNUSED(oldState);
523     Q_UNUSED(newState);
524 }
525
526 void QAbstractAnimationJob::updateDirection(QAbstractAnimationJob::Direction direction)
527 {
528     Q_UNUSED(direction);
529 }
530
531 void QAbstractAnimationJob::finished()
532 {
533     //TODO: update this code so it is valid to delete the animation in animationFinished
534     for (int i = 0; i < changeListeners.count(); ++i) {
535         const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
536         if (change.types & QAbstractAnimationJob::Completion) {
537             RETURN_IF_DELETED(change.listener->animationFinished(this));
538         }
539     }
540
541     if (m_group && (duration() == -1 || loopCount() < 0)) {
542         //this is an uncontrolled animation, need to notify the group animation we are finished
543         m_group->uncontrolledAnimationFinished(this);
544     }
545 }
546
547 void QAbstractAnimationJob::stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
548 {
549     for (int i = 0; i < changeListeners.count(); ++i) {
550         const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
551         if (change.types & QAbstractAnimationJob::StateChange) {
552             RETURN_IF_DELETED(change.listener->animationStateChanged(this, newState, oldState));
553         }
554     }
555 }
556
557 void QAbstractAnimationJob::currentLoopChanged()
558 {
559     for (int i = 0; i < changeListeners.count(); ++i) {
560         const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
561         if (change.types & QAbstractAnimationJob::CurrentLoop) {
562            RETURN_IF_DELETED(change.listener->animationCurrentLoopChanged(this));
563         }
564     }
565 }
566
567 void QAbstractAnimationJob::currentTimeChanged(int currentTime)
568 {
569     Q_ASSERT(m_hasCurrentTimeChangeListeners);
570
571     for (int i = 0; i < changeListeners.count(); ++i) {
572         const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
573         if (change.types & QAbstractAnimationJob::CurrentTime) {
574            RETURN_IF_DELETED(change.listener->animationCurrentTimeChanged(this, currentTime));
575         }
576     }
577 }
578
579 void QAbstractAnimationJob::addAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
580 {
581     if (changes & QAbstractAnimationJob::CurrentTime)
582         m_hasCurrentTimeChangeListeners = true;
583
584     changeListeners.append(ChangeListener(listener, changes));
585 }
586
587 void QAbstractAnimationJob::removeAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
588 {
589     m_hasCurrentTimeChangeListeners = false;
590
591     changeListeners.removeOne(ChangeListener(listener, changes));
592
593     for (int i = 0; i < changeListeners.count(); ++i) {
594         const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
595         if (change.types & QAbstractAnimationJob::CurrentTime) {
596             m_hasCurrentTimeChangeListeners = true;
597             break;
598         }
599     }
600 }
601
602 QT_END_NAMESPACE
603
604 //#include "moc_qabstractanimation2_p.cpp"