src/declarative/graphicsitems/qdeclarativepath.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 01:19:32 +0300
changeset 37 758a864f9613
parent 33 3e2da88830cd
permissions -rw-r--r--
Revision: 201037 Kit: 201039

/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtDeclarative module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "private/qdeclarativepath_p.h"
#include "private/qdeclarativepath_p_p.h"

#include <QSet>
#include <QTime>

#include <private/qbezier_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmlclass PathElement QDeclarativePathElement
    \ingroup qml-view-elements
    \since 4.7
    \brief PathElement is the base path type.

    This type is the base for all path types.  It cannot
    be instantiated.

    \sa Path, PathAttribute, PathPercent, PathLine, PathQuad, PathCubic
*/

/*!
    \qmlclass Path QDeclarativePath
    \ingroup qml-view-elements
    \since 4.7
    \brief A Path object defines a path for use by \l PathView.

    A Path is composed of one or more path segments - PathLine, PathQuad,
    PathCubic.

    The spacing of the items along the Path can be adjusted via a
    PathPercent object.

    PathAttribute allows named attributes with values to be defined
    along the path.

    \sa PathView, PathAttribute, PathPercent, PathLine, PathQuad, PathCubic
*/
QDeclarativePath::QDeclarativePath(QObject *parent)
 : QObject(*(new QDeclarativePathPrivate), parent)
{
}

QDeclarativePath::~QDeclarativePath()
{
}

/*!
    \qmlproperty real Path::startX
    \qmlproperty real Path::startY
    These properties hold the starting position of the path.
*/
qreal QDeclarativePath::startX() const
{
    Q_D(const QDeclarativePath);
    return d->startX;
}

void QDeclarativePath::setStartX(qreal x)
{
    Q_D(QDeclarativePath);
    if (qFuzzyCompare(x, d->startX))
        return;
    d->startX = x;
    emit startXChanged();
    processPath();
}

qreal QDeclarativePath::startY() const
{
    Q_D(const QDeclarativePath);
    return d->startY;
}

void QDeclarativePath::setStartY(qreal y)
{
    Q_D(QDeclarativePath);
    if (qFuzzyCompare(y, d->startY))
        return;
    d->startY = y;
    emit startYChanged();
    processPath();
}

/*!
    \qmlproperty bool Path::closed
    This property holds whether the start and end of the path are identical.
*/
bool QDeclarativePath::isClosed() const
{
    Q_D(const QDeclarativePath);
    return d->closed;
}

/*!
    \qmlproperty list<PathElement> Path::pathElements
    This property holds the objects composing the path.

    \default

    A path can contain the following path objects:
    \list
        \i \l PathLine - a straight line to a given position.
        \i \l PathQuad - a quadratic Bezier curve to a given position with a control point.
        \i \l PathCubic - a cubic Bezier curve to a given position with two control points.
        \i \l PathAttribute - an attribute at a given position in the path.
        \i \l PathPercent - a way to spread out items along various segments of the path.
    \endlist

    \snippet doc/src/snippets/declarative/pathview/pathattributes.qml 2
*/

QDeclarativeListProperty<QDeclarativePathElement> QDeclarativePath::pathElements()
{
    Q_D(QDeclarativePath);
    return QDeclarativeListProperty<QDeclarativePathElement>(this, d->_pathElements);
}

void QDeclarativePath::interpolate(int idx, const QString &name, qreal value)
{
    Q_D(QDeclarativePath);
    if (!idx)
        return;

    qreal lastValue = 0;
    qreal lastPercent = 0;
    int search = idx - 1;
    while(search >= 0) {
        const AttributePoint &point = d->_attributePoints.at(search);
        if (point.values.contains(name)) {
            lastValue = point.values.value(name);
            lastPercent = point.origpercent;
            break;
        }
        --search;
    }

    ++search;

    const AttributePoint &curPoint = d->_attributePoints.at(idx);

    for (int ii = search; ii < idx; ++ii) {
        AttributePoint &point = d->_attributePoints[ii];

        qreal val = lastValue + (value - lastValue) * (point.origpercent - lastPercent) / (curPoint.origpercent - lastPercent);
        point.values.insert(name, val);
    }
}

