ginebra2/ContentViews/ScrollableWebContentView.cpp
changeset 6 1c3b8676e58c
child 9 b39122337a00
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ginebra2/ContentViews/ScrollableWebContentView.cpp	Tue Jul 06 14:03:49 2010 +0300
@@ -0,0 +1,518 @@
+/*
+* 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)));
+}
+
+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())
+        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)
+{
+    //Cache release event to send on release
+    QPointF pos = gestureEvent->position();
+    sendEventToWebKit(QEvent::GraphicsSceneMouseRelease, pos);
+}
+
+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