ginebra2/ContentViews/ScrollableWebContentView.cpp
author hgs
Fri, 06 Aug 2010 17:23:08 -0400
changeset 9 b39122337a00
parent 3 0954f5dd2cd0
child 16 3c88a81ff781
permissions -rw-r--r--
201031

/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not,
* see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/".
*
* Description:
*
*/

#include "ScrollableWebContentView.h"

#include "Gestures/GestureRecognizer.h"
#include "Kinetics/KineticScroller.h"
#include "ScrollableViewBase.h"
#include "ViewportMetaDataParser.h"
#include "WebContentAnimationItem.h"

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsWebView>
#include <QWebElement>
#include <QWebHitTestResult>

//Kinetic scroll constants
static const int ScrollsPerSecond = 30;
static const int MinimumScrollVelocity = 10;
static const qreal AxisLockThreshold = .8;


//Zooming constants
static const int ZoomAnimationDuration = 300;   //ms. Zooming transition duration
static const qreal ZoomStep = .5;               //Incremental zoom step
const int TileUpdateEnableDelay = 500;         //Wait duration before tiling updates are enabled.

namespace GVA {

ScrollableWebContentView::ScrollableWebContentView(WebContentAnimationItem* webAnimationItem, QGraphicsItem* parent)
    : ScrollableViewBase(parent)
    , m_gestureRecognizer(this)
{
    m_viewportMetaData = new ViewportMetaData();

    //Kinetic scroller settings
    //Sets the number of scrolls (frames) per second to sps.
    m_kineticScroller->setScrollsPerSecond(ScrollsPerSecond);
    //For elastic scroll in page edges
    m_kineticScroller->setOvershootPolicy(KineticScroller::OvershootWhenScrollable);

    //Gesture settings
    //For detecting scroll direction
    m_gestureRecognizer.setAxisLockThreshold(AxisLockThreshold);
    //To enable touch and drag scrolling
    m_gestureRecognizer.setMinimumVelocity(MinimumScrollVelocity);

    setWidget(webAnimationItem);
    //FIX ME : Revisit this code. Duplicate info sharing!
    webAnimationItem->setViewportMetaData(m_viewportMetaData);


    m_tileUpdateEnableTimer.setSingleShot(true);
    connect(&m_tileUpdateEnableTimer, SIGNAL(timeout()), webAnimationItem, SLOT(enableContentUpdates()));

    //Setup zooming animator
    m_zoomAnimator = new QPropertyAnimation(webAnimationItem, "geometry");
    m_zoomAnimator->setDuration(ZoomAnimationDuration);
    connect(m_zoomAnimator, SIGNAL(stateChanged(QAbstractAnimation::State,QAbstractAnimation::State)), this, SLOT(zoomAnimationStateChanged(QAbstractAnimation::State,QAbstractAnimation::State)));
    
    m_gesturesEnabled = true;
}

ScrollableWebContentView::~ScrollableWebContentView()
{
    delete m_viewportMetaData;
    delete m_kineticScroller;

    if(m_zoomAnimator) {
        m_zoomAnimator->stop();
        delete m_zoomAnimator;
    }
}

WebContentAnimationItem* ScrollableWebContentView::viewportWidget() const
{
    return qobject_cast<WebContentAnimationItem*>(scrollWidget());
}

void ScrollableWebContentView::zoomToScreenCenter(bool zoomIn)
{
    //If viewport metadata has user scalable false.
    //Do not zoom.
    if (!m_viewportMetaData->m_userScalable)
        return;

    qreal scale = 1;
    scale += ZoomStep;

    if (!zoomIn)
        scale = 1/scale;

    qreal curScale =  viewportWidget()->zoomScale();

    if (zoomIn && (curScale * scale > m_viewportMetaData->m_maximumScale))
        scale = m_viewportMetaData->m_maximumScale / curScale;
    else if (!zoomIn && (curScale * scale < m_viewportMetaData->m_minimumScale))
        scale = m_viewportMetaData->m_minimumScale / curScale;

    if(scale == 1.)
        return;

    //Screen center
    QPointF scrCenter(size().width()/2, size().height()/2);
    //Map screen center to document
    QPointF docPoint(viewportWidget()->mapFromScene(scrCenter));
    //Maintain that spot in the same point on the viewport
    QPointF docPointInScr(viewportWidget()->mapToParent(docPoint));
    startZoomAnimToItemHotspot(docPoint, docPointInScr, scale);
}

ZoomMetaData ScrollableWebContentView::currentPageInfo()
{
    ZoomMetaData data;

    data.initialScale = m_viewportMetaData->m_initialScale;
    data.minScale = m_viewportMetaData->m_minimumScale;
    data.maxScale = m_viewportMetaData->m_maximumScale;
    data.userScalable = m_viewportMetaData->m_userScalable;
    data.m_specifiedWidth = m_viewportMetaData->m_specifiedData.m_width;
    data.m_specifiedHeight= m_viewportMetaData->m_specifiedData.m_height;

    data.rect = viewportWidget()->geometry();
    data.scale = viewportWidget()->zoomScale();
    data.webViewSize = viewportWidget()->webView()->geometry();
    data.viewportSize = size();

    return data;
}

void ScrollableWebContentView::setCurrentPageInfo(ZoomMetaData data)
{
    m_viewportMetaData->m_initialScale = data.initialScale;
    m_viewportMetaData->m_minimumScale = data.minScale;
    m_viewportMetaData->m_maximumScale = data.maxScale;
    m_viewportMetaData->m_userScalable = data.userScalable;
    m_viewportMetaData->m_specifiedData.m_width = data.m_specifiedWidth;
    m_viewportMetaData->m_specifiedData.m_height = data.m_specifiedHeight;
    m_viewportMetaData->m_isValid = true;

    m_viewportMetaData->m_width = data.webViewSize.width();
    m_viewportMetaData->m_height = data.webViewSize.height();

    viewportWidget()->webView()->setGeometry(data.webViewSize);
    viewportWidget()->setZoomScale(data.scale, true);
    viewportWidget()->setGeometry(data.rect);

    if (data.viewportSize.width() != size().width())
        adjustViewportSize(data.viewportSize, size());
}

ZoomMetaData ScrollableWebContentView::defaultZoomData()
{
    ZoomMetaData data;

    data.initialScale = m_viewportMetaData->m_initialScale;
    data.minScale = m_viewportMetaData->m_minimumScale;
    data.maxScale = m_viewportMetaData->m_maximumScale;
    data.userScalable = m_viewportMetaData->m_userScalable;

    data.scale = 1.0;
    data.rect = QRectF();
    data.webViewSize = QRectF();
    data.viewportSize = QSizeF();

    return data;
}

void ScrollableWebContentView::updatePreferredContentSize()
{
    viewportWidget()->updatePreferredContentSize(QSize(m_viewportMetaData->m_width
                                                       , m_viewportMetaData->m_height));
}

void ScrollableWebContentView::setSuperPage()
{
    m_viewportMetaData->m_initialScale = 1.;
    m_viewportMetaData->m_minimumScale = 1.;
    m_viewportMetaData->m_maximumScale = 1.;
    m_viewportMetaData->m_specifiedData.m_width = "device-width";
    m_viewportMetaData->m_specifiedData.m_height = "device-height";
    m_viewportMetaData->m_userScalable = false;

    QSize contentSize = viewportWidget()->contentsSize();
    QRect webViewRect(0, 0, size().width(), contentSize.height());
    viewportWidget()->webView()->setGeometry(webViewRect);
    viewportWidget()->setZoomScale(1., true);
    viewportWidget()->setGeometry(webViewRect);

    m_viewportMetaData->m_width = size().width();
    m_viewportMetaData->m_height = size().height();
    m_viewportMetaData->m_isValid = true;

    updatePreferredContentSize();
}

void ScrollableWebContentView::reset()
{
    // TODO: INVESTIGATE: In the case of multiple windows loading pages simultaneously, it is possible
    // to be calling this slot on a signal from a frame that is not
    // the frame of the page saved here. It might be better to use 'sender' instead of
    // page->mainFrame() to get the metaData so that we use the meta data of the corresponding
    // frame

    QWebPage* page = viewportWidget()->webView()->page();
    if (!page)
        return;

    //Initialize viewport metadata
    m_viewportMetaData->reset();

    QWebFrame* frame = page->mainFrame();
    QMap<QString, QString> metaData = frame->metaData();
    QString viewportTag = metaData.value("viewport");

    QRect clientRect = geometry().toAlignedRect();
    ViewportMetaDataParser parser(clientRect);
    *m_viewportMetaData = parser.parse(viewportTag);

    updatePreferredContentSize();
    setViewportWidgetGeometry(QRectF(QPointF(),
                                     QSize(m_viewportMetaData->m_width, m_viewportMetaData->m_height)
                                     * m_viewportMetaData->m_initialScale));
}

void ScrollableWebContentView::contentsSizeChanged(const QSize& newContentSize)
{
    QRect clientRect = geometry().toAlignedRect();
    m_viewportMetaData->updateViewportData(newContentSize, clientRect);
    viewportWidget()->resize(QSize(m_viewportMetaData->m_width, m_viewportMetaData->m_height)
                             * m_viewportMetaData->m_initialScale);
}

void ScrollableWebContentView::pageLoadFinished(bool ok)
{
    Q_UNUSED(ok);
    QSize contentSize = viewportWidget()->contentsSize();
    QRect clientRect = geometry().toAlignedRect();
    m_viewportMetaData->updateViewportData(contentSize, clientRect);

    viewportWidget()->resize(QSize(m_viewportMetaData->m_width, m_viewportMetaData->m_height)
                             * m_viewportMetaData->m_initialScale);
    viewportWidget()->setZoomScale(m_viewportMetaData->m_initialScale, true);
}

bool ScrollableWebContentView::sceneEventFilter(QGraphicsItem* item, QEvent* event)
{
    Q_UNUSED(item);

    bool handled = false;
    
    if (!isVisible() || !m_gesturesEnabled) {
        if (event->type() == QEvent::GraphicsSceneContextMenu)
            return true;
        else
            return handled;
    }

    //Pass all events to recognizer
    handled  = m_gestureRecognizer.mouseEventFilter(static_cast<QGraphicsSceneMouseEvent *>(event));
    return handled;
}

void ScrollableWebContentView::handleGesture(GestureEvent* gestureEvent)
{
    switch (gestureEvent->type()) {
    case GestureEvent::Touch:
        handlePress(gestureEvent);
        break;
    case GestureEvent::Release:
        handleRelease(gestureEvent);
        break;
    case GestureEvent::Pan:
        handlePan(gestureEvent);
        break;
    case GestureEvent::Flick:
        handleFlick(gestureEvent);
        break;
    case GestureEvent::DoubleTap:
        handleDoubleTap(gestureEvent);
        break;
    case GestureEvent::LongTap:
        handleLongTap(gestureEvent);
        break;
    default:
        break;
    }

}

void ScrollableWebContentView::handlePress(GestureEvent* gestureEvent)
{
    m_kineticScroller->stop();
    QPointF pos = gestureEvent->position();
    sendEventToWebKit(QEvent::GraphicsSceneMousePress, pos);
}

void ScrollableWebContentView::handleRelease(GestureEvent* gestureEvent)
{
    //FIX ME:
    emit mouseEvent(QEvent::GraphicsSceneMousePress);
    //Cache release event to send on release
    QPointF pos = gestureEvent->position();
    sendEventToWebKit(QEvent::GraphicsSceneMouseRelease, pos);
    emit mouseEvent(QEvent::GraphicsSceneMouseRelease);
}

void ScrollableWebContentView::handleDoubleTap(GestureEvent* gestureEvent)
{
    if (!m_viewportMetaData->m_userScalable)
        return;

    QRectF target;
    WebContentAnimationItem* webViewProxy = viewportWidget();

    // Contentview center is the focus hotspot
    QPointF viewTargetHotspot(size().width() / 2, size().height() / 2);

    //Get the focussable element rect from current touch position
    QPointF touchPoint = webViewProxy->mapFromScene(gestureEvent->position());
    QRectF zoomRect = webViewProxy->findZoomableRectForPoint(touchPoint);

    if (!zoomRect.isValid()) {
        //FIX ME: Add an event ignore animation
        return;
    }

    // target is the center of the identified rect x-wise
    // y-wise it's the place user touched
    QPointF hotspot(zoomRect.center().x(), touchPoint.y());
    qreal scale = size().width() / zoomRect.size().width();
    startZoomAnimToItemHotspot(hotspot, viewTargetHotspot, scale, zoomRect);
}

void ScrollableWebContentView::handlePan(GestureEvent* gestureEvent)
{
    QPoint scrollPos = ScrollableViewBase::scrollPosition();
    m_kineticScroller->doPan(gestureEvent->delta());
    QPoint delta;
    delta.setX(-gestureEvent->delta().x());
    delta.setY(-gestureEvent->delta().y());
    emit viewScrolled(scrollPos, delta);
}

void ScrollableWebContentView::handleFlick(GestureEvent* gestureEvent)
{
    QPoint scrollPos = ScrollableViewBase::scrollPosition();
    m_kineticScroller->doFlick(gestureEvent->velocity());
}

void ScrollableWebContentView::handleLongTap(GestureEvent* gestureEvent)
{
    QWebPage* page = viewportWidget()->webView()->page();
    QPointF contextPt = viewportWidget()->webView()->mapFromScene(gestureEvent->position());
    QWebHitTestResult result = page->currentFrame()->hitTestContent(contextPt.toPoint());

    //Notify context menu observers
    emit contextEventObject(&result);
}

void ScrollableWebContentView::setViewportWidgetGeometry(const QRectF& r)
{
    ScrollableViewBase::setScrollWidgetGeometry(r);
}

void ScrollableWebContentView::startZoomAnimToItemHotspot(const QPointF& hotspot, const QPointF& viewTargetHotspot, qreal scale,  QRectF target)
{
    WebContentAnimationItem* animWidget = viewportWidget();

    QPointF newHotspot = hotspot * scale;
    QPointF newViewportOrigon = newHotspot - viewTargetHotspot;
    QRectF zoomedRect(-newViewportOrigon, animWidget->size() * scale);

    QRectF temp = adjustScrollWidgetRect(zoomedRect);
    qreal diff = qAbs(scrollWidget()->geometry().y() - temp.y());

    //FIX ME : Seperate the logic for centerzoom and block-focus zoom
    if (qFuzzyCompare(scrollWidget()->geometry().topLeft().x(), temp.topLeft().x())
        && qFuzzyCompare(scrollWidget()->geometry().width(), temp.width())
        && qFuzzyCompare(scrollWidget()->geometry().height(), temp.height())
        && !target.isEmpty() && (diff <= target.height())) {

            scale = size().width() / animWidget->size().width();
            newHotspot = QPointF(0, -animWidget->pos().y()) * scale;
            newViewportOrigon = newHotspot - viewTargetHotspot;
            zoomedRect = QRectF(-newViewportOrigon, animWidget->size() * scale);
    }

    startZoomAnimation(zoomedRect);
}

bool ScrollableWebContentView::isZoomedIn() const
{
    return size().width() < viewportWidget()->size().width();
}

void ScrollableWebContentView::stateChanged(KineticScrollable::State oldState
                                            , KineticScrollable::State newState)
{
    ScrollableViewBase::stateChanged(oldState, newState);

    if (newState == KineticScrollable::Pushing) {
        m_tileUpdateEnableTimer.stop();
        viewportWidget()->disableContentUpdates();
    }
    else if (newState == KineticScrollable::AutoScrolling) {
        m_tileUpdateEnableTimer.stop();
        viewportWidget()->disableContentUpdates();
    }
    else if (newState == KineticScrollable::Inactive) {
        m_tileUpdateEnableTimer.start(TileUpdateEnableDelay);
    }
}

void ScrollableWebContentView::startZoomAnimation(const QRectF& destRect)
{
    QAbstractAnimation::State animState = m_zoomAnimator->state();
    if (animState == QAbstractAnimation::Running)
        return;

    m_zoomAnimator->setStartValue(scrollWidget()->geometry());
    m_animationEndRect = adjustScrollWidgetRect(destRect);
    m_zoomAnimator->setEndValue(m_animationEndRect);
    m_zoomAnimator->start();
}

void ScrollableWebContentView::stopZoomAnimation()
{
    m_animationEndRect = QRectF();
    m_zoomAnimator->stop();
}

void ScrollableWebContentView::updateZoomEndRect()
{
    if (m_animationEndRect.isValid())
        scrollWidget()->setGeometry(m_animationEndRect);
}

void ScrollableWebContentView::zoomAnimationStateChanged(QAbstractAnimation::State newState,QAbstractAnimation::State)
{
    switch (newState) {
    case QAbstractAnimation::Stopped:
        updateZoomEndRect();
        break;
    default:
        break;
    }
}

void ScrollableWebContentView::resizeEvent(QGraphicsSceneResizeEvent* event)
{
    QGraphicsWidget::resizeEvent(event);

    //Ignore resize when chrome is being still setup
    if (!event->oldSize().width())
        return;

    adjustViewportSize(event->oldSize(), event->newSize());
}

void ScrollableWebContentView::adjustViewportSize(QSizeF oldSize, QSizeF newSize)
{
    //FIX ME : Check this
    if (m_viewportMetaData->m_isValid) {

        QRect clientRect = geometry().toAlignedRect();
        if (m_viewportMetaData->isLayoutNeeded())  {
            m_viewportMetaData->orientationChanged(clientRect);
            updatePreferredContentSize();
            return;
        } else
            m_viewportMetaData->updateViewportData(viewportWidget()->contentsSize(), clientRect);
    }

    qreal scale = newSize.width() / oldSize.width();
    QPointF middleLeft(0, oldSize.height()/2);
    QPointF docPoint(viewportWidget()->mapFromScene(middleLeft));

    QPointF resizedMiddleLeft(0, newSize.height()/2);
    QPointF resizedDocPoint(viewportWidget()->mapFromScene(resizedMiddleLeft));
    QPointF docPointInScr(viewportWidget()->mapToParent(resizedDocPoint));

    //FIX ME : Should be handled with only following function call
    //Since its not working, work-around is added. Plz fix it
    //startZoomAnimToItemHotspot(docPoint, docPointInScr, scale);

    QPointF newHotspot = docPoint * scale;
    QPointF newViewportOrigon = newHotspot - docPointInScr;
    QRectF zoomedRect(-newViewportOrigon,  viewportWidget()->size() * scale);
    QRectF adjustRect = adjustScrollWidgetRect(zoomedRect);

    setScrollWidgetGeometry(zoomedRect);
}

void ScrollableWebContentView::sendEventToWebKit(QEvent::Type type, QPointF& scenPos)
{
    //Setup event and send it to webkit
    QGraphicsSceneMouseEvent event(type);
    event.setScenePos(scenPos);
    event.setPos(viewportWidget()->webView()->mapFromScene(event.scenePos()));
    event.setButton(Qt::LeftButton);
    event.setButtons(Qt::LeftButton);
    event.setModifiers(Qt::NoModifier);
    viewportWidget()->webView()->page()->event(&event);
}

} //namespace GVA