void QDeclarativePath::endpoint(const QString &name)
{
    Q_D(QDeclarativePath);
    const AttributePoint &first = d->_attributePoints.first();
    qreal val = first.values.value(name);
    for (int ii = d->_attributePoints.count() - 1; ii >= 0; ii--) {
        const AttributePoint &point = d->_attributePoints.at(ii);
        if (point.values.contains(name)) {
            for (int jj = ii + 1; jj < d->_attributePoints.count(); ++jj) {
                AttributePoint &setPoint = d->_attributePoints[jj];
                setPoint.values.insert(name, val);
            }
            return;
        }
    }
}

void QDeclarativePath::processPath()
{
    Q_D(QDeclarativePath);

    if (!d->componentComplete)
        return;

    d->_pointCache.clear();
    d->_attributePoints.clear();
    d->_path = QPainterPath();

    AttributePoint first;
    for (int ii = 0; ii < d->_attributes.count(); ++ii)
        first.values[d->_attributes.at(ii)] = 0;
    d->_attributePoints << first;

    d->_path.moveTo(d->startX, d->startY);

    QDeclarativeCurve *lastCurve = 0;
    foreach (QDeclarativePathElement *pathElement, d->_pathElements) {
        if (QDeclarativeCurve *curve = qobject_cast<QDeclarativeCurve *>(pathElement)) {
            curve->addToPath(d->_path);
            AttributePoint p;
            p.origpercent = d->_path.length();
            d->_attributePoints << p;
            lastCurve = curve;
        } else if (QDeclarativePathAttribute *attribute = qobject_cast<QDeclarativePathAttribute *>(pathElement)) {
            AttributePoint &point = d->_attributePoints.last();
            point.values[attribute->name()] = attribute->value();
            interpolate(d->_attributePoints.count() - 1, attribute->name(), attribute->value());
        } else if (QDeclarativePathPercent *percent = qobject_cast<QDeclarativePathPercent *>(pathElement)) {
            AttributePoint &point = d->_attributePoints.last();
            point.values[QLatin1String("_qfx_percent")] = percent->value();
            interpolate(d->_attributePoints.count() - 1, QLatin1String("_qfx_percent"), percent->value());
        }
    }

    // Fixup end points
    const AttributePoint &last = d->_attributePoints.last();
    for (int ii = 0; ii < d->_attributes.count(); ++ii) {
        if (!last.values.contains(d->_attributes.at(ii)))
            endpoint(d->_attributes.at(ii));
    }

    // Adjust percent
    qreal length = d->_path.length();
    qreal prevpercent = 0;
    qreal prevorigpercent = 0;
    for (int ii = 0; ii < d->_attributePoints.count(); ++ii) {
        const AttributePoint &point = d->_attributePoints.at(ii);
        if (point.values.contains(QLatin1String("_qfx_percent"))) { //special string for QDeclarativePathPercent
            if ( ii > 0) {
                qreal scale = (d->_attributePoints[ii].origpercent/length - prevorigpercent) /
                            (point.values.value(QLatin1String("_qfx_percent"))-prevpercent);
                d->_attributePoints[ii].scale = scale;
            }
            d->_attributePoints[ii].origpercent /= length;
            d->_attributePoints[ii].percent = point.values.value(QLatin1String("_qfx_percent"));
            prevorigpercent = d->_attributePoints[ii].origpercent;
            prevpercent = d->_attributePoints[ii].percent;
        } else {
            d->_attributePoints[ii].origpercent /= length;
            d->_attributePoints[ii].percent = d->_attributePoints[ii].origpercent;
        }
    }

    d->closed = lastCurve && d->startX == lastCurve->x() && d->startY == lastCurve->y();

    emit changed();
}

void QDeclarativePath::classBegin()
{
    Q_D(QDeclarativePath);
    d->componentComplete = false;
}

void QDeclarativePath::componentComplete()
{
    Q_D(QDeclarativePath);
    QSet<QString> attrs;
    d->componentComplete = true;

    // First gather up all the attributes
    foreach (QDeclarativePathElement *pathElement, d->_pathElements) {
        if (QDeclarativePathAttribute *attribute =
            qobject_cast<QDeclarativePathAttribute *>(pathElement))
            attrs.insert(attribute->name());
    }
    d->_attributes = attrs.toList();

    processPath();

    foreach (QDeclarativePathElement *pathElement, d->_pathElements)
        connect(pathElement, SIGNAL(changed()), this, SLOT(processPath()));
}

