/****************************************************************************
**
** 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 "qdeclarativepincharea.h"

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>

#include <float.h>
#include <math.h>

QT_BEGIN_NAMESPACE

QDeclarativePinch::QDeclarativePinch()
    : m_target(0), m_minScale(1.0), m_maxScale(1.0)
    , m_minRotation(0.0), m_maxRotation(0.0)
    , m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX)
    , m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false)
{
}

QDeclarativePinchArea::QDeclarativePinchArea(QDeclarativeItem *parent)
  : QDeclarativeItem(parent), absorb(true), stealMouse(false), inPinch(false)
  , pinchRejected(false), pinch_(0)
{
    setAcceptedMouseButtons(Qt::LeftButton);
    setAcceptTouchEvents(true);
    setFiltersChildEvents(true);
}

QDeclarativePinchArea::~QDeclarativePinchArea()
{
}

bool QDeclarativePinchArea::isEnabled() const
{
    return absorb;
}

void QDeclarativePinchArea::setEnabled(bool a)
{
    if (a != absorb) {
        absorb = a;
        emit enabledChanged();
    }
}

bool QDeclarativePinchArea::event(QEvent *event)
{
    if (!absorb || !isVisible())
        return QDeclarativeItem::event(event);
    switch (event->type()) {
    case QEvent::TouchBegin:
    case QEvent::TouchUpdate: {
            QTouchEvent *touch = static_cast<QTouchEvent*>(event);
            touchPoints.clear();
            for (int i = 0; i < touch->touchPoints().count(); ++i) {
                if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased)) {
                    touchPoints << touch->touchPoints().at(i);
                }
            }
            updatePinch();
        }
        return true;
    case QEvent::TouchEnd:
        touchPoints.clear();
        updatePinch();
        break;
    default:
        return QDeclarativeItem::event(event);
    }

    return QDeclarativeItem::event(event);
}

void QDeclarativePinchArea::updatePinch()
{
    if (touchPoints.count() != 2) {
        if (inPinch) {
            stealMouse = false;
            setKeepMouseGrab(false);
            inPinch = false;
            const qreal rotationAngle = pinchStartAngle - pinchLastAngle;
            QPointF pinchCenter = mapFromScene(sceneLastCenter);
            QDeclarativePinchEvent pe(pinchCenter, pinchLastScale, pinchLastAngle, rotationAngle);
            pe.setStartCenter(pinchStartCenter);
            pe.setLastCenter(pinchCenter);
            pe.setLastAngle(pinchLastAngle);
            pe.setLastScale(pinchLastScale);
            pe.setStartPoint1(mapFromScene(sceneStartPoint1));
            pe.setStartPoint2(mapFromScene(sceneStartPoint2));
            pe.setPoint1(lastPoint1);
            pe.setPoint2(lastPoint2);
            emit pinchFinished(&pe);
            if (pinch_ && pinch_->target())
                pinch_->setActive(false);
        }
        return;
    }
    if (touchPoints.at(0).state() & Qt::TouchPointPressed
        || touchPoints.at(1).state() & Qt::TouchPointPressed) {
        sceneStartPoint1 = touchPoints.at(0).scenePos();
        sceneStartPoint2 = touchPoints.at(1).scenePos();
        inPinch = false;
        pinchRejected = false;
    } else if (!pinchRejected){
        QDeclarativeItem *grabber = scene() ? qobject_cast<QDeclarativeItem*>(scene()->mouseGrabberItem()) : 0;
        if (grabber == this || !grabber || !grabber->keepMouseGrab()) {
            const int dragThreshold = QApplication::startDragDistance();
            QPointF p1 = touchPoints.at(0).scenePos();
            QPointF p2 = touchPoints.at(1).scenePos();
            qreal dx = p1.x() - p2.x();
            qreal dy = p1.y() - p2.y();
            qreal dist = sqrt(dx*dx + dy*dy);
            QPointF sceneCenter = (p1 + p2)/2;
            qreal angle = QLineF(p1, p2).angle();
            if (angle > 180)
                angle -= 360;
            if (!inPinch) {
                if (qAbs(p1.x()-sceneStartPoint1.x()) > dragThreshold
                        || qAbs(p1.y()-sceneStartPoint1.y()) > dragThreshold
                        || qAbs(p2.x()-sceneStartPoint2.x()) > dragThreshold
                        || qAbs(p2.y()-sceneStartPoint2.y()) > dragThreshold) {
                    sceneStartCenter = sceneCenter;
                    sceneLastCenter = sceneCenter;
                    pinchStartCenter = mapFromScene(sceneCenter);
                    pinchStartDist = dist;
                    pinchStartAngle = angle;
                    pinchLastScale = 1.0;
                    pinchLastAngle = angle;
                    lastPoint1 = touchPoints.at(0).pos();
                    lastPoint2 = touchPoints.at(1).pos();
                    QDeclarativePinchEvent pe(pinchStartCenter, 1.0, angle, 0.0);
                    pe.setStartCenter(pinchStartCenter);
                    pe.setLastCenter(pinchStartCenter);
                    pe.setLastAngle(pinchLastAngle);
                    pe.setLastScale(pinchLastScale);
                    pe.setStartPoint1(mapFromScene(sceneStartPoint1));
                    pe.setStartPoint2(mapFromScene(sceneStartPoint2));
                    pe.setPoint1(lastPoint1);
                    pe.setPoint2(lastPoint2);
                    emit pinchStarted(&pe);
                    if (pe.accepted()) {
                        inPinch = true;
                        stealMouse = true;
                        grabMouse();
                        setKeepMouseGrab(true);
                        if (pinch_ && pinch_->target()) {
                            pinchStartPos = pinch()->target()->pos();
                            pinchStartScale = pinch()->target()->scale();
                            pinchStartRotation = pinch()->target()->rotation();
                            pinch_->setActive(true);
                        }
                    } else {
                        pinchRejected = true;
                    }
                }
            } else if (pinchStartDist > 0) {
                qreal scale = dist / pinchStartDist;
                qreal rotationAngle = pinchStartAngle - angle;
                if (rotationAngle > 180)
                    rotationAngle -= 360;
                QPointF pinchCenter = mapFromScene(sceneCenter);
                QDeclarativePinchEvent pe(pinchCenter, scale, angle, rotationAngle);
                pe.setStartCenter(pinchStartCenter);
                pe.setLastCenter(mapFromScene(sceneLastCenter));
                pe.setLastAngle(pinchLastAngle);
                pe.setLastScale(pinchLastScale);
                pe.setStartPoint1(mapFromScene(sceneStartPoint1));
                pe.setStartPoint2(mapFromScene(sceneStartPoint2));
                pe.setPoint1(touchPoints.at(0).pos());
                pe.setPoint2(touchPoints.at(1).pos());
                pinchLastScale = scale;
                sceneLastCenter = sceneCenter;
                pinchLastAngle = angle;
                lastPoint1 = touchPoints.at(0).pos();
                lastPoint2 = touchPoints.at(1).pos();
                emit pinchChanged(&pe);
                if (pinch_ && pinch_->target()) {
                    qreal s = pinchStartScale * scale;
                    s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale());
                    pinch()->target()->setScale(s);
                    QPointF pos = sceneCenter - sceneStartCenter + pinchStartPos;
                    if (pinch()->axis() & QDeclarativePinch::XAxis) {
                        qreal x = pos.x();
                        if (x < pinch()->xmin())
                            x = pinch()->xmin();
                        else if (x > pinch()->xmax())
                            x = pinch()->xmax();
                        pinch()->target()->setX(x);
                    }
                    if (pinch()->axis() & QDeclarativePinch::YAxis) {
                        qreal y = pos.y();
                        if (y < pinch()->ymin())
                            y = pinch()->ymin();
                        else if (y > pinch()->ymax())
                            y = pinch()->ymax();
                        pinch()->target()->setY(y);
                    }
                    if (pinchStartRotation >= pinch()->minimumRotation()
                            && pinchStartRotation <= pinch()->maximumRotation()) {
                        qreal r = rotationAngle + pinchStartRotation;
                        r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation());
                        pinch()->target()->setRotation(r);
                    }
                }
            }
        }
    }
}

void QDeclarativePinchArea::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    stealMouse = false;
    if (!absorb)
        QDeclarativeItem::mousePressEvent(event);
    else {
        setKeepMouseGrab(false);
        event->setAccepted(true);
    }
}

void QDeclarativePinchArea::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (!absorb) {
        QDeclarativeItem::mouseMoveEvent(event);
        return;
    }
}

void QDeclarativePinchArea::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    stealMouse = false;
    if (!absorb) {
        QDeclarativeItem::mouseReleaseEvent(event);
    } else {
        QGraphicsScene *s = scene();
        if (s && s->mouseGrabberItem() == this)
            ungrabMouse();
        setKeepMouseGrab(false);
    }
}

bool QDeclarativePinchArea::sceneEvent(QEvent *event)
{
    bool rv = QDeclarativeItem::sceneEvent(event);
    if (event->type() == QEvent::UngrabMouse) {
        setKeepMouseGrab(false);
    }
    return rv;
}

bool QDeclarativePinchArea::sendMouseEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsSceneMouseEvent mouseEvent(event->type());
    QRectF myRect = mapToScene(QRectF(0, 0, width(), height())).boundingRect();

    QGraphicsScene *s = scene();
    QDeclarativeItem *grabber = s ? qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem()) : 0;
    bool stealThisEvent = stealMouse;
    if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) {
        mouseEvent.setAccepted(false);
        for (int i = 0x1; i <= 0x10; i <<= 1) {
            if (event->buttons() & i) {
                Qt::MouseButton button = Qt::MouseButton(i);
                mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button)));
            }
        }
        mouseEvent.setScenePos(event->scenePos());
        mouseEvent.setLastScenePos(event->lastScenePos());
        mouseEvent.setPos(mapFromScene(event->scenePos()));
        mouseEvent.setLastPos(mapFromScene(event->lastScenePos()));

        switch(mouseEvent.type()) {
        case QEvent::GraphicsSceneMouseMove:
            mouseMoveEvent(&mouseEvent);
            break;
        case QEvent::GraphicsSceneMousePress:
            qDebug() << "mouse press";
            mousePressEvent(&mouseEvent);
            break;
        case QEvent::GraphicsSceneMouseRelease:
            mouseReleaseEvent(&mouseEvent);
            break;
        default:
            break;
        }
        grabber = qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem());
        if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
            grabMouse();

        return stealThisEvent;
    }
    if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) {
        stealMouse = false;
        if (s && s->mouseGrabberItem() == this)
            ungrabMouse();
        setKeepMouseGrab(false);
    }
    return false;
}

bool QDeclarativePinchArea::sceneEventFilter(QGraphicsItem *i, QEvent *e)
{
    if (!absorb || !isVisible())
        return QDeclarativeItem::sceneEventFilter(i, e);
    switch (e->type()) {
    case QEvent::GraphicsSceneMousePress:
    case QEvent::GraphicsSceneMouseMove:
    case QEvent::GraphicsSceneMouseRelease:
        return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e));
        break;
    case QEvent::TouchBegin:
    case QEvent::TouchUpdate: {
            QTouchEvent *touch = static_cast<QTouchEvent*>(e);
            touchPoints.clear();
            for (int i = 0; i < touch->touchPoints().count(); ++i)
                if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased))
                    touchPoints << touch->touchPoints().at(i);
            updatePinch();
        }
        return inPinch;
    case QEvent::TouchEnd:
        touchPoints.clear();
        updatePinch();
        break;
    default:
        break;
    }

    return QDeclarativeItem::sceneEventFilter(i, e);
}

void QDeclarativePinchArea::geometryChanged(const QRectF &newGeometry,
                                            const QRectF &oldGeometry)
{
    QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
}

QVariant QDeclarativePinchArea::itemChange(GraphicsItemChange change,
                                       const QVariant &value)
{
    return QDeclarativeItem::itemChange(change, value);
}

QDeclarativePinch *QDeclarativePinchArea::pinch()
{
    if (!pinch_)
        pinch_ = new QDeclarativePinch;
    return pinch_;
}


QT_END_NAMESPACE
