qgraphicswkview.cpp   [plain text]


/*
 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "qgraphicswkview.h"

#include "ChunkedUpdateDrawingAreaProxy.h"
#include "IntSize.h"
#include "RunLoop.h"
#include "TiledDrawingAreaProxy.h"
#include "UpdateChunk.h"
#include "WKAPICast.h"
#include "qwkpage.h"
#include "qwkpage_p.h"
#include <QApplication>
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QMenu>
#include <QPainter>
#include <QScrollBar>
#include <QStyleOptionGraphicsItem>
#include <QUrl>
#include <QtDebug>
#include <WebKit2/WKRetainPtr.h>
#include <wtf/RefPtr.h>
#include <wtf/text/WTFString.h>

using namespace WebKit;
using namespace WebCore;

struct QGraphicsWKViewPrivate {
    QGraphicsWKViewPrivate(QGraphicsWKView* view);
    WKPageRef pageRef() const { return page->pageRef(); }

    void onToolTipChanged(const QString&);
    void onScaleChanged();
    void commitScale();

    QGraphicsWKView* q;
    QWKPage* page;
    QSharedPointer<QMenu> activeMenu;
    RunLoop::Timer<QGraphicsWKViewPrivate> m_scaleCommitTimer;
    bool m_isChangingScale;
};

QGraphicsWKView::QGraphicsWKView(QWKContext* context, BackingStoreType backingStoreType, QGraphicsItem* parent)
    : QGraphicsWidget(parent)
    , d(new QGraphicsWKViewPrivate(this))
{
    d->page = new QWKPage(context);
    init(backingStoreType);
}

QGraphicsWKView::QGraphicsWKView(QWKContext* context, WKPageGroupRef pageGroupRef, BackingStoreType backingStoreType, QGraphicsItem* parent)
    : QGraphicsWidget(parent)
    , d(new QGraphicsWKViewPrivate(this))
{
    d->page = new QWKPage(context, pageGroupRef);
    init(backingStoreType);
}

void QGraphicsWKView::init(BackingStoreType backingStoreType)
{
    setFocusPolicy(Qt::StrongFocus);
    setAcceptHoverEvents(true);

#if ENABLE(TILED_BACKING_STORE)
    if (backingStoreType == Tiled)
        connect(this, SIGNAL(scaleChanged()), this, SLOT(onScaleChanged()));
#endif

    d->page->d->init(this, backingStoreType);
    connect(d->page, SIGNAL(titleChanged(QString)), this, SIGNAL(titleChanged(QString)));
    connect(d->page, SIGNAL(loadStarted()), this, SIGNAL(loadStarted()));
    connect(d->page, SIGNAL(loadFinished(bool)), this, SIGNAL(loadFinished(bool)));
    connect(d->page, SIGNAL(loadProgress(int)), this, SIGNAL(loadProgress(int)));
    connect(d->page, SIGNAL(initialLayoutCompleted()), this, SIGNAL(initialLayoutCompleted()));
    connect(d->page, SIGNAL(urlChanged(const QUrl&)), this, SIGNAL(urlChanged(const QUrl&)));
    connect(d->page, SIGNAL(cursorChanged(const QCursor&)), this, SLOT(updateCursor(const QCursor&)));
    connect(d->page, SIGNAL(focusNextPrevChild(bool)), this, SLOT(focusNextPrevChildCallback(bool)));
    connect(d->page, SIGNAL(showContextMenu(QSharedPointer<QMenu>)), this, SLOT(showContextMenu(QSharedPointer<QMenu>)));
    connect(d->page, SIGNAL(toolTipChanged(QString)), this, SLOT(onToolTipChanged(QString)));
}

QGraphicsWKView::~QGraphicsWKView()
{
    delete d->page;
    delete d;
}

QWKPage* QGraphicsWKView::page() const
{
    return d->page;
}

void QGraphicsWKView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget*)
{
    page()->d->paint(painter, option->exposedRect.toAlignedRect());
}

void QGraphicsWKView::setGeometry(const QRectF& rect)
{
    QSizeF oldSize = geometry().size();
    QGraphicsWidget::setGeometry(rect);
    if (geometry().size() == oldSize)
        return;

    // NOTE: call geometry() as setGeometry ensures that
    // the geometry is within legal bounds (minimumSize, maximumSize)
    page()->setViewportSize(geometry().size().toSize());
}

void QGraphicsWKView::load(const QUrl& url)
{
    page()->load(url);
}

void QGraphicsWKView::setUrl(const QUrl& url)
{
    page()->setUrl(url);
}

QUrl QGraphicsWKView::url() const
{
    return page()->url();
}

QString QGraphicsWKView::title() const
{
    return page()->title();
}

void QGraphicsWKView::triggerPageAction(QWKPage::WebAction action, bool checked)
{
    page()->triggerAction(action, checked);
}

void QGraphicsWKView::back()
{
    page()->triggerAction(QWKPage::Back);
}

void QGraphicsWKView::forward()
{
    page()->triggerAction(QWKPage::Forward);
}

void QGraphicsWKView::reload()
{
    page()->triggerAction(QWKPage::Reload);
}

void QGraphicsWKView::stop()
{
    page()->triggerAction(QWKPage::Stop);
}

void QGraphicsWKView::updateCursor(const QCursor& cursor)
{
    setCursor(cursor);
}

class FriendlyWidget : public QWidget
{
public:
    bool focusNextPrevChild(bool next);
};

void QGraphicsWKView::focusNextPrevChildCallback(bool next)
{
    if (hasFocus()) {
        // find the view which has the focus:
        QList<QGraphicsView*> views = scene()->views();
        const int viewCount = views.count();
        QGraphicsView* focusedView = 0;
        for (int i = 0; i < viewCount; ++i) {
            if (views[i]->hasFocus()) {
                focusedView = views[i];
                break;
            }
        }

        if (focusedView) {
            QWidget* window = focusedView->window();
            FriendlyWidget* friendlyWindow = static_cast<FriendlyWidget*>(window);
            friendlyWindow->focusNextPrevChild(next);
        }
    }
}

/*! \reimp
*/
bool QGraphicsWKView::focusNextPrevChild(bool next)
{
    QKeyEvent ev(QEvent::KeyPress, Qt::Key_Tab, Qt::KeyboardModifiers(next ? Qt::NoModifier : Qt::ShiftModifier));
    page()->d->keyPressEvent(&ev);
    return true;
}