QPainterPath QDeclarativePath::path() const
{
    Q_D(const QDeclarativePath);
    return d->_path;
}

QStringList QDeclarativePath::attributes() const
{
    Q_D(const QDeclarativePath);
    if (!d->componentComplete) {
        QSet<QString> attrs;

        // First gather up all the attributes
        foreach (QDeclarativePathElement *pathElement, d->_pathElements) {
            if (QDeclarativePathAttribute *attribute =
                qobject_cast<QDeclarativePathAttribute *>(pathElement))
                attrs.insert(attribute->name());
        }
        return attrs.toList();
    }
    return d->_attributes;
}

static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bezLength)
{
    const int lastElement = path.elementCount() - 1;
    for (int i=*from; i <= lastElement; ++i) {
        const QPainterPath::Element &e = path.elementAt(i);

        switch (e.type) {
        case QPainterPath::MoveToElement:
            break;
        case QPainterPath::LineToElement:
        {
            QLineF line(path.elementAt(i-1), e);
            *bezLength = line.length();
            QPointF a = path.elementAt(i-1);
            QPointF delta = e - a;
            *from = i+1;
            return QBezier::fromPoints(a, a + delta / 3, a + 2 * delta / 3, e);
        }
        case QPainterPath::CurveToElement:
        {
            QBezier b = QBezier::fromPoints(path.elementAt(i-1),
                                            e,
                                            path.elementAt(i+1),
                                            path.elementAt(i+2));
            *bezLength = b.length();
            *from = i+3;
            return b;
        }
        default:
            break;
        }
    }
    *from = lastElement;
    *bezLength = 0;
    return QBezier();
}

void QDeclarativePath::createPointCache() const
{
    Q_D(const QDeclarativePath);
    qreal pathLength = d->_path.length();
    // more points means less jitter between items as they move along the
    // path, but takes longer to generate
    const int points = int(pathLength*5);
    const int lastElement = d->_path.elementCount() - 1;
    d->_pointCache.resize(points+1);

    int currElement = 0;
    qreal bezLength = 0;
    QBezier currBez = nextBezier(d->_path, &currElement, &bezLength);
    qreal currLength = bezLength;
    qreal epc = currLength / pathLength;

    for (int i = 0; i < d->_pointCache.size(); i++) {
        //find which set we are in
        qreal prevPercent = 0;
        qreal prevOrigPercent = 0;
        for (int ii = 0; ii < d->_attributePoints.count(); ++ii) {
            qreal percent = qreal(i)/points;
            const AttributePoint &point = d->_attributePoints.at(ii);
            if (percent < point.percent || ii == d->_attributePoints.count() - 1) { //### || is special case for very last item
                qreal elementPercent = (percent - prevPercent);

                qreal spc = prevOrigPercent + elementPercent * point.scale;

                while (spc > epc) {
                    if (currElement > lastElement)
                        break;
                    currBez = nextBezier(d->_path, &currElement, &bezLength);
                    if (bezLength == 0.0) {
                        currLength = pathLength;
                        epc = 1.0;
                        break;
                    }
                    currLength += bezLength;
                    epc = currLength / pathLength;
                }
                qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength;
                d->_pointCache[i] = currBez.pointAt(qBound(qreal(0), realT, qreal(1)));
                break;
            }
            prevOrigPercent = point.origpercent;
            prevPercent = point.percent;
        }
    }
}

QPointF QDeclarativePath::pointAt(qreal p) const
{
    Q_D(const QDeclarativePath);
    if (d->_pointCache.isEmpty()) {
        createPointCache();
    }
    int idx = qRound(p*d->_pointCache.size());
    if (idx >= d->_pointCache.size())
        idx = d->_pointCache.size() - 1;
    else if (idx < 0)
        idx = 0;
    return d->_pointCache.at(idx);
}

qreal QDeclarativePath::attributeAt(const QString &name, qreal percent) const
{
    Q_D(const QDeclarativePath);
    if (percent < 0 || percent > 1)
        return 0;

    for (int ii = 0; ii < d->_attributePoints.count(); ++ii) {
        const AttributePoint &point = d->_attributePoints.at(ii);

        if (point.percent == percent) {
            return point.values.value(name);
        } else if (point.percent > percent) {
            qreal lastValue =
                ii?(d->_attributePoints.at(ii - 1).values.value(name)):0;
            qreal lastPercent =
                ii?(d->_attributePoints.at(ii - 1).percent):0;
            qreal curValue = point.values.value(name);
            qreal curPercent = point.percent;

            return lastValue + (curValue - lastValue) * (percent - lastPercent) / (curPercent - lastPercent);
        }
    }

    return 0;
}

