| /* |
| * 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 |
| * 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 "WebPopupMenuProxyQt.h" |
| |
| #include "PlatformPopupMenuData.h" |
| #include "WebPopupItem.h" |
| #include "qquickwebview_p.h" |
| #include "qquickwebview_p_p.h" |
| #include <QtCore/QAbstractListModel> |
| #include <QtQml/QQmlContext> |
| #include <QtQml/QQmlEngine> |
| |
| using namespace WebCore; |
| |
| namespace WebKit { |
| |
| 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>&, bool multiple); |
| virtual int rowCount(const QModelIndex& parent = QModelIndex()) const { return m_items.size(); } |
| virtual QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const; |
| virtual QHash<int, QByteArray> roleNames() const; |
| |
| Q_INVOKABLE void select(int); |
| |
| int selectedOriginalIndex() const; |
| bool multiple() const { return m_allowMultiples; } |
| void toggleItem(int); |
| |
| Q_SIGNALS: |
| void indexUpdated(); |
| |
| private: |
| struct Item { |
| Item(const WebPopupItem& webPopupItem, const QString& group, int originalIndex) |
| : text(webPopupItem.m_text) |
| , toolTip(webPopupItem.m_toolTip) |
| , group(group) |
| , originalIndex(originalIndex) |
| , enabled(webPopupItem.m_isEnabled) |
| , selected(webPopupItem.m_isSelected) |
| , 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); |
| |
| Vector<Item> m_items; |
| int m_selectedModelIndex; |
| bool m_allowMultiples; |
| }; |
| |
| class ItemSelectorContextObject : public QObject { |
| Q_OBJECT |
| Q_PROPERTY(QRectF elementRect READ elementRect CONSTANT FINAL) |
| Q_PROPERTY(QObject* items READ items CONSTANT FINAL) |
| Q_PROPERTY(bool allowMultiSelect READ allowMultiSelect CONSTANT FINAL) |
| |
| public: |
| ItemSelectorContextObject(const QRectF& elementRect, const Vector<WebPopupItem>&, bool multiple); |
| |
| QRectF elementRect() const { return m_elementRect; } |
| PopupMenuItemModel* items() { return &m_items; } |
| bool allowMultiSelect() { return m_items.multiple(); } |
| |
| Q_INVOKABLE void accept(int index = -1); |
| Q_INVOKABLE void reject() { emit done(); } |
| Q_INVOKABLE void dismiss() { emit done(); } |
| |
| Q_SIGNALS: |
| void acceptedWithOriginalIndex(int); |
| void done(); |
| |
| private Q_SLOTS: |
| void onIndexUpdate(); |
| |
| private: |
| QRectF m_elementRect; |
| PopupMenuItemModel m_items; |
| }; |
| |
| ItemSelectorContextObject::ItemSelectorContextObject(const QRectF& elementRect, const Vector<WebPopupItem>& webPopupItems, bool multiple) |
| : m_elementRect(elementRect) |
| , m_items(webPopupItems, multiple) |
| { |
| connect(&m_items, SIGNAL(indexUpdated()), SLOT(onIndexUpdate())); |
| } |
| |
| void ItemSelectorContextObject::onIndexUpdate() |
| { |
| // Send the update for multi-select list. |
| if (m_items.multiple()) |
| emit acceptedWithOriginalIndex(m_items.selectedOriginalIndex()); |
| } |
| |
| |
| void ItemSelectorContextObject::accept(int index) |
| { |
| // If the index is not valid for multi-select lists, just hide the pop up as the selected indices have |
| // already been sent. |
| if ((index == -1) && m_items.multiple()) |
| emit done(); |
| else { |
| if (index != -1) |
| m_items.toggleItem(index); |
| emit acceptedWithOriginalIndex(m_items.selectedOriginalIndex()); |
| } |
| } |
| |
| 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, bool multiple) |
| : m_selectedModelIndex(-1) |
| , m_allowMultiples(multiple) |
| { |
| buildItems(webPopupItems); |
| } |
| |
| QHash<int, QByteArray> PopupMenuItemModel::roleNames() const |
| { |
| static QHash<int, QByteArray> roles = createRoleNamesHash(); |
| return roles; |
| } |
| |
| 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) |
| { |
| toggleItem(index); |
| emit indexUpdated(); |
| } |
| |
| void PopupMenuItemModel::toggleItem(int index) |
| { |
| int oldIndex = m_selectedModelIndex; |
| if (index < 0 || index >= m_items.size()) |
| return; |
| Item& item = m_items[index]; |
| if (!item.enabled) |
| return; |
| |
| m_selectedModelIndex = index; |
| if (m_allowMultiples) |
| item.selected = !item.selected; |
| else { |
| if (index == oldIndex) |
| return; |
| item.selected = true; |
| if (oldIndex != -1) { |
| Item& oldItem = m_items[oldIndex]; |
| oldItem.selected = false; |
| 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) |
| { |
| 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; |
| } |
| if (webPopupItem.m_isSelected && !m_allowMultiples) |
| m_selectedModelIndex = m_items.size(); |
| m_items.append(Item(webPopupItem, currentGroup, i)); |
| } |
| } |
| |
| WebPopupMenuProxyQt::WebPopupMenuProxyQt(WebPopupMenuProxy::Client* client, QQuickWebView* webView) |
| : WebPopupMenuProxy(client) |
| , m_webView(webView) |
| { |
| } |
| |
| WebPopupMenuProxyQt::~WebPopupMenuProxyQt() |
| { |
| } |
| |
| void WebPopupMenuProxyQt::showPopupMenu(const IntRect& rect, WebCore::TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t) |
| { |
| m_selectionType = (data.multipleSelections) ? WebPopupMenuProxyQt::MultipleSelection : WebPopupMenuProxyQt::SingleSelection; |
| |
| const QRectF mappedRect= m_webView->mapRectFromWebContent(QRect(rect)); |
| ItemSelectorContextObject* contextObject = new ItemSelectorContextObject(mappedRect, items, (m_selectionType == WebPopupMenuProxyQt::MultipleSelection)); |
| createItem(contextObject); |
| if (!m_itemSelector) { |
| hidePopupMenu(); |
| return; |
| } |
| } |
| |
| void WebPopupMenuProxyQt::hidePopupMenu() |
| { |
| m_itemSelector.clear(); |
| m_context.clear(); |
| |
| if (m_client) { |
| m_client->closePopupMenu(); |
| invalidate(); |
| } |
| } |
| |
| void WebPopupMenuProxyQt::selectIndex(int index) |
| { |
| m_client->changeSelectedIndex(index); |
| } |
| |
| void WebPopupMenuProxyQt::createItem(QObject* contextObject) |
| { |
| QQmlComponent* component = m_webView->experimental()->itemSelector(); |
| if (!component) { |
| delete contextObject; |
| return; |
| } |
| |
| createContext(component, contextObject); |
| QObject* object = component->beginCreate(m_context.get()); |
| if (!object) |
| return; |
| |
| m_itemSelector = adoptPtr(qobject_cast<QQuickItem*>(object)); |
| if (!m_itemSelector) |
| 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(done()), SLOT(hidePopupMenu()), Qt::QueuedConnection); |
| if (m_selectionType == WebPopupMenuProxyQt::SingleSelection) |
| connect(contextObject, SIGNAL(acceptedWithOriginalIndex(int)), SLOT(hidePopupMenu()), Qt::QueuedConnection); |
| |
| QQuickWebViewPrivate::get(m_webView)->addAttachedPropertyTo(m_itemSelector.get()); |
| m_itemSelector->setParentItem(m_webView); |
| |
| // Only fully create the component once we've set both a parent |
| // and the needed context and attached properties, so that the |
| // dialog can do useful stuff in Component.onCompleted(). |
| component->completeCreate(); |
| } |
| |
| void WebPopupMenuProxyQt::createContext(QQmlComponent* component, QObject* contextObject) |
| { |
| QQmlContext* baseContext = component->creationContext(); |
| if (!baseContext) |
| baseContext = QQmlEngine::contextForObject(m_webView); |
| m_context = adoptPtr(new QQmlContext(baseContext)); |
| |
| contextObject->setParent(m_context.get()); |
| m_context->setContextProperty(QLatin1String("model"), contextObject); |
| m_context->setContextObject(contextObject); |
| } |
| |
| } // 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" |