Re-add documentation for AnchorAnimation and ParentAnimation.
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickanimation.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickanimation_p.h"
43 #include "qquickanimation_p_p.h"
44 #include "qquickstateoperations_p.h"
45
46 #include <private/qdeclarativeproperty_p.h>
47 #include <private/qdeclarativepath_p.h>
48
49 #include <QtDeclarative/qdeclarativeinfo.h>
50 #include <QtCore/qmath.h>
51 #include <QtCore/qsequentialanimationgroup.h>
52 #include <QtCore/qparallelanimationgroup.h>
53 #include <QtGui/qtransform.h>
54
55 QT_BEGIN_NAMESPACE
56
57 /*!
58     \qmlclass ParentAnimation QQuickParentAnimation
59     \inqmlmodule QtQuick 2
60     \ingroup qml-animation-transition
61     \since QtQuick 2.0
62     \inherits Animation
63     \brief The ParentAnimation element animates changes in parent values.
64
65     ParentAnimation is used to animate a parent change for an \l Item.
66
67     For example, the following ParentChange changes \c blueRect to become
68     a child of \c redRect when it is clicked. The inclusion of the
69     ParentAnimation, which defines a NumberAnimation to be applied during
70     the transition, ensures the item animates smoothly as it moves to
71     its new parent:
72
73     \snippet doc/src/snippets/declarative/parentanimation.qml 0
74
75     A ParentAnimation can contain any number of animations. These animations will
76     be run in parallel; to run them sequentially, define them within a
77     SequentialAnimation.
78
79     In some cases, such as when reparenting between items with clipping enabled, it is useful
80     to animate the parent change via another item that does not have clipping
81     enabled. Such an item can be set using the \l via property.
82
83     For convenience, when a ParentAnimation is used in a \l Transition, it will
84     animate any ParentChange that has occurred during the state change.
85     This can be overridden by setting a specific target item using the
86     \l target property.
87
88     Like any other animation element, a ParentAnimation can be applied in a
89     number of ways, including transitions, behaviors and property value
90     sources. The \l {QML Animation and Transitions} documentation shows a
91     variety of methods for creating animations.
92
93     \sa {QML Animation and Transitions}, {declarative/animation/basics}{Animation basics example}
94 */
95 QQuickParentAnimation::QQuickParentAnimation(QObject *parent)
96     : QDeclarativeAnimationGroup(*(new QQuickParentAnimationPrivate), parent)
97 {
98     Q_D(QQuickParentAnimation);
99     d->topLevelGroup = new QSequentialAnimationGroup;
100     QDeclarative_setParent_noEvent(d->topLevelGroup, this);
101
102     d->startAction = new QActionAnimation;
103     QDeclarative_setParent_noEvent(d->startAction, d->topLevelGroup);
104     d->topLevelGroup->addAnimation(d->startAction);
105
106     d->ag = new QParallelAnimationGroup;
107     QDeclarative_setParent_noEvent(d->ag, d->topLevelGroup);
108     d->topLevelGroup->addAnimation(d->ag);
109
110     d->endAction = new QActionAnimation;
111     QDeclarative_setParent_noEvent(d->endAction, d->topLevelGroup);
112     d->topLevelGroup->addAnimation(d->endAction);
113 }
114
115 QQuickParentAnimation::~QQuickParentAnimation()
116 {
117 }
118
119 /*!
120     \qmlproperty Item QtQuick2::ParentAnimation::target
121     The item to reparent.
122
123     When used in a transition, if no target is specified, all
124     ParentChange occurrences are animated by the ParentAnimation.
125 */
126 QQuickItem *QQuickParentAnimation::target() const
127 {
128     Q_D(const QQuickParentAnimation);
129     return d->target;
130 }
131
132 void QQuickParentAnimation::setTarget(QQuickItem *target)
133 {
134     Q_D(QQuickParentAnimation);
135     if (target == d->target)
136         return;
137
138     d->target = target;
139     emit targetChanged();
140 }
141
142 /*!
143     \qmlproperty Item QtQuick2::ParentAnimation::newParent
144     The new parent to animate to.
145
146     If the ParentAnimation is defined within a \l Transition or \l Behavior,
147     this value defaults to the value defined in the end state of the
148     \l Transition, or the value of the property change that triggered the
149     \l Behavior.
150 */
151 QQuickItem *QQuickParentAnimation::newParent() const
152 {
153     Q_D(const QQuickParentAnimation);
154     return d->newParent;
155 }
156
157 void QQuickParentAnimation::setNewParent(QQuickItem *newParent)
158 {
159     Q_D(QQuickParentAnimation);
160     if (newParent == d->newParent)
161         return;
162
163     d->newParent = newParent;
164     emit newParentChanged();
165 }
166
167 /*!
168     \qmlproperty Item QtQuick2::ParentAnimation::via
169     The item to reparent via. This provides a way to do an unclipped animation
170     when both the old parent and new parent are clipped.
171
172     \qml
173     ParentAnimation {
174         target: myItem
175         via: topLevelItem
176         // ...
177     }
178     \endqml
179 */
180 QQuickItem *QQuickParentAnimation::via() const
181 {
182     Q_D(const QQuickParentAnimation);
183     return d->via;
184 }
185
186 void QQuickParentAnimation::setVia(QQuickItem *via)
187 {
188     Q_D(QQuickParentAnimation);
189     if (via == d->via)
190         return;
191
192     d->via = via;
193     emit viaChanged();
194 }
195
196 //### mirrors same-named function in QQuickItem
197 QPointF QQuickParentAnimationPrivate::computeTransformOrigin(QQuickItem::TransformOrigin origin, qreal width, qreal height) const
198 {
199     switch (origin) {
200     default:
201     case QQuickItem::TopLeft:
202         return QPointF(0, 0);
203     case QQuickItem::Top:
204         return QPointF(width / 2., 0);
205     case QQuickItem::TopRight:
206         return QPointF(width, 0);
207     case QQuickItem::Left:
208         return QPointF(0, height / 2.);
209     case QQuickItem::Center:
210         return QPointF(width / 2., height / 2.);
211     case QQuickItem::Right:
212         return QPointF(width, height / 2.);
213     case QQuickItem::BottomLeft:
214         return QPointF(0, height);
215     case QQuickItem::Bottom:
216         return QPointF(width / 2., height);
217     case QQuickItem::BottomRight:
218         return QPointF(width, height);
219     }
220 }
221
222 void QQuickParentAnimation::transition(QDeclarativeStateActions &actions,
223                         QDeclarativeProperties &modified,
224                         TransitionDirection direction)
225 {
226     Q_D(QQuickParentAnimation);
227
228     struct QQuickParentAnimationData : public QAbstractAnimationAction
229     {
230         QQuickParentAnimationData() {}
231         ~QQuickParentAnimationData() { qDeleteAll(pc); }
232
233         QDeclarativeStateActions actions;
234         //### reverse should probably apply on a per-action basis
235         bool reverse;
236         QList<QQuickParentChange *> pc;
237         virtual void doAction()
238         {
239             for (int ii = 0; ii < actions.count(); ++ii) {
240                 const QDeclarativeAction &action = actions.at(ii);
241                 if (reverse)
242                     action.event->reverse();
243                 else
244                     action.event->execute();
245             }
246         }
247     };
248
249     QQuickParentAnimationData *data = new QQuickParentAnimationData;
250     QQuickParentAnimationData *viaData = new QQuickParentAnimationData;
251
252     bool hasExplicit = false;
253     if (d->target && d->newParent) {
254         data->reverse = false;
255         QDeclarativeAction myAction;
256         QQuickParentChange *pc = new QQuickParentChange;
257         pc->setObject(d->target);
258         pc->setParent(d->newParent);
259         myAction.event = pc;
260         data->pc << pc;
261         data->actions << myAction;
262         hasExplicit = true;
263         if (d->via) {
264             viaData->reverse = false;
265             QDeclarativeAction myVAction;
266             QQuickParentChange *vpc = new QQuickParentChange;
267             vpc->setObject(d->target);
268             vpc->setParent(d->via);
269             myVAction.event = vpc;
270             viaData->pc << vpc;
271             viaData->actions << myVAction;
272         }
273         //### once actions have concept of modified,
274         //    loop to match appropriate ParentChanges and mark as modified
275     }
276
277     if (!hasExplicit)
278     for (int i = 0; i < actions.size(); ++i) {
279         QDeclarativeAction &action = actions[i];
280         if (action.event && action.event->typeName() == QLatin1String("ParentChange")
281             && (!d->target || static_cast<QQuickParentChange*>(action.event)->object() == d->target)) {
282
283             QQuickParentChange *pc = static_cast<QQuickParentChange*>(action.event);
284             QDeclarativeAction myAction = action;
285             data->reverse = action.reverseEvent;
286
287             //### this logic differs from PropertyAnimation
288             //    (probably a result of modified vs. done)
289             if (d->newParent) {
290                 QQuickParentChange *epc = new QQuickParentChange;
291                 epc->setObject(static_cast<QQuickParentChange*>(action.event)->object());
292                 epc->setParent(d->newParent);
293                 myAction.event = epc;
294                 data->pc << epc;
295                 data->actions << myAction;
296                 pc = epc;
297             } else {
298                 action.actionDone = true;
299                 data->actions << myAction;
300             }
301
302             if (d->via) {
303                 viaData->reverse = false;
304                 QDeclarativeAction myAction;
305                 QQuickParentChange *vpc = new QQuickParentChange;
306                 vpc->setObject(pc->object());
307                 vpc->setParent(d->via);
308                 myAction.event = vpc;
309                 viaData->pc << vpc;
310                 viaData->actions << myAction;
311                 QDeclarativeAction dummyAction;
312                 QDeclarativeAction &xAction = pc->xIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
313                 QDeclarativeAction &yAction = pc->yIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
314                 QDeclarativeAction &sAction = pc->scaleIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
315                 QDeclarativeAction &rAction = pc->rotationIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
316                 QQuickItem *target = pc->object();
317                 QQuickItem *targetParent = action.reverseEvent ? pc->originalParent() : pc->parent();
318
319                 //### this mirrors the logic in QQuickParentChange.
320                 bool ok;
321                 const QTransform &transform = targetParent->itemTransform(d->via, &ok);
322                 if (transform.type() >= QTransform::TxShear || !ok) {
323                     qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under complex transform");
324                     ok = false;
325                 }
326
327                 qreal scale = 1;
328                 qreal rotation = 0;
329                 bool isRotate = (transform.type() == QTransform::TxRotate) || (transform.m11() < 0);
330                 if (ok && !isRotate) {
331                     if (transform.m11() == transform.m22())
332                         scale = transform.m11();
333                     else {
334                         qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under non-uniform scale");
335                         ok = false;
336                     }
337                 } else if (ok && isRotate) {
338                     if (transform.m11() == transform.m22())
339                         scale = qSqrt(transform.m11()*transform.m11() + transform.m12()*transform.m12());
340                     else {
341                         qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under non-uniform scale");
342                         ok = false;
343                     }
344
345                     if (scale != 0)
346                         rotation = atan2(transform.m12()/scale, transform.m11()/scale) * 180/M_PI;
347                     else {
348                         qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under scale of 0");
349                         ok = false;
350                     }
351                 }
352
353                 const QPointF &point = transform.map(QPointF(xAction.toValue.toReal(),yAction.toValue.toReal()));
354                 qreal x = point.x();
355                 qreal y = point.y();
356                 if (ok && target->transformOrigin() != QQuickItem::TopLeft) {
357                     qreal w = target->width();
358                     qreal h = target->height();
359                     if (pc->widthIsSet() && i < actions.size() - 1)
360                         w = actions[++i].toValue.toReal();
361                     if (pc->heightIsSet() && i < actions.size() - 1)
362                         h = actions[++i].toValue.toReal();
363                     const QPointF &transformOrigin
364                             = d->computeTransformOrigin(target->transformOrigin(), w,h);
365                     qreal tempxt = transformOrigin.x();
366                     qreal tempyt = transformOrigin.y();
367                     QTransform t;
368                     t.translate(-tempxt, -tempyt);
369                     t.rotate(rotation);
370                     t.scale(scale, scale);
371                     t.translate(tempxt, tempyt);
372                     const QPointF &offset = t.map(QPointF(0,0));
373                     x += offset.x();
374                     y += offset.y();
375                 }
376
377                 if (ok) {
378                     //qDebug() << x << y << rotation << scale;
379                     xAction.toValue = x;
380                     yAction.toValue = y;
381                     sAction.toValue = sAction.toValue.toReal() * scale;
382                     rAction.toValue = rAction.toValue.toReal() + rotation;
383                 }
384             }
385         }
386     }
387
388     if (data->actions.count()) {
389         if (direction == QDeclarativeAbstractAnimation::Forward) {
390             d->startAction->setAnimAction(d->via ? viaData : data, QActionAnimation::DeleteWhenStopped);
391             d->endAction->setAnimAction(d->via ? data : 0, QActionAnimation::DeleteWhenStopped);
392         } else {
393             d->endAction->setAnimAction(d->via ? viaData : data, QActionAnimation::DeleteWhenStopped);
394             d->startAction->setAnimAction(d->via ? data : 0, QActionAnimation::DeleteWhenStopped);
395         }
396     } else {
397         delete data;
398         delete viaData;
399     }
400
401     //take care of any child animations
402     bool valid = d->defaultProperty.isValid();
403     for (int ii = 0; ii < d->animations.count(); ++ii) {
404         if (valid)
405             d->animations.at(ii)->setDefaultTarget(d->defaultProperty);
406         d->animations.at(ii)->transition(actions, modified, direction);
407     }
408
409 }
410
411 QAbstractAnimation *QQuickParentAnimation::qtAnimation()
412 {
413     Q_D(QQuickParentAnimation);
414     return d->topLevelGroup;
415 }
416
417 /*!
418     \qmlclass AnchorAnimation QQuickAnchorAnimation
419     \inqmlmodule QtQuick 2
420     \ingroup qml-animation-transition
421     \inherits Animation
422     \brief The AnchorAnimation element animates changes in anchor values.
423
424     AnchorAnimation is used to animate an anchor change.
425
426     In the following snippet we animate the addition of a right anchor to a \l Rectangle:
427
428     \snippet doc/src/snippets/declarative/anchoranimation.qml 0
429
430     For convenience, when an AnchorAnimation is used in a \l Transition, it will
431     animate any AnchorChanges that have occurred during the state change.
432     This can be overridden by setting a specific target item using the
433     \l target property.
434
435     Like any other animation element, an AnchorAnimation can be applied in a
436     number of ways, including transitions, behaviors and property value
437     sources. The \l {QML Animation and Transitions} documentation shows a
438     variety of methods for creating animations.
439
440     \sa {QML Animation and Transitions}, AnchorChanges
441 */
442 QQuickAnchorAnimation::QQuickAnchorAnimation(QObject *parent)
443 : QDeclarativeAbstractAnimation(*(new QQuickAnchorAnimationPrivate), parent)
444 {
445     Q_D(QQuickAnchorAnimation);
446     d->va = new QDeclarativeBulkValueAnimator;
447     QDeclarative_setParent_noEvent(d->va, this);
448 }
449
450 QQuickAnchorAnimation::~QQuickAnchorAnimation()
451 {
452 }
453
454 QAbstractAnimation *QQuickAnchorAnimation::qtAnimation()
455 {
456     Q_D(QQuickAnchorAnimation);
457     return d->va;
458 }
459
460 /*!
461     \qmlproperty list<Item> QtQuick2::AnchorAnimation::targets
462     The items to reanchor.
463
464     If no targets are specified all AnchorChanges will be
465     animated by the AnchorAnimation.
466 */
467 QDeclarativeListProperty<QQuickItem> QQuickAnchorAnimation::targets()
468 {
469     Q_D(QQuickAnchorAnimation);
470     return QDeclarativeListProperty<QQuickItem>(this, d->targets);
471 }
472
473 /*!
474     \qmlproperty int QtQuick2::AnchorAnimation::duration
475     This property holds the duration of the animation, in milliseconds.
476
477     The default value is 250.
478 */
479 int QQuickAnchorAnimation::duration() const
480 {
481     Q_D(const QQuickAnchorAnimation);
482     return d->va->duration();
483 }
484
485 void QQuickAnchorAnimation::setDuration(int duration)
486 {
487     if (duration < 0) {
488         qmlInfo(this) << tr("Cannot set a duration of < 0");
489         return;
490     }
491
492     Q_D(QQuickAnchorAnimation);
493     if (d->va->duration() == duration)
494         return;
495     d->va->setDuration(duration);
496     emit durationChanged(duration);
497 }
498
499 /*!
500     \qmlproperty enumeration QtQuick2::AnchorAnimation::easing.type
501     \qmlproperty real QtQuick2::AnchorAnimation::easing.amplitude
502     \qmlproperty real QtQuick2::AnchorAnimation::easing.overshoot
503     \qmlproperty real QtQuick2::AnchorAnimation::easing.period
504     \brief the easing curve used for the animation.
505
506     To specify an easing curve you need to specify at least the type. For some curves you can also specify
507     amplitude, period and/or overshoot. The default easing curve is
508     Linear.
509
510     \qml
511     AnchorAnimation { easing.type: Easing.InOutQuad }
512     \endqml
513
514     See the \l{PropertyAnimation::easing.type} documentation for information
515     about the different types of easing curves.
516 */
517 QEasingCurve QQuickAnchorAnimation::easing() const
518 {
519     Q_D(const QQuickAnchorAnimation);
520     return d->va->easingCurve();
521 }
522
523 void QQuickAnchorAnimation::setEasing(const QEasingCurve &e)
524 {
525     Q_D(QQuickAnchorAnimation);
526     if (d->va->easingCurve() == e)
527         return;
528
529     d->va->setEasingCurve(e);
530     emit easingChanged(e);
531 }
532
533 void QQuickAnchorAnimation::transition(QDeclarativeStateActions &actions,
534                         QDeclarativeProperties &modified,
535                         TransitionDirection direction)
536 {
537     Q_UNUSED(modified);
538     Q_D(QQuickAnchorAnimation);
539     QDeclarativeAnimationPropertyUpdater *data = new QDeclarativeAnimationPropertyUpdater;
540     data->interpolatorType = QMetaType::QReal;
541     data->interpolator = d->interpolator;
542
543     data->reverse = direction == Backward ? true : false;
544     data->fromSourced = false;
545     data->fromDefined = false;
546
547     for (int ii = 0; ii < actions.count(); ++ii) {
548         QDeclarativeAction &action = actions[ii];
549         if (action.event && action.event->typeName() == QLatin1String("AnchorChanges")
550             && (d->targets.isEmpty() || d->targets.contains(static_cast<QQuickAnchorChanges*>(action.event)->object()))) {
551             data->actions << static_cast<QQuickAnchorChanges*>(action.event)->additionalActions();
552         }
553     }
554
555     if (data->actions.count()) {
556         if (!d->rangeIsSet) {
557             d->va->setStartValue(qreal(0));
558             d->va->setEndValue(qreal(1));
559             d->rangeIsSet = true;
560         }
561         d->va->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped);
562         d->va->setFromSourcedValue(&data->fromSourced);
563     } else {
564         delete data;
565     }
566 }
567
568 QQuickPathAnimation::QQuickPathAnimation(QObject *parent)
569 : QDeclarativeAbstractAnimation(*(new QQuickPathAnimationPrivate), parent)
570 {
571     Q_D(QQuickPathAnimation);
572     d->pa = new QDeclarativeBulkValueAnimator;
573     QDeclarative_setParent_noEvent(d->pa, this);
574 }
575
576 QQuickPathAnimation::~QQuickPathAnimation()
577 {
578 }
579
580 int QQuickPathAnimation::duration() const
581 {
582     Q_D(const QQuickPathAnimation);
583     return d->pa->duration();
584 }
585
586 void QQuickPathAnimation::setDuration(int duration)
587 {
588     if (duration < 0) {
589         qmlInfo(this) << tr("Cannot set a duration of < 0");
590         return;
591     }
592
593     Q_D(QQuickPathAnimation);
594     if (d->pa->duration() == duration)
595         return;
596     d->pa->setDuration(duration);
597     emit durationChanged(duration);
598 }
599
600 QEasingCurve QQuickPathAnimation::easing() const
601 {
602     Q_D(const QQuickPathAnimation);
603     return d->pa->easingCurve();
604 }
605
606 void QQuickPathAnimation::setEasing(const QEasingCurve &e)
607 {
608     Q_D(QQuickPathAnimation);
609     if (d->pa->easingCurve() == e)
610         return;
611
612     d->pa->setEasingCurve(e);
613     emit easingChanged(e);
614 }
615
616 QDeclarativePath *QQuickPathAnimation::path() const
617 {
618     Q_D(const QQuickPathAnimation);
619     return d->path;
620 }
621
622 void QQuickPathAnimation::setPath(QDeclarativePath *path)
623 {
624     Q_D(QQuickPathAnimation);
625     if (d->path == path)
626         return;
627
628     d->path = path;
629     emit pathChanged();
630 }
631
632 QQuickItem *QQuickPathAnimation::target() const
633 {
634     Q_D(const QQuickPathAnimation);
635     return d->target;
636 }
637
638 void QQuickPathAnimation::setTarget(QQuickItem *target)
639 {
640     Q_D(QQuickPathAnimation);
641     if (d->target == target)
642         return;
643
644     d->target = target;
645     emit targetChanged();
646 }
647
648 QQuickPathAnimation::Orientation QQuickPathAnimation::orientation() const
649 {
650     Q_D(const QQuickPathAnimation);
651     return d->orientation;
652 }
653
654 void QQuickPathAnimation::setOrientation(Orientation orientation)
655 {
656     Q_D(QQuickPathAnimation);
657     if (d->orientation == orientation)
658         return;
659
660     d->orientation = orientation;
661     emit orientationChanged(d->orientation);
662 }
663
664 QPointF QQuickPathAnimation::anchorPoint() const
665 {
666     Q_D(const QQuickPathAnimation);
667     return d->anchorPoint;
668 }
669
670 void QQuickPathAnimation::setAnchorPoint(const QPointF &point)
671 {
672     Q_D(QQuickPathAnimation);
673     if (d->anchorPoint == point)
674         return;
675
676     d->anchorPoint = point;
677     emit anchorPointChanged(point);
678 }
679
680 qreal QQuickPathAnimation::orientationEntryInterval() const
681 {
682     Q_D(const QQuickPathAnimation);
683     return d->entryInterval;
684 }
685
686 void QQuickPathAnimation::setOrientationEntryInterval(qreal interval)
687 {
688     Q_D(QQuickPathAnimation);
689     if (d->entryInterval == interval)
690         return;
691     d->entryInterval = interval;
692     emit orientationEntryIntervalChanged(interval);
693 }
694
695 qreal QQuickPathAnimation::orientationExitInterval() const
696 {
697     Q_D(const QQuickPathAnimation);
698     return d->exitInterval;
699 }
700
701 void QQuickPathAnimation::setOrientationExitInterval(qreal interval)
702 {
703     Q_D(QQuickPathAnimation);
704     if (d->exitInterval == interval)
705         return;
706     d->exitInterval = interval;
707     emit orientationExitIntervalChanged(interval);
708 }
709
710 qreal QQuickPathAnimation::endRotation() const
711 {
712     Q_D(const QQuickPathAnimation);
713     return d->endRotation.isNull ? qreal(0) : d->endRotation.value;
714 }
715
716 void QQuickPathAnimation::setEndRotation(qreal rotation)
717 {
718     Q_D(QQuickPathAnimation);
719     if (!d->endRotation.isNull && d->endRotation == rotation)
720         return;
721
722     d->endRotation = rotation;
723     emit endRotationChanged(d->endRotation);
724 }
725
726
727 QAbstractAnimation *QQuickPathAnimation::qtAnimation()
728 {
729     Q_D(QQuickPathAnimation);
730     return d->pa;
731 }
732
733 void QQuickPathAnimation::transition(QDeclarativeStateActions &actions,
734                                            QDeclarativeProperties &modified,
735                                            TransitionDirection direction)
736 {
737     Q_D(QQuickPathAnimation);
738     QQuickPathAnimationUpdater *data = new QQuickPathAnimationUpdater;
739
740     data->orientation = d->orientation;
741     data->anchorPoint = d->anchorPoint;
742     data->entryInterval = d->entryInterval;
743     data->exitInterval = d->exitInterval;
744     data->endRotation = d->endRotation;
745     data->reverse = direction == Backward ? true : false;
746     data->fromSourced = false;
747     data->fromDefined = (d->path && d->path->hasStartX() && d->path->hasStartY()) ? true : false;
748     data->toDefined = d->path ? d->path->hasEnd() : false;
749     int origModifiedSize = modified.count();
750
751     for (int i = 0; i < actions.count(); ++i) {
752         QDeclarativeAction &action = actions[i];
753         if (action.event)
754             continue;
755         if (action.specifiedObject == d->target && action.property.name() == QLatin1String("x")) {
756             data->toX = action.toValue.toReal();
757             modified << action.property;
758             action.fromValue = action.toValue;
759         }
760         if (action.specifiedObject == d->target && action.property.name() == QLatin1String("y")) {
761             data->toY = action.toValue.toReal();
762             modified << action.property;
763             action.fromValue = action.toValue;
764         }
765     }
766
767     if (d->target && d->path &&
768         (modified.count() > origModifiedSize || data->toDefined)) {
769         data->target = d->target;
770         data->path = d->path;
771         data->path->invalidateSequentialHistory();
772         if (!d->rangeIsSet) {
773             d->pa->setStartValue(qreal(0));
774             d->pa->setEndValue(qreal(1));
775             d->rangeIsSet = true;
776         }
777         /*
778             NOTE: The following block relies on the fact that the previous value hasn't
779             yet been deleted, and has the same target, etc, which may be a bit fragile.
780          */
781         if (d->pa->getAnimValue()) {
782             QQuickPathAnimationUpdater *prevData = static_cast<QQuickPathAnimationUpdater*>(d->pa->getAnimValue());
783
784             // get the original start angle that was used (so we can exactly reverse).
785             data->startRotation = prevData->startRotation;
786
787             // treat interruptions specially, otherwise we end up with strange paths
788             if ((data->reverse || prevData->reverse) && prevData->currentV > 0 && prevData->currentV < 1) {
789                 if (!data->fromDefined && !data->toDefined && !prevData->painterPath.isEmpty()) {
790                     QPointF pathPos = QDeclarativePath::sequentialPointAt(prevData->painterPath, prevData->pathLength, prevData->attributePoints, prevData->prevBez, prevData->currentV);
791                     if (!prevData->anchorPoint.isNull())
792                         pathPos -= prevData->anchorPoint;
793                     if (pathPos == data->target->pos()) {   //only treat as interruption if we interrupted ourself
794                         data->painterPath = prevData->painterPath;
795                         data->toDefined = data->fromDefined = data->fromSourced = true;
796                         data->prevBez.isValid = false;
797                         data->interruptStart = prevData->currentV;
798                         data->startRotation = prevData->startRotation;
799                         data->pathLength = prevData->pathLength;
800                         data->attributePoints = prevData->attributePoints;
801                     }
802                 }
803             }
804         }
805         d->pa->setFromSourcedValue(&data->fromSourced);
806         d->pa->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped);
807     } else {
808         d->pa->setFromSourcedValue(0);
809         d->pa->setAnimValue(0, QAbstractAnimation::DeleteWhenStopped);
810         delete data;
811     }
812 }
813
814 void QQuickPathAnimationUpdater::setValue(qreal v)
815 {
816     if (interruptStart.isValid()) {
817         if (reverse)
818             v = 1 - v;
819         qreal end = reverse ? 0.0 : 1.0;
820         v = interruptStart + v * (end-interruptStart);
821     }
822     currentV = v;
823     bool atStart = ((reverse && v == 1.0) || (!reverse && v == 0.0));
824     if (!fromSourced && (!fromDefined || !toDefined)) {
825         qreal startX = reverse ? toX + anchorPoint.x() : target->x() + anchorPoint.x();
826         qreal startY = reverse ? toY + anchorPoint.y() : target->y() + anchorPoint.y();
827         qreal endX = reverse ? target->x() + anchorPoint.x() : toX + anchorPoint.x();
828         qreal endY = reverse ? target->y() + anchorPoint.y() : toY + anchorPoint.y();
829
830         prevBez.isValid = false;
831         painterPath = path->createPath(QPointF(startX, startY), QPointF(endX, endY), QStringList(), pathLength, attributePoints);
832         fromSourced = true;
833     }
834
835     qreal angle;
836     bool fixed = orientation == QQuickPathAnimation::Fixed;
837     QPointF currentPos = !painterPath.isEmpty() ? path->sequentialPointAt(painterPath, pathLength, attributePoints, prevBez, v, fixed ? 0 : &angle) : path->sequentialPointAt(v, fixed ? 0 : &angle);
838
839     //adjust position according to anchor point
840     if (!anchorPoint.isNull()) {
841         currentPos -= anchorPoint;
842         if (atStart) {
843             if (!anchorPoint.isNull() && !fixed)
844                 target->setTransformOriginPoint(anchorPoint);
845         }
846     }
847
848     //### could cache properties rather than reconstructing each time
849     QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, QStringLiteral("x")), currentPos.x(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
850     QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, QStringLiteral("y")), currentPos.y(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
851
852     //adjust angle according to orientation
853     if (!fixed) {
854         switch (orientation) {
855             case QQuickPathAnimation::RightFirst:
856                 angle = -angle;
857                 break;
858             case QQuickPathAnimation::TopFirst:
859                 angle = -angle + 90;
860                 break;
861             case QQuickPathAnimation::LeftFirst:
862                 angle = -angle + 180;
863                 break;
864             case QQuickPathAnimation::BottomFirst:
865                 angle = -angle + 270;
866                 break;
867             default:
868                 angle = 0;
869                 break;
870         }
871
872         if (atStart && !reverse) {
873             startRotation = target->rotation();
874
875             //shortest distance to correct orientation
876             qreal diff = angle - startRotation;
877             while (diff > 180.0) {
878                 startRotation.value += 360.0;
879                 diff -= 360.0;
880             }
881             while (diff < -180.0) {
882                 startRotation.value -= 360.0;
883                 diff += 360.0;
884             }
885         }
886
887         //smoothly transition to the desired orientation
888         if (startRotation.isValid()) {
889             if (reverse && v == 0.0)
890                 angle = startRotation;
891             else if (v < entryInterval)
892                 angle = angle * v / entryInterval + startRotation * (entryInterval - v) / entryInterval;
893         }
894         if (endRotation.isValid()) {
895             qreal exitStart = 1 - exitInterval;
896             if (!reverse && v == 1.0)
897                 angle = endRotation;
898             else if (v > exitStart)
899                 angle = endRotation * (v - exitStart) / exitInterval + angle * (exitInterval - (v - exitStart)) / exitInterval;
900         }
901         QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, QStringLiteral("rotation")), angle, QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
902     }
903
904     /*
905         NOTE: we don't always reset the transform origin, as it can cause a
906         visual jump if ending on an angle. This means that in some cases
907         (anchor point and orientation both specified, and ending at an angle)
908         the transform origin will always be set after running the path animation.
909      */
910     if ((reverse && v == 0.0) || (!reverse && v == 1.0)) {
911         if (!anchorPoint.isNull() && !fixed && qFuzzyIsNull(angle))
912             target->setTransformOriginPoint(QPointF());
913     }
914 }
915
916 QT_END_NAMESPACE