/****************************************************************************/

qreal QDeclarativeCurve::x() const
{
    return _x;
}

void QDeclarativeCurve::setX(qreal x)
{
    if (_x != x) {
        _x = x;
        emit xChanged();
        emit changed();
    }
}

qreal QDeclarativeCurve::y() const
{
    return _y;
}

void QDeclarativeCurve::setY(qreal y)
{
    if (_y != y) {
        _y = y;
        emit yChanged();
        emit changed();
    }
}

/****************************************************************************/

/*!
    \qmlclass PathAttribute QDeclarativePathAttribute
    \ingroup qml-view-elements
    \since 4.7
    \brief The PathAttribute allows setting an attribute at a given position in a Path.

    The PathAttribute object allows attibutes consisting of a name and
    a value to be specified for the endpoints of path segments.  The
    attributes are exposed to the delegate as
    \l{qdeclarativeintroduction.html#attached-properties} {Attached Properties}.
    The value of an attribute at any particular point is interpolated
    from the PathAttributes bounding the point.

    The example below shows a path with the items scaled to 30% with
    opacity 50% at the top of the path and scaled 100% with opacity
    100% at the bottom.  Note the use of the PathView.scale and
    PathView.opacity attached properties to set the scale and opacity
    of the delegate.

    \table
    \row
    \o \image declarative-pathattribute.png
    \o
    \snippet doc/src/snippets/declarative/pathview/pathattributes.qml 0
    \endtable

   \sa Path
*/

/*!
    \qmlproperty string PathAttribute::name
    the name of the attribute to change.

    This attribute will be available to the delegate as PathView.<name>

    Note that using an existing Item property name such as "opacity" as an
    attribute is allowed.  This is because path attributes add a new
    \l{qdeclarativeintroduction.html#attached-properties} {Attached Property}
    which in no way clashes with existing properties.
*/

/*!
    the name of the attribute to change.
*/

QString QDeclarativePathAttribute::name() const
{
    return _name;
}

void QDeclarativePathAttribute::setName(const QString &name)
{
    if (_name == name)
        return;
     _name = name;
    emit nameChanged();
}

/*!
   \qmlproperty string PathAttribute::value
   the new value of the attribute.
*/

/*!
    the new value of the attribute.
*/
qreal QDeclarativePathAttribute::value() const
{
    return _value;
}

void QDeclarativePathAttribute::setValue(qreal value)
{
    if (_value != value) {
        _value = value;
        emit valueChanged();
        emit changed();
    }
}

/****************************************************************************/

/*!
    \qmlclass PathLine QDeclarativePathLine
    \ingroup qml-view-elements
    \since 4.7
    \brief The PathLine defines a straight line.

    The example below creates a path consisting of a straight line from
    0,100 to 200,100:

    \qml
    Path {
        startX: 0; startY: 100
        PathLine { x: 200; y: 100 }
    }
    \endqml

    \sa Path, PathQuad, PathCubic
*/

/*!
    \qmlproperty real PathLine::x
    \qmlproperty real PathLine::y

    Defines the end point of the line.
*/

void QDeclarativePathLine::addToPath(QPainterPath &path)
{
    path.lineTo(x(), y());
}

/****************************************************************************/

/*!
    \qmlclass PathQuad QDeclarativePathQuad
    \ingroup qml-view-elements
    \since 4.7
    \brief The PathQuad defines a quadratic Bezier curve with a control point.

    The following QML produces the path shown below:
    \table
    \row
    \o \image declarative-pathquad.png
    \o
    \qml
    Path {
        startX: 0; startY: 0
        PathQuad { x: 200; y: 0; controlX: 100; controlY: 150 }
    }
    \endqml
    \endtable

    \sa Path, PathCubic, PathLine
*/

/*!
    \qmlproperty real PathQuad::x
    \qmlproperty real PathQuad::y

    Defines the end point of the curve.
*/

