Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "qquickkeyframe_p.h"
++
++ #include "qquicktimeline_p.h"
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/QVariantAnimation>
++ #include <QtCore/qmath.h>
++ #include <QtGui/qpainter.h>
++ #include <QtQml/QQmlProperty>
++ #include <QtQml/QQmlFile>
++ #include <QtQml/QQmlContext>
++ #include <QtQml/QQmlEngine>
++ #include <QtCore/QFile>
++ #include <QtCore/QCborStreamReader>
++ #include <QCborArray>
++ #include <QRect>
++ #include <QVector2D>
++ #include <QVector3D>
++ #include <QVector4D>
++ #include <QQuaternion>
++ #include <QColor>
++
++ #include <private/qvariantanimation_p.h>
++ #include <private/qqmlproperty_p.h>
++
++ #include <algorithm>
++
++ QT_BEGIN_NAMESPACE
++
++ class QQuickKeyframeGroupPrivate : public QObjectPrivate
++ {
++ Q_DECLARE_PUBLIC(QQuickKeyframeGroup)
++ public:
++ QQuickKeyframeGroupPrivate() = default;
++
++ QObject *target = nullptr;
++ QString propertyName;
++ QUrl keyframeSource;
++ QByteArray keyframeData;
++ bool componentComplete = false;
++ int userType = -1;
++
++ protected:
++ void setupKeyframes();
++ bool loadKeyframes(bool fromBinary = false);
++ void resetKeyframes();
++
++ static void append_keyframe(QQmlListProperty<QQuickKeyframe> *list, QQuickKeyframe *a);
++ static qsizetype keyframe_count(QQmlListProperty<QQuickKeyframe> *list);
++ static QQuickKeyframe* keyframe_at(QQmlListProperty<QQuickKeyframe> *list, qsizetype pos);
++ static void clear_keyframes(QQmlListProperty<QQuickKeyframe> *list);
++
++ QList<QQuickKeyframe *> keyframes;
++ QList<QQuickKeyframe *> sortedKeyframes;
++
++ QVariant originalValue;
++ QVariant lastValue;
++ QQmlAnyBinding originalBinding;
++ };
++
++ void QQuickKeyframeGroupPrivate::setupKeyframes()
++ {
++ sortedKeyframes = keyframes;
++ std::sort(sortedKeyframes.begin(), sortedKeyframes.end(), [](const QQuickKeyframe *first, const QQuickKeyframe *second) {
++ return first->frame() < second->frame();
++ });
++ }
++
++ // a valid frame : time, easingType, data
++ // returns total size(data + 2) if valid, -1 otherwise
++ static int validFrameSize(QMetaType::Type type)
++ {
++ switch (type) {
++ case QMetaType::Bool:
++ case QMetaType::Int:
++ case QMetaType::Float:
++ case QMetaType::Double:
++ return 3;
++ case QMetaType::QVector2D:
++ case QMetaType::QPoint:
++ case QMetaType::QPointF:
++ case QMetaType::QSize:
++ case QMetaType::QSizeF:
++ return 4;
++ case QMetaType::QVector3D:
++ return 5;
++ case QMetaType::QVector4D:
++ case QMetaType::QQuaternion:
++ case QMetaType::QColor:
++ case QMetaType::QRect:
++ case QMetaType::QRectF:
++ return 6;
++ default:
++ qWarning() << "Keyframe property type not handled:" << type;
++ return -1;
++ }
++ }
++
++ // Read property 'type' value from CborArray from index 'id' and return it as QVariant.
++ static QVariant qQuickKeyframeReadProperty(const QCborArray &array, qsizetype id, QMetaType::Type type)
++ {
++ switch (type) {
++ case QMetaType::QVector2D:
++ {
++ QVector2D v;
++ v.setX(array.at(id).toDouble());
++ v.setY(array.at(id + 1).toDouble());
++ return QVariant(v);
++ }
++ break;
++ case QMetaType::QVector3D:
++ {
++ QVector3D v;
++ v.setX(array.at(id).toDouble());
++ v.setY(array.at(id + 1).toDouble());
++ v.setZ(array.at(id + 2).toDouble());
++ return QVariant(v);
++ }
++ break;
++ case QMetaType::QVector4D:
++ {
++ QVector4D v;
++ v.setX(array.at(id).toDouble());
++ v.setY(array.at(id + 1).toDouble());
++ v.setZ(array.at(id + 2).toDouble());
++ v.setW(array.at(id + 3).toDouble());
++ return QVariant(v);
++ }
++ break;
++ case QMetaType::QQuaternion:
++ {
++ QQuaternion q;
++ q.setScalar(array.at(id).toDouble());
++ q.setX(array.at(id + 1).toDouble());
++ q.setY(array.at(id + 2).toDouble());
++ q.setZ(array.at(id + 3).toDouble());
++ return QVariant(q);
++ }
++ break;
++ case QMetaType::QColor:
++ {
++ QColor c;
++ c.setRed(array.at(id).toInteger());
++ c.setGreen(array.at(id + 1).toInteger());
++ c.setBlue(array.at(id + 2).toInteger());
++ c.setAlpha(array.at(id + 3).toInteger());
++ return QVariant(c);
++ }
++ break;
++ case QMetaType::QRectF:
++ {
++ QRectF r;
++ r.setX(array.at(id).toDouble());
++ r.setY(array.at(id + 1).toDouble());
++ r.setWidth(array.at(id + 2).toDouble());
++ r.setHeight(array.at(id + 3).toDouble());
++ return QVariant(r);
++ }
++ break;
++ case QMetaType::QRect:
++ {
++ QRect r;
++ r.setX(array.at(id).toInteger());
++ r.setY(array.at(id + 1).toInteger());
++ r.setWidth(array.at(id + 2).toInteger());
++ r.setHeight(array.at(id + 3).toInteger());
++ return QVariant(r);
++ }
++ break;
++ case QMetaType::QPointF:
++ {
++ QPointF p;
++ p.setX(array.at(id).toDouble());
++ p.setY(array.at(id + 1).toDouble());
++ return QVariant(p);
++ }
++ break;
++ case QMetaType::QPoint:
++ {
++ QPoint p;
++ p.setX(array.at(id).toInteger());
++ p.setY(array.at(id + 1).toInteger());
++ return QVariant(p);
++ }
++ break;
++ case QMetaType::QSizeF:
++ {
++ QSizeF s;
++ s.setWidth(array.at(id).toDouble());
++ s.setHeight(array.at(id + 1).toDouble());
++ return QVariant(s);
++ }
++ break;
++ case QMetaType::QSize:
++ {
++ QSize s;
++ s.setWidth(array.at(id).toInteger());
++ s.setHeight(array.at(id + 1).toInteger());
++ return QVariant(s);
++ }
++ break;
++ case QMetaType::Bool:
++ case QMetaType::Int:
++ case QMetaType::Float:
++ case QMetaType::Double:
++ {
++ return array.at(id).toVariant();
++ }
++
++ default:
++ qWarning() << "Keyframe property type not handled:" << type;
++ }
++
++ return QVariant();
++ }
++
++ bool QQuickKeyframeGroupPrivate::loadKeyframes(bool fromBinary)
++ {
++ Q_Q(QQuickKeyframeGroup);
++
++ QCborStreamReader reader;
++ QFile dataFile;
++ if (!fromBinary) {
++ // Resolve URL similar to QQuickImage source
++ QUrl loadUrl = keyframeSource;
++ QQmlContext *context = qmlContext(q);
++ if (context)
++ loadUrl = context->resolvedUrl(keyframeSource);
++ QString dataFilePath = QQmlFile::urlToLocalFileOrQrc(loadUrl);
++
++ dataFile.setFileName(dataFilePath);
++ if (!dataFile.open(QIODevice::ReadOnly)) {
++ // Invalid file
++ qWarning() << "Unable to open keyframeSource:" << dataFilePath;
++ return false;
++ }
++ reader.setDevice(&dataFile);
++ } else {
++ reader.addData(keyframeData);
++ }
++
++ auto error = [&reader](const QString &msg = QString()) {
++ if (!msg.isEmpty())
++ qWarning() << "Corrupt keyframeSource" << msg;
++ else
++ qWarning() << "Corrupt keyframeSource" << reader.lastError().toString();
++ return false;
++ };
++
++ QCborValue kfSrcCborValue = QCborValue::fromCbor(reader);
++ if (reader.lastError() != QCborError::NoError || !kfSrcCborValue.isArray())
++ return error(QStringLiteral("invalid format.(array expected)"));
++
++ QCborArray kfSrcCborArray = kfSrcCborValue.toArray();
++ // [ "QTimelineKeyframes", version(int), property type(int), [...]]
++ if (kfSrcCborArray.size() != 4)
++ return error(QStringLiteral("invalid data size"));
++
++ if (kfSrcCborArray.at(0).toString() != QStringLiteral("QTimelineKeyframes"))
++ return error(QStringLiteral("invalid keyframeSource header string"));
++
++ if (kfSrcCborArray.at(1).toInteger() != 1)
++ return error(QStringLiteral("invalid keyframeSource version %1").arg(kfSrcCborArray.at(1).toInteger()));
++
++ // QMetaType::UnknownType = 0;
++ QMetaType::Type propertyType = static_cast<QMetaType::Type>(kfSrcCborArray.at(2).toInteger(0));
++ const int frameSize = validFrameSize(propertyType);
++ if (frameSize < 0)
++ return error(QStringLiteral("unsupported property type"));
++
++ // Start keyframes array
++ QCborArray kfArray = kfSrcCborArray.at(3).toArray();
++ const auto arraySize = kfArray.size();
++ if (arraySize % frameSize != 0)
++ return error(QStringLiteral("invalid keyframe array"));
++
++ bool validKeyframeData = true;
++ for (qsizetype i = 0; i < arraySize; i += frameSize) {
++ // An array element should be ( time, easingType, keyframeData )
++ auto keyframe = std::make_unique<QQuickKeyframe>(q);
++
++ keyframe->setFrame(kfArray.at(i).toDouble());
++
++ QCborValue easingTypeValue = kfArray.at(i + 1);
++ if (!easingTypeValue.isInteger()) {
++ validKeyframeData = false;
++ break;
++ }
++ keyframe->setEasing(static_cast<QEasingCurve::Type>(easingTypeValue.toInteger()));
++
++ QVariant value = qQuickKeyframeReadProperty(kfArray, i + 2, propertyType);
++ if (value.isValid()) {
++ keyframe->setValue(value);
++ keyframes.append(keyframe.release());
++ } else {
++ validKeyframeData = false;
++ break;
++ }
++ }
++
++ if (!validKeyframeData) {
++ qWarning() << "Invalid keyframe data";
++ resetKeyframes();
++ return false;
++ }
++
++ return true;
++ }
++
++ void QQuickKeyframeGroupPrivate::resetKeyframes()
++ {
++ qDeleteAll(keyframes);
++ keyframes.clear();
++ }
++
++ void QQuickKeyframeGroupPrivate::append_keyframe(QQmlListProperty<QQuickKeyframe> *list, QQuickKeyframe *a)
++ {
++ auto q = static_cast<QQuickKeyframeGroup *>(list->object);
++ q->d_func()->keyframes.append(a);
++ q->d_func()->setupKeyframes();
++ q->reset();
++ }
++
++ qsizetype QQuickKeyframeGroupPrivate::keyframe_count(QQmlListProperty<QQuickKeyframe> *list)
++ {
++ auto q = static_cast<QQuickKeyframeGroup *>(list->object);
++ return q->d_func()->keyframes.size();
++ }
++
++ QQuickKeyframe* QQuickKeyframeGroupPrivate::keyframe_at(QQmlListProperty<QQuickKeyframe> *list, qsizetype pos)
++ {
++ auto q = static_cast<QQuickKeyframeGroup *>(list->object);
++ return q->d_func()->keyframes.at(pos);
++ }
++
++ void QQuickKeyframeGroupPrivate::clear_keyframes(QQmlListProperty<QQuickKeyframe> *list)
++ {
++ auto q = static_cast<QQuickKeyframeGroup *>(list->object);
++ q->d_func()->keyframes.clear();
++ q->d_func()->setupKeyframes();
++ }
++
++ class QQuickKeyframePrivate : public QObjectPrivate
++ {
++ Q_DECLARE_PUBLIC(QQuickKeyframe)
++ public:
++ QQuickKeyframePrivate() = default;
++
++ qreal frame = 0;
++ QEasingCurve easingCurve;
++ QVariant value;
++ };
++
++ /*!
++ \qmltype Keyframe
++ \inherits QtObject
++ \nativetype QQuickKeyframe
++ \inqmlmodule QtQuick.Timeline
++ \ingroup qtqmltypes
++
++ \brief A keyframe.
++
++ The value of a keyframe on a timeline.
++
++ An easing curve can be attached to the keyframe.
++ */
++
++ /*!
++ \qmlproperty double Keyframe::frame
++
++ The position of the keyframe on the timeline.
++ */
++
++ /*!
++ \qmlproperty var Keyframe::easing
++
++ The easing curve attached to the keyframe.
++ */
++
++ /*!
++ \qmlproperty var Keyframe::value
++
++ The value of the keyframe.
++ */
++
++ /*!
++ \qmlsignal Keyframe::easingCurveChanged
++
++ This signal is emitted when the easing curve attached to the keyframe
++ changes.
++ */
++
++ QQuickKeyframe::QQuickKeyframe(QObject *parent)
++ : QObject(*(new QQuickKeyframePrivate), parent)
++ {
++ }
++
++ qreal QQuickKeyframe::frame() const
++ {
++ Q_D(const QQuickKeyframe);
++ return d->frame;
++ }
++
++ void QQuickKeyframe::setFrame(qreal f)
++ {
++ Q_D(QQuickKeyframe);
++ if (d->frame == f)
++ return;
++ d->frame = f;
++
++ reset();
++
++ emit frameChanged();
++ }
++
++ void QQuickKeyframe::reset()
++ {
++ auto keyframes = qobject_cast<QQuickKeyframeGroup*>(parent());
++ if (keyframes)
++ keyframes->reset();
++ }
++
++ QQuickKeyframe::QQuickKeyframe(QQuickKeyframePrivate &dd, QObject *parent)
++ : QObject(dd, parent)
++ {
++ }
++
++ /*!
++ \qmltype KeyframeGroup
++ \inherits QtObject
++ \nativetype QQuickKeyframeGroup
++ \inqmlmodule QtQuick.Timeline
++ \ingroup qtqmltypes
++
++ \brief A keyframe group.
++
++ A keyframe group contains all keyframes for a specific property of an item
++ and always belongs to a timeline.
++ */
++
++ /*!
++ \qmlproperty var KeyframeGroup::target
++
++ The item that is targeted by the keyframe group.
++ */
++
++ /*!
++ \qmlproperty string KeyframeGroup::property
++
++ The property that is targeted by the keyframe group.
++ */
++
++ /*!
++ \qmlproperty list KeyframeGroup::keyframes
++ \readonly
++
++ A list of keyframes that belong to the keyframe group.
++ */
++
++ /*!
++ \qmlproperty url KeyframeGroup::keyframeSource
++
++ The URL to a file containing binary keyframe data.
++
++ \note KeyframeGroup should either set this property or contain
++ Keyframe child elements. Using both can lead to an undefined behavior.
++
++ \since 1.1
++ */
++
++ QQuickKeyframeGroup::QQuickKeyframeGroup(QObject *parent)
++ : QObject(*(new QQuickKeyframeGroupPrivate), parent)
++ {
++
++ }
++
++ QQmlListProperty<QQuickKeyframe> QQuickKeyframeGroup::keyframes()
++ {
++ Q_D(QQuickKeyframeGroup);
++
++ return { this, &d->keyframes, QQuickKeyframeGroupPrivate::append_keyframe,
++ QQuickKeyframeGroupPrivate::keyframe_count,
++ QQuickKeyframeGroupPrivate::keyframe_at,
++ QQuickKeyframeGroupPrivate::clear_keyframes };
++ }
++
++ QObject *QQuickKeyframeGroup::target() const
++ {
++ Q_D(const QQuickKeyframeGroup);
++ return d->target;
++ }
++
++ void QQuickKeyframeGroup::setTargetObject(QObject *o)
++ {
++ Q_D(QQuickKeyframeGroup);
++ if (d->target == o)
++ return;
++ d->target = o;
++
++ if (!property().isEmpty())
++ init();
++
++ emit targetChanged();
++ }
++
++ QString QQuickKeyframeGroup::property() const
++ {
++ Q_D(const QQuickKeyframeGroup);
++ return d->propertyName;
++ }
++
++ void QQuickKeyframeGroup::setProperty(const QString &n)
++ {
++ Q_D(QQuickKeyframeGroup);
++ if (d->propertyName == n)
++ return;
++ d->propertyName = n;
++
++ if (target())
++ init();
++
++ emit propertyChanged();
++ }
++
++ QUrl QQuickKeyframeGroup::keyframeSource() const
++ {
++ Q_D(const QQuickKeyframeGroup);
++ return d->keyframeSource;
++ }
++
++ void QQuickKeyframeGroup::setKeyframeSource(const QUrl &source)
++ {
++ Q_D(QQuickKeyframeGroup);
++ if (d->keyframeSource == source)
++ return;
++
++ if (d->keyframes.size() > 0) {
++ // Remove possible previously loaded keyframes
++ d->resetKeyframes();
++ d->keyframeData.clear();
++ }
++
++ d->keyframeSource = source;
++ if (d->loadKeyframes())
++ d->setupKeyframes();
++ reset();
++
++ emit keyframeSourceChanged();
++ }
++
++ const QByteArray QQuickKeyframeGroup::keyframeData() const
++ {
++ Q_D(const QQuickKeyframeGroup);
++ return d->keyframeData;
++ }
++
++ void QQuickKeyframeGroup::setKeyframeData(const QByteArray &data)
++ {
++ Q_D(QQuickKeyframeGroup);
++ if (d->keyframeData == data)
++ return;
++
++ if (d->keyframes.size() > 0) {
++ // Remove possible previously loaded keyframes
++ d->resetKeyframes();
++ d->keyframeSource.clear();
++ }
++
++ d->keyframeData = data;
++ if (d->loadKeyframes(true))
++ d->setupKeyframes();
++ reset();
++
++ emit keyframeSourceChanged();
++ }
++
++ QVariant QQuickKeyframeGroup::evaluate(qreal frame) const
++ {
++ Q_D(const QQuickKeyframeGroup);
++
++ if (d->sortedKeyframes.isEmpty())
++ return QVariant();
++
++ static QQuickKeyframe dummy;
++ auto timeline = qobject_cast<QQuickTimeline*>(parent());
++ if (timeline)
++ dummy.setFrame(timeline->startFrame() - 0.0001);
++ dummy.setValue(d->originalValue);
++
++ QQuickKeyframe *lastFrame = &dummy;
++
++ for (auto keyFrame : std::as_const(d->sortedKeyframes)) {
++ if (qFuzzyCompare(frame, keyFrame->frame()) || frame < keyFrame->frame())
++ return keyFrame->evaluate(lastFrame, frame, d->userType);
++ lastFrame = keyFrame;
++ }
++
++ return lastFrame->value();
++ }
++
++ void QQuickKeyframeGroup::setProperty(qreal frame)
++ {
++ Q_D(QQuickKeyframeGroup);
++
++ if (target()) {
++ QQmlProperty qmlProperty(target(), property());
++
++ d->lastValue = evaluate(frame);
++
++ if (!qmlProperty.write(d->lastValue))
++ qWarning() << "Cannot set property" << property();
++ }
++ }
++
++ void QQuickKeyframeGroup::init()
++ {
++ Q_D(QQuickKeyframeGroup);
++ if (target()) {
++ QQmlProperty qmlProperty(target(), property());
++ d->originalValue = QQmlProperty::read(target(), property());
++ d->userType = qmlProperty.property().userType();
++ if (d->originalBinding)
++ d->originalBinding = nullptr;
++ d->originalBinding = QQmlAnyBinding::ofProperty(qmlProperty);
++ if (property().contains(QLatin1Char('.'))) {
++ if (d->userType == QMetaType::QVector2D
++ || d->userType == QMetaType::QVector3D
++ || d->userType == QMetaType::QVector4D
++ || d->userType == QMetaType::QQuaternion)
++ d->userType = QMetaType::Double;
++ }
++ }
++ }
++
++ void QQuickKeyframeGroup::resetDefaultValue()
++ {
++ Q_D(QQuickKeyframeGroup);
++
++ if (QQmlProperty::read(target(), property()) == d->lastValue) {
++ if (d->originalBinding) {
++ QQmlProperty qmlProperty(target(), property());
++ d->originalBinding.installOn(qmlProperty);
++ d->originalBinding = nullptr;
++ } else {
++ QQmlProperty::write(target(), property(), d->originalValue);
++ }
++ }
++ }
++
++ void QQuickKeyframeGroup::reset()
++ {
++ Q_D(QQuickKeyframeGroup);
++ if (!d->componentComplete)
++ return;
++
++ auto *timeline = qobject_cast<QQuickTimeline*>(parent());
++ if (timeline)
++ timeline->reevaluate();
++ }
++
++ void QQuickKeyframeGroup::setupKeyframes()
++ {
++ Q_D(QQuickKeyframeGroup);
++
++ if (d->componentComplete)
++ d->setupKeyframes();
++ }
++
++ void QQuickKeyframeGroup::classBegin()
++ {
++ Q_D(QQuickKeyframeGroup);
++ d->componentComplete = false;
++ }
++
++ void QQuickKeyframeGroup::componentComplete()
++ {
++ Q_D(QQuickKeyframeGroup);
++ d->componentComplete = true;
++ setupKeyframes();
++ }
++
++ QEasingCurve QQuickKeyframe::easing() const
++ {
++ Q_D(const QQuickKeyframe);
++ return d->easingCurve;
++ }
++
++ void QQuickKeyframe::setEasing(const QEasingCurve &e)
++ {
++ Q_D(QQuickKeyframe);
++ if (d->easingCurve == e)
++ return;
++
++ d->easingCurve = e;
++
++ reset();
++
++ emit easingCurveChanged();
++ }
++
++ QVariant QQuickKeyframe::value() const
++ {
++ Q_D(const QQuickKeyframe);
++ return d->value;
++ }
++
++ void QQuickKeyframe::setValue(const QVariant &v)
++ {
++ Q_D(QQuickKeyframe);
++ if (d->value == v)
++ return;
++ d->value = v;
++
++ reset();
++
++ emit valueChanged();
++ }
++
++ QVariant QQuickKeyframe::evaluate(QQuickKeyframe *pre, qreal frametime, int userType) const
++ {
++ QVariantAnimation::Interpolator interpolator = QVariantAnimationPrivate::getInterpolator(userType);
++ if (!pre)
++ return value();
++
++ QVariant preValue = pre->value();
++ qreal preFrame = pre->frame();
++
++ qreal duration = frame() - preFrame;
++ qreal offset = frametime - preFrame;
++
++ qreal progress = easing().valueForProgress(offset / duration);
++
++ const QMetaType targetType(userType);
++ preValue.convert(targetType);
++ QVariant convertedValue = value();
++ convertedValue.convert(targetType);
++
++ if (!interpolator) {
++ if (progress < 1.0)
++ return preValue;
++
++ return convertedValue;
++ }
++
++ if (preValue.isValid() && convertedValue.isValid())
++ return interpolator(preValue.constData(), convertedValue.constData(), progress);
++
++ qWarning() << "invalid keyframe target" << preValue << convertedValue << userType;
++
++ return QVariant();
++ }
++
++ QT_END_NAMESPACE
++
++