[Qt] [WK2] Support customizing popup menus with QML
authorcaio.oliveira@openbossa.org <caio.oliveira@openbossa.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 12 Dec 2011 13:53:13 +0000 (13:53 +0000)
committercaio.oliveira@openbossa.org <caio.oliveira@openbossa.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 12 Dec 2011 13:53:13 +0000 (13:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=73560

Reviewed by Tor Arne Vestbø.

Source/WebKit2:

Add a new property 'itemSelector' to WebView (experimental for now) that contains
the QML component used when it needs to spawn a popup menu. For example, <select>
HTML tag may trigger a popup menu.

When loaded the component will have the 'model' available in its context with two
properties: 'elementRect', describing the position of the element which spawned
the item selector, and 'items', which is a model ready to be used by ListView. The
'model' also have methods to accept/reject the selection.

Option groups are available as a property for each row in the 'items' model. This
can be used together with ListView to create sections, as demonstrated in the
MiniBrowser. QML tests were added as well.

The existing Desktop version is removed since after the Qt5 refactoring isn't
working correctly. Once Qt have its own QML components for popup, we hope to use
it as a default if no other popupMenu is specified.

* Target.pri:
* UIProcess/API/qt/qquickwebview.cpp:
(QQuickWebViewPrivate::QQuickWebViewPrivate):
(QQuickWebViewExperimental::itemSelector):
(QQuickWebViewExperimental::setItemSelector):
* UIProcess/API/qt/qquickwebview_p.h:
* UIProcess/API/qt/qquickwebview_p_p.h:
(QQuickWebViewPrivate::get):
* UIProcess/API/qt/tests/qmltests/WebView/tst_itemSelector.qml: Added.
* UIProcess/API/qt/tests/qmltests/common/select.html: Added.
* UIProcess/API/qt/tests/qmltests/qmltests.pro:
* UIProcess/qt/QtPageClient.cpp:
* UIProcess/qt/QtWebPageProxy.cpp:
(QtWebPageProxy::createPopupMenuProxy):
* UIProcess/qt/WebPopupMenuProxyQt.cpp:
(WebKit::PopupMenuItemModel::rowCount):
(WebKit::PopupMenuItemModel::Item::Item):
(WebKit::ItemSelectorContextObject::elementRect):
(WebKit::ItemSelectorContextObject::items):
(WebKit::ItemSelectorContextObject::reject):
(WebKit::ItemSelectorContextObject::ItemSelectorContextObject):
(WebKit::ItemSelectorContextObject::accept):
(WebKit::createRoleNamesHash):
(WebKit::PopupMenuItemModel::PopupMenuItemModel):
(WebKit::PopupMenuItemModel::data):
(WebKit::PopupMenuItemModel::select):
(WebKit::PopupMenuItemModel::selectedOriginalIndex):
(WebKit::PopupMenuItemModel::buildItems):
(WebKit::WebPopupMenuProxyQt::WebPopupMenuProxyQt):
(WebKit::WebPopupMenuProxyQt::showPopupMenu):
(WebKit::WebPopupMenuProxyQt::hidePopupMenu):
(WebKit::WebPopupMenuProxyQt::selectIndex):
(WebKit::WebPopupMenuProxyQt::createItem):
(WebKit::WebPopupMenuProxyQt::createContext):
(WebKit::WebPopupMenuProxyQt::notifyValueChanged):
* UIProcess/qt/WebPopupMenuProxyQt.h:
(WebKit::WebPopupMenuProxyQt::create):
* UIProcess/qt/WebPopupMenuProxyQtDesktop.cpp: Removed.
* UIProcess/qt/WebPopupMenuProxyQtDesktop.h: Removed.

Tools:

Add an Item Selector to our WebView using the experimental API.

* MiniBrowser/qt/MiniBrowser.pro:
* MiniBrowser/qt/MiniBrowser.qrc:
* MiniBrowser/qt/qml/BrowserWindow.qml:
* MiniBrowser/qt/qml/ItemSelector.qml: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@102573 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
Source/WebKit2/ChangeLog
Source/WebKit2/Target.pri
Source/WebKit2/UIProcess/API/qt/qquickwebview.cpp
Source/WebKit2/UIProcess/API/qt/qquickwebview_p.h
Source/WebKit2/UIProcess/API/qt/qquickwebview_p_p.h
Source/WebKit2/UIProcess/API/qt/tests/qmltests/WebView/tst_itemSelector.qml [new file with mode: 0644]
Source/WebKit2/UIProcess/API/qt/tests/qmltests/common/select.html [new file with mode: 0644]
Source/WebKit2/UIProcess/API/qt/tests/qmltests/qmltests.pro
Source/WebKit2/UIProcess/qt/QtPageClient.cpp
Source/WebKit2/UIProcess/qt/QtWebPageProxy.cpp
Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQt.cpp
Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQt.h
Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQtDesktop.cpp [deleted file]
Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQtDesktop.h [deleted file]
Tools/ChangeLog
Tools/MiniBrowser/qt/MiniBrowser.pro
Tools/MiniBrowser/qt/MiniBrowser.qrc
Tools/MiniBrowser/qt/qml/BrowserWindow.qml
Tools/MiniBrowser/qt/qml/ItemSelector.qml [new file with mode: 0644]

index 18b3081..5e9dfdd 100644 (file)
@@ -1,3 +1,67 @@
+2011-12-12  Caio Marcelo de Oliveira Filho  <caio.oliveira@openbossa.org>
+
+        [Qt] [WK2] Support customizing popup menus with QML
+        https://bugs.webkit.org/show_bug.cgi?id=73560
+
+        Reviewed by Tor Arne Vestbø.
+
+        Add a new property 'itemSelector' to WebView (experimental for now) that contains
+        the QML component used when it needs to spawn a popup menu. For example, <select>
+        HTML tag may trigger a popup menu.
+
+        When loaded the component will have the 'model' available in its context with two
+        properties: 'elementRect', describing the position of the element which spawned
+        the item selector, and 'items', which is a model ready to be used by ListView. The
+        'model' also have methods to accept/reject the selection.
+
+        Option groups are available as a property for each row in the 'items' model. This
+        can be used together with ListView to create sections, as demonstrated in the
+        MiniBrowser. QML tests were added as well.
+
+        The existing Desktop version is removed since after the Qt5 refactoring isn't
+        working correctly. Once Qt have its own QML components for popup, we hope to use
+        it as a default if no other popupMenu is specified.
+
+        * Target.pri:
+        * UIProcess/API/qt/qquickwebview.cpp:
+        (QQuickWebViewPrivate::QQuickWebViewPrivate):
+        (QQuickWebViewExperimental::itemSelector):
+        (QQuickWebViewExperimental::setItemSelector):
+        * UIProcess/API/qt/qquickwebview_p.h:
+        * UIProcess/API/qt/qquickwebview_p_p.h:
+        (QQuickWebViewPrivate::get):
+        * UIProcess/API/qt/tests/qmltests/WebView/tst_itemSelector.qml: Added.
+        * UIProcess/API/qt/tests/qmltests/common/select.html: Added.
+        * UIProcess/API/qt/tests/qmltests/qmltests.pro:
+        * UIProcess/qt/QtPageClient.cpp:
+        * UIProcess/qt/QtWebPageProxy.cpp:
+        (QtWebPageProxy::createPopupMenuProxy):
+        * UIProcess/qt/WebPopupMenuProxyQt.cpp:
+        (WebKit::PopupMenuItemModel::rowCount):
+        (WebKit::PopupMenuItemModel::Item::Item):
+        (WebKit::ItemSelectorContextObject::elementRect):
+        (WebKit::ItemSelectorContextObject::items):
+        (WebKit::ItemSelectorContextObject::reject):
+        (WebKit::ItemSelectorContextObject::ItemSelectorContextObject):
+        (WebKit::ItemSelectorContextObject::accept):
+        (WebKit::createRoleNamesHash):
+        (WebKit::PopupMenuItemModel::PopupMenuItemModel):
+        (WebKit::PopupMenuItemModel::data):
+        (WebKit::PopupMenuItemModel::select):
+        (WebKit::PopupMenuItemModel::selectedOriginalIndex):
+        (WebKit::PopupMenuItemModel::buildItems):
+        (WebKit::WebPopupMenuProxyQt::WebPopupMenuProxyQt):
+        (WebKit::WebPopupMenuProxyQt::showPopupMenu):
+        (WebKit::WebPopupMenuProxyQt::hidePopupMenu):
+        (WebKit::WebPopupMenuProxyQt::selectIndex):
+        (WebKit::WebPopupMenuProxyQt::createItem):
+        (WebKit::WebPopupMenuProxyQt::createContext):
+        (WebKit::WebPopupMenuProxyQt::notifyValueChanged):
+        * UIProcess/qt/WebPopupMenuProxyQt.h:
+        (WebKit::WebPopupMenuProxyQt::create):
+        * UIProcess/qt/WebPopupMenuProxyQtDesktop.cpp: Removed.
+        * UIProcess/qt/WebPopupMenuProxyQtDesktop.h: Removed.
+
 2011-12-11  Gopal Raghavan  <gopal.1.raghavan@nokia.com>
 
         [Qt] QQuickWebView missing titleChanged signal tests
index be2813b..707cf09 100644 (file)
@@ -273,7 +273,6 @@ HEADERS += \
     UIProcess/qt/WebContextMenuProxyQt.h \
     UIProcess/qt/WebGeolocationProviderQt.h \
     UIProcess/qt/WebPopupMenuProxyQt.h \
-    UIProcess/qt/WebPopupMenuProxyQtDesktop.h \
     WebProcess/ApplicationCache/WebApplicationCacheManager.h \
     WebProcess/Authentication/AuthenticationManager.h \
     WebProcess/Cookies/WebCookieManager.h \
@@ -600,7 +599,6 @@ SOURCES += \
     UIProcess/qt/WebInspectorProxyQt.cpp \
     UIProcess/qt/WebPageProxyQt.cpp \
     UIProcess/qt/WebPopupMenuProxyQt.cpp \
-    UIProcess/qt/WebPopupMenuProxyQtDesktop.cpp \
     UIProcess/qt/WebPreferencesQt.cpp \
     WebProcess/ApplicationCache/WebApplicationCacheManager.cpp \
     WebProcess/Authentication/AuthenticationManager.cpp \
index 3086042..a2fa93b 100644 (file)
@@ -42,6 +42,7 @@ QQuickWebViewPrivate::QQuickWebViewPrivate(QQuickWebView* viewport, WKContextRef
     , alertDialog(0)
     , confirmDialog(0)
     , promptDialog(0)
+    , itemSelector(0)
     , postTransitionState(adoptPtr(new PostTransitionState(this)))
     , isTransitioningToNewPage(false)
     , pageIsSuspended(false)
@@ -494,6 +495,21 @@ void QQuickWebViewExperimental::setPromptDialog(QDeclarativeComponent* promptDia
     emit promptDialogChanged();
 }
 
+QDeclarativeComponent* QQuickWebViewExperimental::itemSelector() const
+{
+    Q_D(const QQuickWebView);
+    return d->itemSelector;
+}
+
+void QQuickWebViewExperimental::setItemSelector(QDeclarativeComponent* itemSelector)
+{
+    Q_D(QQuickWebView);
+    if (d->itemSelector == itemSelector)
+        return;
+    d->itemSelector = itemSelector;
+    emit itemSelectorChanged();
+}
+
 bool QQuickWebViewExperimental::useTraditionalDesktopBehaviour() const
 {
     Q_D(const QQuickWebView);
index 099504f..0d2a3b2 100644 (file)
@@ -172,6 +172,7 @@ class QWEBKIT_EXPORT QQuickWebViewExperimental : public QObject {
     Q_PROPERTY(QDeclarativeComponent* alertDialog READ alertDialog WRITE setAlertDialog NOTIFY alertDialogChanged)
     Q_PROPERTY(QDeclarativeComponent* confirmDialog READ confirmDialog WRITE setConfirmDialog NOTIFY confirmDialogChanged)
     Q_PROPERTY(QDeclarativeComponent* promptDialog READ promptDialog WRITE setPromptDialog NOTIFY promptDialogChanged)
+    Q_PROPERTY(QDeclarativeComponent* itemSelector READ itemSelector WRITE setItemSelector NOTIFY itemSelectorChanged)
     Q_PROPERTY(bool useTraditionalDesktopBehaviour READ useTraditionalDesktopBehaviour WRITE setUseTraditionalDesktopBehaviour)
 
 public:
@@ -184,6 +185,8 @@ public:
     void setConfirmDialog(QDeclarativeComponent*);
     QDeclarativeComponent* promptDialog() const;
     void setPromptDialog(QDeclarativeComponent*);
+    QDeclarativeComponent* itemSelector() const;
+    void setItemSelector(QDeclarativeComponent*);
 
     bool useTraditionalDesktopBehaviour() const;
 
@@ -194,6 +197,7 @@ Q_SIGNALS:
     void alertDialogChanged();
     void confirmDialogChanged();
     void promptDialogChanged();
+    void itemSelectorChanged();
     void downloadRequested(QWebDownloadItem* downloadItem);
     void permissionRequested(QWebPermissionRequest* permission);
 
index 634e802..75804e8 100644 (file)
@@ -48,6 +48,8 @@ class QQuickWebViewPrivate {
     friend class QQuickWebViewExperimental;
 
 public:
+    static QQuickWebViewPrivate* get(QQuickWebView* q) { return q->d_ptr.data(); }
+
     QQuickWebViewPrivate(QQuickWebView* viewport, WKContextRef contextRef = 0, WKPageGroupRef pageGroupRef = 0);
     virtual ~QQuickWebViewPrivate(); 
     void setPageProxy(QtWebPageProxy*);
@@ -81,6 +83,7 @@ public:
     QString runJavaScriptPrompt(const QString&, const QString& defaultValue, bool& ok);
 
     void setUseTraditionalDesktopBehaviour(bool enable);
+    void setViewInAttachedProperties(QObject*);
 
 private:
     // This class is responsible for collecting and applying all properties
@@ -109,8 +112,6 @@ private:
         QSize contentsSize;
     };
 
-    void setViewInAttachedProperties(QObject*);
-
     QScopedPointer<QtPageClient> pageClient;
     QScopedPointer<QtWebPageEventHandler> eventHandler;
 
@@ -127,6 +128,7 @@ private:
     QDeclarativeComponent* alertDialog;
     QDeclarativeComponent* confirmDialog;
     QDeclarativeComponent* promptDialog;
+    QDeclarativeComponent* itemSelector;
 
     WebCore::ViewportArguments viewportArguments;
     OwnPtr<PostTransitionState> postTransitionState;
diff --git a/Source/WebKit2/UIProcess/API/qt/tests/qmltests/WebView/tst_itemSelector.qml b/Source/WebKit2/UIProcess/API/qt/tests/qmltests/WebView/tst_itemSelector.qml
new file mode 100644 (file)
index 0000000..03d68be
--- /dev/null
@@ -0,0 +1,119 @@
+import QtQuick 2.0
+import QtTest 1.0
+import QtWebKit 3.0
+import QtWebKit.experimental 3.0
+
+// FIXME: used because we want to have mouseClick() to open the <select> tag. We can remove this
+// when TestCase starts supporting touch events, see https://bugreports.qt.nokia.com/browse/QTBUG-23083.
+import "../DesktopBehavior"
+
+DesktopWebView {
+    id: webView
+
+    width: 400
+    height: 400
+
+    property int initialSelection
+    property int finalSelection
+    property bool useAcceptDirectly
+    property bool selectorLoaded
+
+    experimental.itemSelector: Item {
+        Component.onCompleted: {
+            if (WebView.view.initialSelection != -1)
+                model.items.select(WebView.view.initialSelection)
+
+            if (WebView.view.finalSelection == -1)
+                model.reject()
+            else {
+                if (useAcceptDirectly) {
+                    model.accept(WebView.view.finalSelection)
+                } else {
+                    model.items.select(WebView.view.finalSelection)
+                    model.accept()
+                }
+            }
+
+            WebView.view.selectorLoaded = true
+        }
+    }
+
+    SignalSpy {
+        id: loadSpy
+        target: webView
+        signalName: "loadSucceeded"
+    }
+
+    SignalSpy {
+        id: titleSpy
+        target: webView
+        signalName: "titleChanged"
+    }
+
+    TestCase {
+        id: test
+        name: "WebViewItemSelector"
+        when: windowShown
+
+        function init() {
+            webView.initialSelection = -1
+            webView.finalSelection = -1
+            webView.useAcceptDirectly = false
+            webView.selectorLoaded = false
+            loadSpy.clear()
+            webView.load(Qt.resolvedUrl("../common/select.html"))
+            loadSpy.wait()
+            titleSpy.clear()
+        }
+
+        function openItemSelector() {
+            mouseClick(webView, 15, 15, Qt.LeftButton)
+        }
+
+        function test_accept() {
+            webView.finalSelection = 1
+            openItemSelector()
+            titleSpy.wait()
+            compare(webView.title, "__closed__")
+        }
+
+        function test_acceptDirectly() {
+            webView.finalSelection = 1
+            webView.useAcceptDirectly = true
+            openItemSelector()
+            titleSpy.wait()
+            compare(webView.title, "__closed__")
+        }
+
+        function test_selectFirstThenAccept() {
+            webView.initialSelection = 1
+            webView.finalSelection = 2
+            openItemSelector()
+            titleSpy.wait()
+            compare(webView.title, "__all__")
+        }
+
+        function test_selectFirstThenAcceptDirectly() {
+            webView.initialSelection = 1
+            webView.finalSelection = 2
+            webView.useAcceptDirectly = true
+            openItemSelector()
+            titleSpy.wait()
+            compare(webView.title, "__all__")
+        }
+
+        function test_reject() {
+            openItemSelector()
+            tryCompare(webView, "selectorLoaded", true)
+            compare(webView.title, "No new selection was made")
+        }
+
+        function test_selectFirstThenReject() {
+            webView.initialSelection = 1
+            webView.finalSelection = -1
+            openItemSelector()
+            tryCompare(webView, "selectorLoaded", true)
+            compare(webView.title, "No new selection was made")
+        }
+    }
+}
diff --git a/Source/WebKit2/UIProcess/API/qt/tests/qmltests/common/select.html b/Source/WebKit2/UIProcess/API/qt/tests/qmltests/common/select.html
new file mode 100644 (file)
index 0000000..46080f6
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>No new selection was made</title>
+<script>
+function updateTitle(selectElement) {
+    var index = selectElement.selectedIndex;
+    document.title = selectElement.options[index].value;
+}
+</script>
+</head>
+<body>
+<select onchange="updateTitle(this)">
+<option value="__open__" >Open</option>
+<option value="__closed__" >Closed</option>
+<option value="__all__" >All</option>
+</select>
+</html>
index ace6357..5c61df2 100644 (file)
@@ -23,6 +23,7 @@ OTHER_FILES += \
     DesktopBehavior/tst_navigationRequested.qml \
     WebView/tst_download.qml \
     WebView/tst_geopermission.qml \
+    WebView/tst_itemSelector.qml \
     WebView/tst_javaScriptDialogs.qml \
     WebView/tst_loadFail.qml \
     WebView/tst_loadHtml.qml \
index 2d4644b..e6faf06 100644 (file)
@@ -26,7 +26,6 @@
 #include "QtWebUndoCommand.h"
 #include "WebContextMenuProxyQt.h"
 #include "WebEditCommandProxy.h"
-#include "WebPopupMenuProxyQtDesktop.h"
 #include <QGuiApplication>
 #include <QUndoStack>
 #include <WebCore/Cursor.h>
index 036011e..a6fd7b3 100644 (file)
@@ -44,7 +44,7 @@
 #include "WebContext.h"
 #include "WebContextMenuProxyQt.h"
 #include "WebEditCommandProxy.h"
-#include "WebPopupMenuProxyQtDesktop.h"
+#include "WebPopupMenuProxyQt.h"
 #include "WKStringQt.h"
 #include "WKURLQt.h"
 #include <QDrag>
@@ -184,7 +184,7 @@ void QtWebPageProxy::selectionChanged(bool, bool, bool, bool)
 
 PassRefPtr<WebPopupMenuProxy> QtWebPageProxy::createPopupMenuProxy(WebPageProxy*)
 {
-    return WebPopupMenuProxyQtDesktop::create(m_webPageProxy.get(), m_qmlWebView);
+    return WebPopupMenuProxyQt::create(m_webPageProxy.get(), m_qmlWebView);
 }
 
 WKPageRef QtWebPageProxy::pageRef() const
index 1e97d74..4c0dead 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 #include "PlatformPopupMenuData.h"
 #include "WebPopupItem.h"
+#include "qquickwebview_p.h"
+#include "qquickwebview_p_p.h"
+#include <QtCore/QAbstractListModel>
+#include <QtDeclarative/QDeclarativeContext>
+#include <QtDeclarative/QDeclarativeEngine>
 
 using namespace WebCore;
 
 namespace WebKit {
 
-WebPopupMenuProxyQt::WebPopupMenuProxyQt()
-    : WebPopupMenuProxy(0)
+static QHash<int, QByteArray> createRoleNamesHash();
+
+class PopupMenuItemModel : public QAbstractListModel {
+    Q_OBJECT
+
+public:
+    enum Roles {
+        GroupRole = Qt::UserRole,
+        EnabledRole = Qt::UserRole + 1,
+        SelectedRole = Qt::UserRole + 2,
+        IsSeparatorRole = Qt::UserRole + 3
+    };
+
+    PopupMenuItemModel(const Vector<WebPopupItem>&, int selectedOriginalIndex);
+    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const { return m_items.size(); }
+    virtual QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const;
+
+    Q_INVOKABLE void select(int);
+
+    int selectedOriginalIndex() const;
+
+private:
+    struct Item {
+        Item(const WebPopupItem& webPopupItem, const QString& group, int originalIndex, bool selected)
+            : text(webPopupItem.m_text)
+            , toolTip(webPopupItem.m_toolTip)
+            , group(group)
+            , originalIndex(originalIndex)
+            , enabled(webPopupItem.m_isEnabled)
+            , selected(selected)
+            , isSeparator(webPopupItem.m_type == WebPopupItem::Separator)
+        { }
+
+        QString text;
+        QString toolTip;
+        QString group;
+        // Keep track of originalIndex because we don't add the label (group) items to our vector.
+        int originalIndex;
+        bool enabled;
+        bool selected;
+        bool isSeparator;
+    };
+
+    void buildItems(const Vector<WebPopupItem>& webPopupItems, int selectedOriginalIndex);
+
+    Vector<Item> m_items;
+    int m_selectedModelIndex;
+};
+
+class ItemSelectorContextObject : public QObject {
+    Q_OBJECT
+    Q_PROPERTY(QRect elementRect READ elementRect CONSTANT FINAL)
+    Q_PROPERTY(QObject* items READ items CONSTANT FINAL)
+
+public:
+    ItemSelectorContextObject(const IntRect& elementRect, const Vector<WebPopupItem>&, int selectedIndex);
+
+    QRect elementRect() const { return m_elementRect; }
+    PopupMenuItemModel* items() { return &m_items; }
+
+    Q_INVOKABLE void accept(int index = -1);
+    Q_INVOKABLE void reject() { emit rejected(); }
+
+Q_SIGNALS:
+    void acceptedWithOriginalIndex(int);
+    void rejected();
+
+private:
+    QRect m_elementRect;
+    PopupMenuItemModel m_items;
+};
+
+ItemSelectorContextObject::ItemSelectorContextObject(const IntRect& elementRect, const Vector<WebPopupItem>& webPopupItems, int selectedIndex)
+    : m_elementRect(elementRect)
+    , m_items(webPopupItems, selectedIndex)
+{
+}
+
+void ItemSelectorContextObject::accept(int index)
+{
+    if (index != -1)
+        m_items.select(index);
+    int originalIndex = m_items.selectedOriginalIndex();
+    emit acceptedWithOriginalIndex(originalIndex);
+}
+
+static QHash<int, QByteArray> createRoleNamesHash()
+{
+    QHash<int, QByteArray> roles;
+    roles[Qt::DisplayRole] = "text";
+    roles[Qt::ToolTipRole] = "tooltip";
+    roles[PopupMenuItemModel::GroupRole] = "group";
+    roles[PopupMenuItemModel::EnabledRole] = "enabled";
+    roles[PopupMenuItemModel::SelectedRole] = "selected";
+    roles[PopupMenuItemModel::IsSeparatorRole] = "isSeparator";
+    return roles;
+}
+
+PopupMenuItemModel::PopupMenuItemModel(const Vector<WebPopupItem>& webPopupItems, int selectedOriginalIndex)
+    : m_selectedModelIndex(-1)
+{
+    static QHash<int, QByteArray> roles = createRoleNamesHash();
+    setRoleNames(roles);
+    buildItems(webPopupItems, selectedOriginalIndex);
+}
+
+QVariant PopupMenuItemModel::data(const QModelIndex& index, int role) const
+{
+    if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
+        return QVariant();
+
+    const Item& item = m_items[index.row()];
+    if (item.isSeparator) {
+        if (role == IsSeparatorRole)
+            return true;
+        return QVariant();
+    }
+
+    switch (role) {
+    case Qt::DisplayRole:
+        return item.text;
+    case Qt::ToolTipRole:
+        return item.toolTip;
+    case GroupRole:
+        return item.group;
+    case EnabledRole:
+        return item.enabled;
+    case SelectedRole:
+        return item.selected;
+    case IsSeparatorRole:
+        return false;
+    }
+
+    return QVariant();
+}
+
+void PopupMenuItemModel::select(int index)
+{
+    int oldIndex = m_selectedModelIndex;
+    if (index == oldIndex)
+        return;
+    if (index < 0 || index >= m_items.size())
+        return;
+    Item& item = m_items[index];
+    if (!item.enabled)
+        return;
+
+    Item& oldItem = m_items[oldIndex];
+    oldItem.selected = false;
+    item.selected = true;
+    m_selectedModelIndex = index;
+
+    emit dataChanged(this->index(oldIndex), this->index(oldIndex));
+    emit dataChanged(this->index(index), this->index(index));
+}
+
+int PopupMenuItemModel::selectedOriginalIndex() const
+{
+    if (m_selectedModelIndex == -1)
+        return -1;
+    return m_items[m_selectedModelIndex].originalIndex;
+}
+
+void PopupMenuItemModel::buildItems(const Vector<WebPopupItem>& webPopupItems, int selectedOriginalIndex)
+{
+    QString currentGroup;
+    m_items.reserveInitialCapacity(webPopupItems.size());
+    for (int i = 0; i < webPopupItems.size(); i++) {
+        const WebPopupItem& webPopupItem = webPopupItems[i];
+        if (webPopupItem.m_isLabel) {
+            currentGroup = webPopupItem.m_text;
+            continue;
+        }
+        const bool selected = i == selectedOriginalIndex;
+        if (selected)
+            m_selectedModelIndex = m_items.size();
+        m_items.append(Item(webPopupItem, currentGroup, i, selected));
+    }
+}
+
+WebPopupMenuProxyQt::WebPopupMenuProxyQt(WebPopupMenuProxy::Client* client, QQuickWebView* webView)
+    : WebPopupMenuProxy(client)
+    , m_webView(webView)
 {
 }
 
@@ -44,10 +231,86 @@ WebPopupMenuProxyQt::~WebPopupMenuProxyQt()
 
 void WebPopupMenuProxyQt::showPopupMenu(const IntRect& rect, WebCore::TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex)
 {
+    m_selectedIndex = selectedIndex;
+
+    ItemSelectorContextObject* contextObject = new ItemSelectorContextObject(rect, items, m_selectedIndex);
+    createItem(contextObject);
+    if (!m_itemSelector) {
+        notifyValueChanged();
+        return;
+    }
 }
 
 void WebPopupMenuProxyQt::hidePopupMenu()
 {
+    m_itemSelector.clear();
+    m_context.clear();
+    notifyValueChanged();
+}
+
+void WebPopupMenuProxyQt::selectIndex(int index)
+{
+    m_selectedIndex = index;
+}
+
+void WebPopupMenuProxyQt::createItem(QObject* contextObject)
+{
+    QDeclarativeComponent* component = m_webView->experimental()->itemSelector();
+    if (!component) {
+        delete contextObject;
+        return;
+    }
+
+    createContext(component, contextObject);
+    QObject* object = component->beginCreate(m_context.get());
+    if (!object) {
+        m_context.clear();
+        return;
+    }
+
+    m_itemSelector = adoptPtr(qobject_cast<QQuickItem*>(object));
+    if (!m_itemSelector) {
+        m_context.clear();
+        m_itemSelector.clear();
+        return;
+    }
+
+    connect(contextObject, SIGNAL(acceptedWithOriginalIndex(int)), SLOT(selectIndex(int)));
+
+    // We enqueue these because they are triggered by m_itemSelector and will lead to its destruction.
+    connect(contextObject, SIGNAL(acceptedWithOriginalIndex(int)), SLOT(hidePopupMenu()), Qt::QueuedConnection);
+    connect(contextObject, SIGNAL(rejected()), SLOT(hidePopupMenu()), Qt::QueuedConnection);
+
+    QQuickWebViewPrivate::get(m_webView)->setViewInAttachedProperties(m_itemSelector.get());
+    component->completeCreate();
+
+    m_itemSelector->setParentItem(m_webView);
+}
+
+void WebPopupMenuProxyQt::createContext(QDeclarativeComponent* component, QObject* contextObject)
+{
+    QDeclarativeContext* baseContext = component->creationContext();
+    if (!baseContext)
+        baseContext = QDeclarativeEngine::contextForObject(m_webView);
+    m_context = adoptPtr(new QDeclarativeContext(baseContext));
+
+    contextObject->setParent(m_context.get());
+    m_context->setContextProperty(QLatin1String("model"), contextObject);
+    m_context->setContextObject(contextObject);
+}
+
+void WebPopupMenuProxyQt::notifyValueChanged()
+{
+    if (m_client) {
+        m_client->valueChangedForPopupMenu(this, m_selectedIndex);
+        invalidate();
+    }
 }
 
 } // namespace WebKit
+
+// Since we define QObjects in WebPopupMenuProxyQt.cpp, this will trigger moc to run on .cpp.
+#include "WebPopupMenuProxyQt.moc"
+
+// And we can't compile the moc for WebPopupMenuProxyQt.h by itself, since it doesn't include "config.h"
+#include "moc_WebPopupMenuProxyQt.cpp"
index 9230deb..ea22bbd 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 #include "WebPopupMenuProxy.h"
 
+#include <QtCore/QObject>
+#include <wtf/OwnPtr.h>
+
+class QDeclarativeComponent;
+class QDeclarativeContext;
+class QQuickWebView;
+class QQuickItem;
+
 namespace WebKit {
 
-class WebPopupMenuProxyQt : public WebPopupMenuProxy {
+class WebPopupMenuProxyQt : public QObject, public WebPopupMenuProxy {
+    Q_OBJECT
+
 public:
-    static PassRefPtr<WebPopupMenuProxyQt> create()
+    static PassRefPtr<WebPopupMenuProxyQt> create(WebPopupMenuProxy::Client* client, QQuickWebView* webView)
     {
-        return adoptRef(new WebPopupMenuProxyQt());
+        return adoptRef(new WebPopupMenuProxyQt(client, webView));
     }
     ~WebPopupMenuProxyQt();
 
     virtual void showPopupMenu(const WebCore::IntRect&, WebCore::TextDirection, double pageScaleFactor, const Vector<WebPopupItem>&, const PlatformPopupMenuData&, int32_t selectedIndex);
+
+public Q_SLOTS:
     virtual void hidePopupMenu();
 
+private Q_SLOTS:
+    void selectIndex(int);
+
 private:
-    WebPopupMenuProxyQt();
+    WebPopupMenuProxyQt(WebPopupMenuProxy::Client*, QQuickWebView*);
+    void createItem(QObject*);
+    void createContext(QDeclarativeComponent*, QObject*);
+
+    void notifyValueChanged();
+
+    OwnPtr<QDeclarativeContext> m_context;
+    OwnPtr<QQuickItem> m_itemSelector;
+
+    QQuickWebView* m_webView;
+    int32_t m_selectedIndex;
 };
 
 } // namespace WebKit
diff --git a/Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQtDesktop.cpp b/Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQtDesktop.cpp
deleted file mode 100644 (file)
index e911a9f..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "config.h"
-#include "WebPopupMenuProxyQtDesktop.h"
-
-#include "PlatformPopupMenuData.h"
-#include <QAbstractItemView>
-#include <QCoreApplication>
-#include <QtQuick/QQuickCanvas>
-#include <QtQuick/QQuickItem>
-#include <QMouseEvent>
-#include <QStandardItemModel>
-#include "WebPopupItem.h"
-#include <wtf/CurrentTime.h>
-
-using namespace WebCore;
-
-namespace WebKit {
-
-WebPopupMenuProxyQtDesktop::WebPopupMenuProxyQtDesktop(WebPopupMenuProxy::Client* client, QQuickItem* webViewItem)
-    : WebPopupMenuProxy(client)
-    , m_webViewItem(webViewItem)
-    , m_selectedIndex(-1)
-{
-    window()->winId(); // Ensure that the combobox has a window
-    Q_ASSERT(window()->windowHandle());
-    window()->windowHandle()->setTransientParent(m_webViewItem->canvas());
-
-    connect(this, SIGNAL(activated(int)), SLOT(setSelectedIndex(int)));
-    // Install an event filter on the view inside the combo box popup to make sure we know
-    // when the popup got closed. E.g. QComboBox::hidePopup() won't be called when the popup
-    // is closed by a mouse wheel event outside its window.
-    view()->installEventFilter(this);
-}
-
-WebPopupMenuProxyQtDesktop::~WebPopupMenuProxyQtDesktop()
-{
-}
-
-void WebPopupMenuProxyQtDesktop::showPopupMenu(const IntRect& rect, WebCore::TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex)
-{
-    m_selectedIndex = selectedIndex;
-    populate(items);
-    setCurrentIndex(selectedIndex);
-    setGeometry(m_webViewItem->mapRectToScene(QRect(rect)).toRect());
-
-    QMouseEvent event(QEvent::MouseButtonPress, QCursor::pos(), Qt::LeftButton,
-                      Qt::LeftButton, Qt::NoModifier);
-    event.setTimestamp(static_cast<qint64>(WTF::currentTimeMS()));
-    QCoreApplication::sendEvent(this, &event);
-}
-
-void WebPopupMenuProxyQtDesktop::hidePopupMenu()
-{
-    hidePopup();
-}
-
-bool WebPopupMenuProxyQtDesktop::eventFilter(QObject *watched, QEvent *event)
-{
-    Q_ASSERT(watched == view());
-    if (event->type() == QEvent::Hide) {
-        if (m_client)
-            m_client->valueChangedForPopupMenu(this, m_selectedIndex);
-    }
-    return false;
-}
-
-void WebPopupMenuProxyQtDesktop::setSelectedIndex(int index)
-{
-    m_selectedIndex = index;
-}
-
-void WebPopupMenuProxyQtDesktop::populate(const Vector<WebPopupItem>& items)
-{
-    clear();
-
-    QStandardItemModel* model = qobject_cast<QStandardItemModel*>(this->model());
-    Q_ASSERT(model);
-
-    for (size_t i = 0; i < items.size(); ++i) {
-        const WebPopupItem& item = items.at(i);
-        if (item.m_type == WebPopupItem::Separator) {
-            insertSeparator(i);
-            continue;
-        }
-        insertItem(i, item.m_text);
-        model->item(i)->setToolTip(item.m_toolTip);
-        model->item(i)->setEnabled(item.m_isEnabled);
-    }
-}
-
-#include "moc_WebPopupMenuProxyQtDesktop.cpp"
-
-} // namespace WebKit
diff --git a/Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQtDesktop.h b/Source/WebKit2/UIProcess/qt/WebPopupMenuProxyQtDesktop.h
deleted file mode 100644 (file)
index 5d97817..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef WebPopupMenuProxyQtDesktop_h
-#define WebPopupMenuProxyQtDesktop_h
-
-#include "WebPopupMenuProxy.h"
-#include <QComboBox>
-#include <QObject>
-#include <QWeakPointer>
-
-class QQuickItem;
-
-namespace WebCore {
-class QtWebComboBox;
-}
-
-namespace WebKit {
-
-class WebPopupMenuProxyQtDesktop : public QComboBox, public WebPopupMenuProxy {
-    Q_OBJECT
-
-public:
-    virtual ~WebPopupMenuProxyQtDesktop();
-
-    static PassRefPtr<WebPopupMenuProxyQtDesktop> create(WebPopupMenuProxy::Client* client, QQuickItem* webViewItem)
-    {
-        return adoptRef(new WebPopupMenuProxyQtDesktop(client, webViewItem));
-    }
-
-    virtual void showPopupMenu(const WebCore::IntRect&, WebCore::TextDirection, double pageScaleFactor, const Vector<WebPopupItem>&, const PlatformPopupMenuData&, int32_t selectedIndex);
-    virtual void hidePopupMenu();
-
-    virtual bool eventFilter(QObject* watched, QEvent*);
-
-private Q_SLOTS:
-    void setSelectedIndex(int);
-
-private:
-    WebPopupMenuProxyQtDesktop(WebPopupMenuProxy::Client*, QQuickItem* webViewItem);
-    void populate(const Vector<WebPopupItem>&);
-
-    QQuickItem* m_webViewItem;
-    int32_t m_selectedIndex;
-};
-
-} // namespace WebKit
-
-#endif // WebPopupMenuProxyQtDesktop_h
index 029d682..19dc48b 100644 (file)
@@ -1,3 +1,17 @@
+2011-12-12  Caio Marcelo de Oliveira Filho  <caio.oliveira@openbossa.org>
+
+        [Qt] [WK2] Support customizing popup menus with QML
+        https://bugs.webkit.org/show_bug.cgi?id=73560
+
+        Reviewed by Tor Arne Vestbø.
+
+        Add an Item Selector to our WebView using the experimental API.
+
+        * MiniBrowser/qt/MiniBrowser.pro:
+        * MiniBrowser/qt/MiniBrowser.qrc:
+        * MiniBrowser/qt/qml/BrowserWindow.qml:
+        * MiniBrowser/qt/qml/ItemSelector.qml: Added.
+
 2011-12-12  Alexander Færøy  <alexander.faeroy@nokia.com>
 
         [Qt] MiniBrowser should only visualize mock touch points when in non-desktop mode
index 6678c11..8237217 100644 (file)
@@ -30,4 +30,5 @@ macx: QT += xml
 RESOURCES += MiniBrowser.qrc
 
 OTHER_FILES += \
-    qml/BrowserWindow.qml
+    qml/BrowserWindow.qml \
+    qml/ItemSelector.qml
index 7f05526..b7ebf45 100644 (file)
@@ -6,6 +6,7 @@
         <file>icons/stop.png</file>
         <file>icons/touchpoint.png</file>
         <file>qml/BrowserWindow.qml</file>
+        <file>qml/ItemSelector.qml</file>
         <file>qml/MockTouchPoint.qml</file>
         <file>useragentlist.txt</file>
     </qresource>
index 0480a26..a221553 100644 (file)
@@ -28,6 +28,7 @@
 
 import QtQuick 2.0
 import QtWebKit 3.0
+import QtWebKit.experimental 3.0
 
 Rectangle {
     // Do not define anchors or an initial size here! This would mess up with QSGView::SizeRootObjectToView.
@@ -230,6 +231,8 @@ Rectangle {
                 console.log("Loaded:", webView.url);
             forceActiveFocus();
         }
+
+        experimental.itemSelector: ItemSelector { }
     }
 
     Keys.onPressed: {
diff --git a/Tools/MiniBrowser/qt/qml/ItemSelector.qml b/Tools/MiniBrowser/qt/qml/ItemSelector.qml
new file mode 100644 (file)
index 0000000..43022b6
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import QtQuick 2.0
+
+MouseArea {
+    // To avoid conflicting with ListView.model when inside ListView context.
+    property QtObject selectorModel: model
+    anchors.fill: parent
+    onClicked: selectorModel.reject()
+
+    Rectangle {
+        clip: true
+        x: selectorModel.elementRect.x
+        y: selectorModel.elementRect.y + selectorModel.elementRect.height
+        height: Math.min(400, parent.height - y)
+        width: 200
+        radius: 5
+        color: "gainsboro"
+        opacity: 0.8
+
+        ListView {
+            anchors.fill: parent
+            anchors.margins: 10
+            spacing: 5
+            model: selectorModel.items
+
+            delegate: Rectangle {
+                color: model.selected ? "gold" : "silver"
+                height: 50
+                width: parent.width
+
+                Text {
+                    anchors.centerIn: parent
+                    text: model.text
+                    color: model.enabled ? "black" : "gainsboro"
+                }
+
+                MouseArea {
+                    anchors.fill: parent
+                    enabled: model.enabled
+                    onClicked: selectorModel.accept(model.index)
+                }
+            }
+
+            section.property: "group"
+            section.delegate: Rectangle {
+                height: 30
+                width: parent.width
+                color: "silver"
+                Text {
+                    anchors.centerIn: parent
+                    text: section
+                    font.bold: true
+                }
+            }
+        }
+    }
+}