9d936a669ecac00f56b9f7d55f408565a61d9946
[WebKit-https.git] / Source / WebKit2 / UIProcess / qt / WebPopupMenuProxyQt.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "WebPopupMenuProxyQt.h"
29
30 #include "PlatformPopupMenuData.h"
31 #include "WebPopupItem.h"
32 #include "qquickwebview_p.h"
33 #include "qquickwebview_p_p.h"
34 #include <QtCore/QAbstractListModel>
35 #include <QtQml/QQmlContext>
36 #include <QtQml/QQmlEngine>
37
38 using namespace WebCore;
39
40 namespace WebKit {
41
42 static QHash<int, QByteArray> createRoleNamesHash();
43
44 class PopupMenuItemModel : public QAbstractListModel {
45     Q_OBJECT
46
47 public:
48     enum Roles {
49         GroupRole = Qt::UserRole,
50         EnabledRole = Qt::UserRole + 1,
51         SelectedRole = Qt::UserRole + 2,
52         IsSeparatorRole = Qt::UserRole + 3
53     };
54
55     PopupMenuItemModel(const Vector<WebPopupItem>&, int selectedOriginalIndex);
56     virtual int rowCount(const QModelIndex& parent = QModelIndex()) const { return m_items.size(); }
57     virtual QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const;
58
59     Q_INVOKABLE void select(int);
60
61     int selectedOriginalIndex() const;
62
63 private:
64     struct Item {
65         Item(const WebPopupItem& webPopupItem, const QString& group, int originalIndex, bool selected)
66             : text(webPopupItem.m_text)
67             , toolTip(webPopupItem.m_toolTip)
68             , group(group)
69             , originalIndex(originalIndex)
70             , enabled(webPopupItem.m_isEnabled)
71             , selected(selected)
72             , isSeparator(webPopupItem.m_type == WebPopupItem::Separator)
73         { }
74
75         QString text;
76         QString toolTip;
77         QString group;
78         // Keep track of originalIndex because we don't add the label (group) items to our vector.
79         int originalIndex;
80         bool enabled;
81         bool selected;
82         bool isSeparator;
83     };
84
85     void buildItems(const Vector<WebPopupItem>& webPopupItems, int selectedOriginalIndex);
86
87     Vector<Item> m_items;
88     int m_selectedModelIndex;
89 };
90
91 class ItemSelectorContextObject : public QObject {
92     Q_OBJECT
93     Q_PROPERTY(QRectF elementRect READ elementRect CONSTANT FINAL)
94     Q_PROPERTY(QObject* items READ items CONSTANT FINAL)
95
96 public:
97     ItemSelectorContextObject(const QRectF& elementRect, const Vector<WebPopupItem>&, int selectedIndex);
98
99     QRectF elementRect() const { return m_elementRect; }
100     PopupMenuItemModel* items() { return &m_items; }
101
102     Q_INVOKABLE void accept(int index = -1);
103     Q_INVOKABLE void reject() { emit rejected(); }
104
105 Q_SIGNALS:
106     void acceptedWithOriginalIndex(int);
107     void rejected();
108
109 private:
110     QRectF m_elementRect;
111     PopupMenuItemModel m_items;
112 };
113
114 ItemSelectorContextObject::ItemSelectorContextObject(const QRectF& elementRect, const Vector<WebPopupItem>& webPopupItems, int selectedIndex)
115     : m_elementRect(elementRect)
116     , m_items(webPopupItems, selectedIndex)
117 {
118 }
119
120 void ItemSelectorContextObject::accept(int index)
121 {
122     if (index != -1)
123         m_items.select(index);
124     int originalIndex = m_items.selectedOriginalIndex();
125     emit acceptedWithOriginalIndex(originalIndex);
126 }
127
128 static QHash<int, QByteArray> createRoleNamesHash()
129 {
130     QHash<int, QByteArray> roles;
131     roles[Qt::DisplayRole] = "text";
132     roles[Qt::ToolTipRole] = "tooltip";
133     roles[PopupMenuItemModel::GroupRole] = "group";
134     roles[PopupMenuItemModel::EnabledRole] = "enabled";
135     roles[PopupMenuItemModel::SelectedRole] = "selected";
136     roles[PopupMenuItemModel::IsSeparatorRole] = "isSeparator";
137     return roles;
138 }
139
140 PopupMenuItemModel::PopupMenuItemModel(const Vector<WebPopupItem>& webPopupItems, int selectedOriginalIndex)
141     : m_selectedModelIndex(-1)
142 {
143     static QHash<int, QByteArray> roles = createRoleNamesHash();
144     setRoleNames(roles);
145     buildItems(webPopupItems, selectedOriginalIndex);
146 }
147
148 QVariant PopupMenuItemModel::data(const QModelIndex& index, int role) const
149 {
150     if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
151         return QVariant();
152
153     const Item& item = m_items[index.row()];
154     if (item.isSeparator) {
155         if (role == IsSeparatorRole)
156             return true;
157         return QVariant();
158     }
159
160     switch (role) {
161     case Qt::DisplayRole:
162         return item.text;
163     case Qt::ToolTipRole:
164         return item.toolTip;
165     case GroupRole:
166         return item.group;
167     case EnabledRole:
168         return item.enabled;
169     case SelectedRole:
170         return item.selected;
171     case IsSeparatorRole:
172         return false;
173     }
174
175     return QVariant();
176 }
177
178 void PopupMenuItemModel::select(int index)
179 {
180     int oldIndex = m_selectedModelIndex;
181     if (index == oldIndex)
182         return;
183     if (index < 0 || index >= m_items.size())
184         return;
185     Item& item = m_items[index];
186     if (!item.enabled)
187         return;
188
189     item.selected = true;
190     m_selectedModelIndex = index;
191
192     if (oldIndex != -1) {
193         Item& oldItem = m_items[oldIndex];
194         oldItem.selected = false;
195         emit dataChanged(this->index(oldIndex), this->index(oldIndex));
196     }
197
198     emit dataChanged(this->index(index), this->index(index));
199 }
200
201 int PopupMenuItemModel::selectedOriginalIndex() const
202 {
203     if (m_selectedModelIndex == -1)
204         return -1;
205     return m_items[m_selectedModelIndex].originalIndex;
206 }
207
208 void PopupMenuItemModel::buildItems(const Vector<WebPopupItem>& webPopupItems, int selectedOriginalIndex)
209 {
210     QString currentGroup;
211     m_items.reserveInitialCapacity(webPopupItems.size());
212     for (int i = 0; i < webPopupItems.size(); i++) {
213         const WebPopupItem& webPopupItem = webPopupItems[i];
214         if (webPopupItem.m_isLabel) {
215             currentGroup = webPopupItem.m_text;
216             continue;
217         }
218         const bool selected = i == selectedOriginalIndex;
219         if (selected)
220             m_selectedModelIndex = m_items.size();
221         m_items.append(Item(webPopupItem, currentGroup, i, selected));
222     }
223 }
224
225 WebPopupMenuProxyQt::WebPopupMenuProxyQt(WebPopupMenuProxy::Client* client, QQuickWebView* webView)
226     : WebPopupMenuProxy(client)
227     , m_webView(webView)
228 {
229 }
230
231 WebPopupMenuProxyQt::~WebPopupMenuProxyQt()
232 {
233 }
234
235 void WebPopupMenuProxyQt::showPopupMenu(const IntRect& rect, WebCore::TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex)
236 {
237     m_selectedIndex = selectedIndex;
238
239     const QRectF mappedRect= m_webView->mapRectFromWebContent(QRect(rect));
240     ItemSelectorContextObject* contextObject = new ItemSelectorContextObject(mappedRect, items, m_selectedIndex);
241     createItem(contextObject);
242     if (!m_itemSelector) {
243         notifyValueChanged();
244         return;
245     }
246     QQuickWebViewPrivate::get(m_webView)->setDialogActive(true);
247 }
248
249 void WebPopupMenuProxyQt::hidePopupMenu()
250 {
251     m_itemSelector.clear();
252     QQuickWebViewPrivate::get(m_webView)->setDialogActive(false);
253     m_context.clear();
254     notifyValueChanged();
255 }
256
257 void WebPopupMenuProxyQt::selectIndex(int index)
258 {
259     m_selectedIndex = index;
260 }
261
262 void WebPopupMenuProxyQt::createItem(QObject* contextObject)
263 {
264     QQmlComponent* component = m_webView->experimental()->itemSelector();
265     if (!component) {
266         delete contextObject;
267         return;
268     }
269
270     createContext(component, contextObject);
271     QObject* object = component->beginCreate(m_context.get());
272     if (!object) {
273         m_context.clear();
274         return;
275     }
276
277     m_itemSelector = adoptPtr(qobject_cast<QQuickItem*>(object));
278     if (!m_itemSelector) {
279         m_context.clear();
280         m_itemSelector.clear();
281         return;
282     }
283
284     connect(contextObject, SIGNAL(acceptedWithOriginalIndex(int)), SLOT(selectIndex(int)));
285
286     // We enqueue these because they are triggered by m_itemSelector and will lead to its destruction.
287     connect(contextObject, SIGNAL(acceptedWithOriginalIndex(int)), SLOT(hidePopupMenu()), Qt::QueuedConnection);
288     connect(contextObject, SIGNAL(rejected()), SLOT(hidePopupMenu()), Qt::QueuedConnection);
289
290     QQuickWebViewPrivate::get(m_webView)->addAttachedPropertyTo(m_itemSelector.get());
291     m_itemSelector->setParentItem(m_webView);
292
293     // Only fully create the component once we've set both a parent
294     // and the needed context and attached properties, so that the
295     // dialog can do useful stuff in Component.onCompleted().
296     component->completeCreate();
297 }
298
299 void WebPopupMenuProxyQt::createContext(QQmlComponent* component, QObject* contextObject)
300 {
301     QQmlContext* baseContext = component->creationContext();
302     if (!baseContext)
303         baseContext = QQmlEngine::contextForObject(m_webView);
304     m_context = adoptPtr(new QQmlContext(baseContext));
305
306     contextObject->setParent(m_context.get());
307     m_context->setContextProperty(QLatin1String("model"), contextObject);
308     m_context->setContextObject(contextObject);
309 }
310
311 void WebPopupMenuProxyQt::notifyValueChanged()
312 {
313     if (m_client) {
314         m_client->valueChangedForPopupMenu(this, m_selectedIndex);
315         invalidate();
316     }
317 }
318
319 } // namespace WebKit
320
321 // Since we define QObjects in WebPopupMenuProxyQt.cpp, this will trigger moc to run on .cpp.
322 #include "WebPopupMenuProxyQt.moc"
323
324 // And we can't compile the moc for WebPopupMenuProxyQt.h by itself, since it doesn't include "config.h"
325 #include "moc_WebPopupMenuProxyQt.cpp"