/*! \reimp
*/
QVariant QGraphicsWKView::itemChange(GraphicsItemChange change, const QVariant& value)
{
    // Here so that it can be reimplemented without breaking ABI.
    return QGraphicsWidget::itemChange(change, value);
}

/*! \reimp
*/
bool QGraphicsWKView::event(QEvent* event)
{
    QEvent::Type eventType = event->type();
    switch (eventType) {
    case QEvent::TouchBegin:
    case QEvent::TouchEnd:
    case QEvent::TouchUpdate:
        touchEvent(static_cast<QTouchEvent*>(event));
        return true;
    case QEvent::Show:
        page()->d->page->drawingArea()->setPageIsVisible(true);
        break;
    case QEvent::Hide:
        page()->d->page->drawingArea()->setPageIsVisible(false);
        break;
    default:
        break;
    }

    // Here so that it can be reimplemented without breaking ABI.
    return QGraphicsWidget::event(event);
}

/*! \reimp
*/
QSizeF QGraphicsWKView::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const
{
    if (which == Qt::PreferredSize)
        return QSizeF(800, 600);
    return QGraphicsWidget::sizeHint(which, constraint);
}

/*! \reimp
*/
QVariant QGraphicsWKView::inputMethodQuery(Qt::InputMethodQuery query) const
{
    // implement
    return QVariant();
}

/*! \reimp
*/
void QGraphicsWKView::keyPressEvent(QKeyEvent* ev)
{
    page()->d->keyPressEvent(ev);
}

/*! \reimp
*/
void QGraphicsWKView::keyReleaseEvent(QKeyEvent* ev)
{
    page()->d->keyReleaseEvent(ev);
}

void QGraphicsWKView::hoverMoveEvent(QGraphicsSceneHoverEvent* ev)
{
    QGraphicsSceneMouseEvent me(QEvent::GraphicsSceneMouseMove);
    me.setPos(ev->pos());
    me.setScreenPos(ev->screenPos());

    page()->d->mouseMoveEvent(&me);

    if (!ev->isAccepted())
        QGraphicsItem::hoverMoveEvent(ev);
}

void QGraphicsWKView::mouseMoveEvent(QGraphicsSceneMouseEvent* ev)
{
    page()->d->mouseMoveEvent(ev);
    if (!ev->isAccepted())
        QGraphicsItem::mouseMoveEvent(ev);
}

void QGraphicsWKView::mousePressEvent(QGraphicsSceneMouseEvent* ev)
{
    page()->d->mousePressEvent(ev);
    if (!ev->isAccepted())
        QGraphicsItem::mousePressEvent(ev);
}

void QGraphicsWKView::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev)
{
    page()->d->mouseReleaseEvent(ev);
    if (!ev->isAccepted())
        QGraphicsItem::mouseReleaseEvent(ev);
}

void QGraphicsWKView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev)
{
    page()->d->mouseDoubleClickEvent(ev);
    if (!ev->isAccepted())
        QGraphicsItem::mouseReleaseEvent(ev);
}

