browsercore/core/webdirectionalnavigation.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 04 May 2010 12:39:35 +0300
changeset 0 1450b09d0cfd
permissions -rw-r--r--
Revision: 201015 Kit: 201018

/*
* 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();
        }
    }
}

}