browsercore/core/webdirectionalnavigation.cpp
changeset 0 1450b09d0cfd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/browsercore/core/webdirectionalnavigation.cpp	Tue May 04 12:39:35 2010 +0300
@@ -0,0 +1,403 @@
+/*
+* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+*
+*/
+
+
+#include "qmath.h"
+#include "qpainter.h"
+#include "qwebpage.h"
+#include "qwebview.h"
+#include "qwebframe.h"
+#include "qwebelement.h"
+#include "webdirectionalnavigation.h"
+
+namespace WRT {
+
+const int KInitialSize = 20;
+const int KNormalScrollRange = 40;
+const int KFullStep = 14;
+const double KMaxDistance = 1000000;
+
+/*!
+    \class WebDirectionalNavigation
+    \since cwrt 1.0
+    \brief cwrt navigation.
+
+    \sa WebNavigation, WebTouchNavigation, WebCursorNavigation, WebHtmlTabIndexedNavigation
+*/
+WebDirectionalNavigation::WebDirectionalNavigation(QWebPage* webPage,QObject* view) :
+    m_webPage(webPage)
+,   m_view(view)
+{
+    install();
+}
+
+WebDirectionalNavigation::~WebDirectionalNavigation()
+{
+    uninstall();
+}
+
+void WebDirectionalNavigation::install()
+{
+    m_view->installEventFilter(this);
+    //initiallayoutCompleted();
+}
+
+void WebDirectionalNavigation::uninstall()
+{
+    if (m_view)
+        m_view->removeEventFilter(this);
+}
+
+void WebDirectionalNavigation::initiallayoutCompleted()
+{
+    m_webElement = m_webPage->currentFrame()->findFirstElement(QString("a,input,select,textarea,object"));
+    if (!m_webElement.isNull()) {
+        m_webElement.setFocus();
+        m_focusPoint = m_webElement.geometry().topLeft();
+        setCurrentFrameScrollPosition(m_focusPoint);
+    }
+}
+
+double WebDirectionalNavigation::calculateElementDistance(int direction, const QRect& possibleRect) 
+{
+    // Roughly based on this algorithm http://www.w3.org/TR/WICD/#nav-distance-fkt 
+    // Deviates in that the overlap is not calculated. Instead, if there is any overlap
+    // selection is restricted to x or y direction only. This helps for differnt size elments
+    // all lying on the same plane, but might mess up backtracking the naviation path.
+
+    QRect focusedRect(0,0,1,1);
+    if (!m_webElement.isNull())
+        focusedRect = QRect(m_webElement.geometry());
+
+    //calculate the next focuspoint
+    switch (direction) {
+    case Qt::Key_Up:
+        if (m_focusPoint.x() < focusedRect.x())
+            m_focusPoint.setX(focusedRect.x());
+        else if (m_focusPoint.x() > focusedRect.right())
+            m_focusPoint.setX(focusedRect.right());
+        m_focusPoint.setY(focusedRect.y());
+        break;
+    case Qt::Key_Down:
+        if (m_focusPoint.x() < focusedRect.x())
+            m_focusPoint.setX(focusedRect.x());
+        else if (m_focusPoint.x() > focusedRect.right())
+            m_focusPoint.setX(focusedRect.right());
+        m_focusPoint.setY(focusedRect.bottom());
+        break;
+    case Qt::Key_Right:
+        m_focusPoint.setX(focusedRect.right());
+        if (m_focusPoint.y() < focusedRect.y())
+            m_focusPoint.setY(focusedRect.y());
+        else if (m_focusPoint.y() > focusedRect.bottom())
+            m_focusPoint.setY(focusedRect.bottom());
+        break;
+    case Qt::Key_Left:
+        m_focusPoint.setX(focusedRect.x());
+        if (m_focusPoint.y() < focusedRect.y())
+            m_focusPoint.setY(focusedRect.y());
+        else if (m_focusPoint.y() > focusedRect.bottom())
+            m_focusPoint.setY(focusedRect.bottom());
+        break;
+    }
+
+    // Make sure the rectangle falls within the search area
+    if (direction == Qt::Key_Up && possibleRect.bottom() > m_focusPoint.y()
+        || direction == Qt::Key_Down && possibleRect.y() < m_focusPoint.y()
+        || direction == Qt::Key_Right && possibleRect.x() < m_focusPoint.x()
+        || direction == Qt::Key_Left && possibleRect.right() > m_focusPoint.x())
+        return KMaxDistance;
+
+    // The absolute distance (dx or dy) on the navigation axis between the opposing edges of the currently focused
+    // element and each of the candidates.
+    double distanceX = 0;
+    double distanceY = 0;
+
+    if (direction == Qt::Key_Up) {
+        // adjust the x distance based on the closest edge
+        if (m_focusPoint.x() < possibleRect.x()) 
+            distanceX = possibleRect.x() - m_focusPoint.x();
+        else if (m_focusPoint.x() > possibleRect.right())
+            distanceX = m_focusPoint.x() - possibleRect.right();
+       distanceY = m_focusPoint.y() - possibleRect.bottom();
+    } else if (direction == Qt::Key_Down) {
+        // adjust the x distance based on the closest edge
+        if (m_focusPoint.x() < possibleRect.x()) 
+            distanceX = possibleRect.x() - m_focusPoint.x();
+        else if (m_focusPoint.x() > possibleRect.right())
+            distanceX = m_focusPoint.x() - possibleRect.right();
+        distanceY = possibleRect.y() - m_focusPoint.y();
+    } else if (direction == Qt::Key_Right) {
+        distanceX = possibleRect.x() - m_focusPoint.x();
+        // adjust the y distance based on the closest edge
+        if (m_focusPoint.y() < possibleRect.y())
+            distanceY = possibleRect.y() - m_focusPoint.y();
+        else if (m_focusPoint.y() > possibleRect.bottom())
+            distanceY = m_focusPoint.y() - possibleRect.bottom();
+    } else if (direction == Qt::Key_Left) {
+        distanceX = m_focusPoint.x() - possibleRect.right();
+        // adjust the y distance based on the closest edge
+        if (m_focusPoint.y() < possibleRect.y()) 
+            distanceY = possibleRect.y() - m_focusPoint.y();
+        else if (m_focusPoint.y() > possibleRect.bottom())
+            distanceY = m_focusPoint.y() - possibleRect.bottom();
+    }
+
+    // The absolute distance on the axis orthogonal to the navigation axis between
+    // the opposing edges of currently focused element and each of candidates
+    double displacement = 0;
+
+    // The euclidean distance
+    double euclideanDist = sqrt(distanceX * distanceX + distanceY * distanceY);
+
+    // Area of the document to search
+    QRect searchRect(m_webPage->currentFrame()->scrollPosition(),m_webPage->viewportSize());
+
+    if (direction == Qt::Key_Up || direction == Qt::Key_Down) {
+        // if the rectangles are on the same plane set the euclideanDist to zero to favor this navigation
+        if ((possibleRect.x() == focusedRect.x())
+            || (possibleRect.right() > focusedRect.x() && possibleRect.right() < focusedRect.right())
+            || (possibleRect.x() > focusedRect.x() && possibleRect.x() < focusedRect.right())
+            || (possibleRect.x() > focusedRect.x() && possibleRect.right() < focusedRect.right())
+            || (possibleRect.x() < focusedRect.x() && possibleRect.right() > focusedRect.right())) {
+            euclideanDist = 0;
+            distanceX = 0;
+        } else {
+            // displacement is added for being orthogonally far from the current rectangle.
+            if (possibleRect.x() > focusedRect.right())
+                displacement = possibleRect.x() - focusedRect.right();
+            if (possibleRect.right() < focusedRect.x())
+                displacement = focusedRect.x() - possibleRect.right();
+
+            // This is a little sketchy, but if the rectangles are all 
+            // touching set the displacment to 1 so rects on the same plane win
+            if (displacement == 0)
+                displacement = 1;
+
+            // if the focus is within the view limit the diagonal search by half the search rectangle
+            if (searchRect.intersects(focusedRect) && (displacement > (searchRect.width()/2)))
+                return KMaxDistance;
+ 
+        }
+
+    } else if (direction == Qt::Key_Left || direction == Qt::Key_Right) {
+
+        // if the rectangles are on the same plane set the euclideanDist to zero to favor this navigation
+        if ((possibleRect.y() == focusedRect.y())
+            || (possibleRect.bottom() > focusedRect.y() && possibleRect.bottom() < focusedRect.bottom())
+            || (possibleRect.y() > focusedRect.y() && possibleRect.y() < focusedRect.bottom())
+            || (possibleRect.y() > focusedRect.y() && possibleRect.bottom() < focusedRect.bottom())
+            || (possibleRect.y() < focusedRect.y() && possibleRect.bottom() > focusedRect.bottom())) {
+           euclideanDist = 0;
+           distanceY = 0; 
+        } else {
+            // displacement is added for being orthogonally far from the current rectangle.
+            if (possibleRect.y() > focusedRect.bottom())
+                displacement = possibleRect.y() - focusedRect.bottom();
+            if (possibleRect.bottom() < focusedRect.y())
+                displacement = focusedRect.y() - possibleRect.bottom();
+
+            // This is a little sketchy, but if the rectangles are all 
+            // touching set the displacment to 1 so rects on the same plane win
+            if (displacement == 0) 
+                displacement = 1;
+
+            //  if the focus is within the view limit the diagonal search by half the search rectangle
+            if (searchRect.intersects(focusedRect) && (displacement > (searchRect.height()/2)))
+                return KMaxDistance; 
+        }
+    }
+
+    return euclideanDist + distanceX + distanceY + 2 * (displacement);
+}
+
+
+
+bool WebDirectionalNavigation::nextElementInDirection(int direction)
+{
+    double bestDistance = KMaxDistance;
+    QWebElement bestElement;
+#if QT_VERSION < 0x040600
+    QList<QWebElement> elementList = m_webPage->currentFrame()->findAllElements(QString("a,input,select,textarea,object"));
+#else
+    QList<QWebElement> elementList = m_webPage->currentFrame()->findAllElements(QString("a,input,select,textarea,object")).toList();
+#endif
+    QList<QWebElement>::iterator it;
+    for (it = elementList.begin(); it != elementList.end(); it++) {
+        QWebElement el(*it);
+        QRect nRect(el.geometry());
+        if (nRect.isValid()) {
+            if (QRect(m_webPage->currentFrame()->scrollPosition(),m_webPage->viewportSize()).intersects(nRect)) {
+                double distance = calculateElementDistance (direction, nRect);
+                if (bestDistance > distance) {
+                    bestDistance = distance;
+                    bestElement = el;
+                }
+            }
+        }
+    }
+
+    if (!bestElement.isNull() && bestElement != m_webElement) {
+        m_webElement.setStyleProperty("outline", m_webElementStyle);    	  
+        m_webElement = bestElement;
+        m_webElement.setFocus();
+        m_webElementStyle = m_webElement.styleProperty("outline", QWebElement::ComputedStyle);
+        m_webElement.setStyleProperty("outline", "3px ridge rgb(140,140,255)");             
+        scrollFrame(direction);
+        return true;
+    }
+
+    scrollFrameOneDirection (direction, KNormalScrollRange);
+    return false;
+}
+
+
+bool WebDirectionalNavigation::eventFilter(QObject *object, QEvent *event)
+{
+    if (object == m_view) {
+        switch (event->type()) {
+            case QEvent::KeyPress: {
+                QKeyEvent* ev = static_cast<QKeyEvent*>(event);
+                if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down 
+                || ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right )
+                    {
+                    return true;
+                    }
+                if (ev->key() ==  Qt::Key_Select )
+                    {
+                    QKeyEvent rockerEnterEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
+                    m_webPage->event(&rockerEnterEvent); 
+                    return true; 
+                    }                
+            }
+            break; 
+            case QEvent::KeyRelease: {
+                QKeyEvent* ev = static_cast<QKeyEvent*>(event);
+                if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down 
+                || ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right ) 
+                    {
+                    nextElementInDirection(ev->key());    
+                    return true;
+                    }
+                if (ev->key() == Qt::Key_Select )
+                    {
+                    QKeyEvent rockerEnterEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
+                    m_webPage->event(&rockerEnterEvent); 
+                    return true; 
+                    }
+            }
+            break;
+            default: 
+                return false; 
+            break; 
+        } // end of switch statement 
+    }  // end of if statement 
+    return false;
+}
+
+
+void WebDirectionalNavigation::scrollFrame(int direction)
+{
+
+    int xadjust = m_webPage->viewportSize().width()/5;
+    int yadjust = m_webPage->viewportSize().height()/5;
+
+    switch (direction) {
+        case Qt::Key_Left: {
+            int distanceX =  xadjust - m_webElement.geometry().x();
+            if (distanceX > 0)
+                scrollCurrentFrame(-distanceX,0);
+        }
+        break;
+        case Qt::Key_Right: {
+            int distanceX = m_webElement.geometry().right() - m_webPage->viewportSize().width()-xadjust;
+            if (distanceX > 0)
+                scrollCurrentFrame(distanceX,0);
+        }
+        break;
+        case Qt::Key_Up: {
+            int distanceY = yadjust - m_webElement.geometry().y();
+            if (distanceY > 0)
+                scrollCurrentFrame(0,-distanceY);
+        }
+        break;
+        case Qt::Key_Down: {
+            int distanceY = m_webElement.geometry().bottom() - m_webPage->viewportSize().height()-yadjust;
+            if (distanceY > 0)
+                scrollCurrentFrame(0,distanceY);
+        }
+        break;
+    }
+}
+
+void WebDirectionalNavigation::scrollFrameOneDirection (int direction, int distance)
+{
+    int dx = 0;
+    int dy = 0;
+
+    switch (direction) {
+        case Qt::Key_Up: {
+            dy = -distance;
+        }
+        break;
+        case Qt::Key_Down: {
+            dy = +distance;
+        }
+        break;
+        case Qt::Key_Left: {
+            dx = -distance;
+        }
+        break;
+        case Qt::Key_Right: {
+            dx = distance;
+        }
+        break;
+        default:
+        break;
+    }
+
+    scrollCurrentFrame(dx, dy);
+}
+
+void WebDirectionalNavigation::scrollCurrentFrame (int dx, int dy)
+{
+    QPoint scrollPosition = m_webPage->currentFrame()->scrollPosition();
+    m_webPage->currentFrame()->scroll(dx, dy);
+
+    /* emit pageScrollPositionZero singal if it's mainFrame scrolling or scroll to top*/
+    if (m_webPage->currentFrame() == m_webPage->mainFrame()) {
+        if (scrollPosition.y() == 0 || m_webPage->currentFrame()->scrollPosition().y() == 0) {
+            emit pageScrollPositionZero();
+        }
+    }
+}
+
+void WebDirectionalNavigation::setCurrentFrameScrollPosition (QPoint& pos)
+{
+    QPoint scrollPosition = m_webPage->currentFrame()->scrollPosition();
+    m_webPage->currentFrame()->setScrollPosition(pos);
+
+    /* emit pageScrollPositionZero singal if it's mainFrame scrolling or scroll to top*/
+    if (m_webPage->currentFrame() == m_webPage->mainFrame()) {
+        if (scrollPosition.y() == 0 || m_webPage->currentFrame()->scrollPosition().y() == 0) {
+            emit pageScrollPositionZero();
+        }
+    }
+}
+
+}
+