void QGraphicsWKView::wheelEvent(QGraphicsSceneWheelEvent* ev)
{
    page()->d->wheelEvent(ev);
    if (!ev->isAccepted())
        QGraphicsItem::wheelEvent(ev);
}

void QGraphicsWKView::touchEvent(QTouchEvent* ev)
{
    page()->d->touchEvent(ev);
}

void QGraphicsWKView::focusInEvent(QFocusEvent*)
{
    page()->d->page->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
}

void QGraphicsWKView::focusOutEvent(QFocusEvent*)
{
    page()->d->page->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
}


/*!
    This slot is called when the engine require a context sensitive menu to be displayed.

    The \a menu passed as a parameter is the menu to be displayed. It is populated with the
    actions possible for its current position. The menu is empty if there is no action for the position.
*/
void QGraphicsWKView::showContextMenu(QSharedPointer<QMenu> menu)
{
    // Remove the active menu in case this function is called twice.
    if (d->activeMenu)
        d->activeMenu->hide();

    if (menu->isEmpty())
        return;

    d->activeMenu = menu;

    QWidget* view = 0;
    if (QGraphicsScene* myScene = scene()) {
        const QList<QGraphicsView*> views = myScene->views();
        for (unsigned i = 0; i < views.size(); ++i) {
            if (views.at(i) == QApplication::focusWidget()) {
                view = views.at(i);
                break;
            }
        }
        if (!view)
            view = views.value(0, 0);
    }
    if (view)
        menu->setParent(view, menu->windowFlags());
    menu->exec(view->mapToGlobal(menu->pos()));
    if (d->activeMenu == menu)
        d->activeMenu.clear();
}

void QGraphicsWKView::takeSnapshot(const QSize& size, const QRect& contentsRect)
{
#if ENABLE(TILED_BACKING_STORE)
    DrawingAreaProxy* drawingArea = page()->d->page->drawingArea();
    if (drawingArea->type() != DrawingAreaTypeTiled)
        return;
    TiledDrawingAreaProxy* tiledDrawingArea = static_cast<TiledDrawingAreaProxy*>(drawingArea);
    tiledDrawingArea->takeSnapshot(size, contentsRect);
#endif
}

QGraphicsWKViewPrivate::QGraphicsWKViewPrivate(QGraphicsWKView* view)
    : q(view)
    , activeMenu(0)
    , m_scaleCommitTimer(RunLoop::current(), this, &QGraphicsWKViewPrivate::commitScale)
    , m_isChangingScale(false)
{
}

QRectF QGraphicsWKView::visibleRect() const
{
    if (!scene())
        return QRectF();

    QList<QGraphicsView*> views = scene()->views();
    if (views.isEmpty())
        return QRectF();

    QGraphicsView* graphicsView = views.at(0);
    int xOffset = graphicsView->horizontalScrollBar()->value();
    int yOffset = graphicsView->verticalScrollBar()->value();
    return mapRectFromScene(QRectF(QPointF(xOffset, yOffset), graphicsView->viewport()->size()));
}

void QGraphicsWKView::prepareScaleChange()
{
#if ENABLE(TILED_BACKING_STORE)
    ASSERT(!d->m_isChangingScale);
    d->m_isChangingScale = true;
    d->m_scaleCommitTimer.stop();
#endif
}

void QGraphicsWKView::commitScaleChange()
{
#if ENABLE(TILED_BACKING_STORE)
    ASSERT(d->m_isChangingScale);
    d->m_isChangingScale = false;
    d->commitScale();
#endif
}

void QGraphicsWKViewPrivate::onScaleChanged()
{
#if ENABLE(TILED_BACKING_STORE)
    if (!m_isChangingScale)
        m_scaleCommitTimer.startOneShot(0.1);
#endif
}

void QGraphicsWKViewPrivate::onToolTipChanged(const QString& toolTip)
{
    q->setToolTip(toolTip);
}

void QGraphicsWKViewPrivate::commitScale()
{
#if ENABLE(TILED_BACKING_STORE)
    DrawingAreaProxy* drawingArea = page->d->page->drawingArea();
    float newScale = q->scale();
    if (drawingArea->type() == DrawingAreaTypeTiled) {
        TiledDrawingAreaProxy* tiledDrawingArea = static_cast<TiledDrawingAreaProxy*>(drawingArea);
        if (tiledDrawingArea->contentsScale() == newScale)
            return;
        tiledDrawingArea->setContentsScale(newScale);
        // For now we block until complete.
        tiledDrawingArea->waitUntilUpdatesComplete();
    }
#endif
}

#include "moc_qgraphicswkview.cpp"