/*!
   \qmlproperty real PathQuad::controlX
   \qmlproperty real PathQuad::controlY

   Defines the position of the control point.
*/

/*!
    the x position of the control point.
*/
qreal QDeclarativePathQuad::controlX() const
{
    return _controlX;
}

void QDeclarativePathQuad::setControlX(qreal x)
{
    if (_controlX != x) {
        _controlX = x;
        emit controlXChanged();
        emit changed();
    }
}


/*!
    the y position of the control point.
*/
qreal QDeclarativePathQuad::controlY() const
{
    return _controlY;
}

void QDeclarativePathQuad::setControlY(qreal y)
{
    if (_controlY != y) {
        _controlY = y;
        emit controlYChanged();
        emit changed();
    }
}

void QDeclarativePathQuad::addToPath(QPainterPath &path)
{
    path.quadTo(controlX(), controlY(), x(), y());
}

/****************************************************************************/

/*!
   \qmlclass PathCubic QDeclarativePathCubic
    \ingroup qml-view-elements
    \since 4.7
   \brief The PathCubic defines a cubic Bezier curve with two control points.

    The following QML produces the path shown below:
    \table
    \row
    \o \image declarative-pathcubic.png
    \o
    \qml
    Path {
        startX: 20; startY: 0
        PathCubic {
            x: 180; y: 0
            control1X: -10; control1Y: 90
            control2X: 210; control2Y: 90
        }
    }
    \endqml
    \endtable

    \sa Path, PathQuad, PathLine
*/

/*!
    \qmlproperty real PathCubic::x
    \qmlproperty real PathCubic::y

    Defines the end point of the curve.
*/

/*!
   \qmlproperty real PathCubic::control1X
   \qmlproperty real PathCubic::control1Y

    Defines the position of the first control point.
*/
qreal QDeclarativePathCubic::control1X() const
{
    return _control1X;
}

void QDeclarativePathCubic::setControl1X(qreal x)
{
    if (_control1X != x) {
        _control1X = x;
        emit control1XChanged();
        emit changed();
    }
}

qreal QDeclarativePathCubic::control1Y() const
{
    return _control1Y;
}

void QDeclarativePathCubic::setControl1Y(qreal y)
{
    if (_control1Y != y) {
        _control1Y = y;
        emit control1YChanged();
        emit changed();
    }
}

/*!
   \qmlproperty real PathCubic::control2X
   \qmlproperty real PathCubic::control2Y

    Defines the position of the second control point.
*/
qreal QDeclarativePathCubic::control2X() const
{
    return _control2X;
}

void QDeclarativePathCubic::setControl2X(qreal x)
{
    if (_control2X != x) {
        _control2X = x;
        emit control2XChanged();
        emit changed();
    }
}

qreal QDeclarativePathCubic::control2Y() const
{
    return _control2Y;
}

void QDeclarativePathCubic::setControl2Y(qreal y)
{
    if (_control2Y != y) {
        _control2Y = y;
        emit control2YChanged();
        emit changed();
    }
}

void QDeclarativePathCubic::addToPath(QPainterPath &path)
{
    path.cubicTo(control1X(), control1Y(), control2X(), control2Y(), x(), y());
}

/****************************************************************************/

/*!
    \qmlclass PathPercent QDeclarativePathPercent
    \ingroup qml-view-elements
    \since 4.7
    \brief The PathPercent manipulates the way a path is interpreted.

    The examples below show the normal distrubution of items along a path
    compared to a distribution which places 50% of the items along the
    PathLine section of the path.
    \table
    \row
    \o \image declarative-nopercent.png
    \o
    \qml
    Path {
        startX: 20; startY: 0
        PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 }
        PathLine { x: 150; y: 80 }
        PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 }
    }
    \endqml
    \row
    \o \image declarative-percent.png
    \o
    \qml
    Path {
        startX: 20; startY: 0
        PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 }
        PathPercent { value: 0.25 }
        PathLine { x: 150; y: 80 }
        PathPercent { value: 0.75 }
        PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 }
        PathPercent { value: 1 }
    }
    \endqml
    \endtable

    \sa Path
*/

qreal QDeclarativePathPercent::value() const
{
    return _value;
}

void QDeclarativePathPercent::setValue(qreal value)
{
    if (_value != value) {
        _value = value;
        emit valueChanged();
        emit changed();
    }
}
QT_END_NAMESPACE