[Qt][WK2] Add support for multi-select list
[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>&, bool multiple);
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     bool multiple() const { return m_allowMultiples; }
63     void toggleItem(int);
64
65 Q_SIGNALS:
66     void indexUpdated();
67
68 private:
69     struct Item {
70         Item(const WebPopupItem& webPopupItem, const QString& group, int originalIndex)
71             : text(webPopupItem.m_text)
72             , toolTip(webPopupItem.m_toolTip)
73             , group(group)
74             , originalIndex(originalIndex)
75             , enabled(webPopupItem.m_isEnabled)
76             , selected(webPopupItem.m_isSelected)
77             , isSeparator(webPopupItem.m_type == WebPopupItem::Separator)
78         { }
79
80         QString text;
81         QString toolTip;
82         QString group;
83         // Keep track of originalIndex because we don't add the label (group) items to our vector.
84         int originalIndex;
85         bool enabled;
86         bool selected;
87         bool isSeparator;
88     };
89
90     void buildItems(const Vector<WebPopupItem>& webPopupItems);
91
92     Vector<Item> m_items;
93     int m_selectedModelIndex;
94     bool m_allowMultiples;
95 };
96
97 class ItemSelectorContextObject : public QObject {
98     Q_OBJECT
99     Q_PROPERTY(QRectF elementRect READ elementRect CONSTANT FINAL)
100     Q_PROPERTY(QObject* items READ items CONSTANT FINAL)
101     Q_PROPERTY(bool allowMultiSelect READ allowMultiSelect CONSTANT FINAL)
102
103 public:
104     ItemSelectorContextObject(const QRectF& elementRect, const Vector<WebPopupItem>&, bool multiple);
105
106     QRectF elementRect() const { return m_elementRect; }
107     PopupMenuItemModel* items() { return &m_items; }
108     bool allowMultiSelect() { return m_items.multiple(); }
109
110     Q_INVOKABLE void accept(int index = -1);
111     Q_INVOKABLE void reject() { emit done(); }
112     Q_INVOKABLE void dismiss() { emit done(); }
113
114 Q_SIGNALS:
115     void acceptedWithOriginalIndex(int);
116     void done();
117
118 private Q_SLOTS:
119     void onIndexUpdate();
120
121 private:
122     QRectF m_elementRect;
123     PopupMenuItemModel m_items;
124 };
125
126 ItemSelectorContextObject::ItemSelectorContextObject(const QRectF& elementRect, const Vector<WebPopupItem>& webPopupItems, bool multiple)
127     : m_elementRect(elementRect)
128     , m_items(webPopupItems, multiple)
129 {
130     connect(&m_items, SIGNAL(indexUpdated()), SLOT(onIndexUpdate()));
131 }
132
133 void ItemSelectorContextObject::onIndexUpdate()
134 {
135     // Send the update for multi-select list.
136     if (m_items.multiple())
137         emit acceptedWithOriginalIndex(m_items.selectedOriginalIndex());
138 }
139
140
141 void ItemSelectorContextObject::accept(int index)
142 {
143     // If the index is not valid for multi-select lists, just hide the pop up as the selected indices have
144     // already been sent.
145     if ((index == -1) && m_items.multiple())
146         emit done();
147     else {
148         if (index != -1)
149             m_items.toggleItem(index);
150         emit acceptedWithOriginalIndex(m_items.selectedOriginalIndex());
151     }
152 }
153
154 static QHash<int, QByteArray> createRoleNamesHash()
155 {
156     QHash<int, QByteArray> roles;
157     roles[Qt::DisplayRole] = "text";
158     roles[Qt::ToolTipRole] = "tooltip";
159     roles[PopupMenuItemModel::GroupRole] = "group";
160     roles[PopupMenuItemModel::EnabledRole] = "enabled";
161     roles[PopupMenuItemModel::SelectedRole] = "selected";
162     roles[PopupMenuItemModel::IsSeparatorRole] = "isSeparator";
163     return roles;
164 }
165
166 PopupMenuItemModel::PopupMenuItemModel(const Vector<WebPopupItem>& webPopupItems, bool multiple)
167     : m_selectedModelIndex(-1)
168     , m_allowMultiples(multiple)
169 {
170     static QHash<int, QByteArray> roles = createRoleNamesHash();
171     setRoleNames(roles);
172     buildItems(webPopupItems);
173 }
174
175 QVariant PopupMenuItemModel::data(const QModelIndex& index, int role) const
176 {
177     if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
178         return QVariant();
179
180     const Item& item = m_items[index.row()];
181     if (item.isSeparator) {
182         if (role == IsSeparatorRole)
183             return true;
184         return QVariant();
185     }
186
187     switch (role) {
188     case Qt::DisplayRole:
189         return item.text;
190     case Qt::ToolTipRole:
191         return item.toolTip;
192     case GroupRole:
193         return item.group;
194     case EnabledRole:
195         return item.enabled;
196     case SelectedRole:
197         return item.selected;
198     case IsSeparatorRole:
199         return false;
200     }
201
202     return QVariant();
203 }
204
205 void PopupMenuItemModel::select(int index)
206 {
207     toggleItem(index);
208     emit indexUpdated();
209 }
210
211 void PopupMenuItemModel::toggleItem(int index)
212 {
213     int oldIndex = m_selectedModelIndex;
214     if (index < 0 || index >= m_items.size())
215         return;
216     Item& item = m_items[index];
217     if (!item.enabled)
218         return;
219
220     m_selectedModelIndex = index;
221     if (m_allowMultiples)
222         item.selected = !item.selected;
223     else {
224         if (index == oldIndex)
225             return;
226         item.selected = true;
227         if (oldIndex != -1) {
228             Item& oldItem = m_items[oldIndex];
229             oldItem.selected = false;
230             emit dataChanged(this->index(oldIndex), this->index(oldIndex));
231         }
232     }
233
234     emit dataChanged(this->index(index), this->index(index));
235 }
236
237 int PopupMenuItemModel::selectedOriginalIndex() const
238 {
239     if (m_selectedModelIndex == -1)
240         return -1;
241     return m_items[m_selectedModelIndex].originalIndex;
242 }
243
244 void PopupMenuItemModel::buildItems(const Vector<WebPopupItem>& webPopupItems)
245 {
246     QString currentGroup;
247     m_items.reserveInitialCapacity(webPopupItems.size());
248     for (int i = 0; i < webPopupItems.size(); i++) {
249         const WebPopupItem& webPopupItem = webPopupItems[i];
250         if (webPopupItem.m_isLabel) {
251             currentGroup = webPopupItem.m_text;
252             continue;
253         }
254         if (webPopupItem.m_isSelected && !m_allowMultiples)
255             m_selectedModelIndex = m_items.size();
256         m_items.append(Item(webPopupItem, currentGroup, i));
257     }
258 }
259
260 WebPopupMenuProxyQt::WebPopupMenuProxyQt(WebPopupMenuProxy::Client* client, QQuickWebView* webView)
261     : WebPopupMenuProxy(client)
262     , m_webView(webView)
263 {
264 }
265
266 WebPopupMenuProxyQt::~WebPopupMenuProxyQt()
267 {
268 }
269
270 void WebPopupMenuProxyQt::showPopupMenu(const IntRect& rect, WebCore::TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t)
271 {
272     m_selectionType = (data.multipleSelections) ? WebPopupMenuProxyQt::MultipleSelection : WebPopupMenuProxyQt::SingleSelection;
273
274     const QRectF mappedRect= m_webView->mapRectFromWebContent(QRect(rect));
275     ItemSelectorContextObject* contextObject = new ItemSelectorContextObject(mappedRect, items, (m_selectionType == WebPopupMenuProxyQt::MultipleSelection));
276     createItem(contextObject);
277     if (!m_itemSelector) {
278         hidePopupMenu();
279         return;
280     }
281     QQuickWebViewPrivate::get(m_webView)->setDialogActive(true);
282 }
283
284 void WebPopupMenuProxyQt::hidePopupMenu()
285 {
286     m_itemSelector.clear();
287     QQuickWebViewPrivate::get(m_webView)->setDialogActive(false);
288     m_context.clear();
289
290     if (m_client) {
291         m_client->closePopupMenu();
292         invalidate();
293     }
294 }
295
296 void WebPopupMenuProxyQt::selectIndex(int index)
297 {
298     m_client->changeSelectedIndex(index);
299 }
300
301 void WebPopupMenuProxyQt::createItem(QObject* contextObject)
302 {
303     QQmlComponent* component = m_webView->experimental()->itemSelector();
304     if (!component) {
305         delete contextObject;
306         return;
307     }
308
309     createContext(component, contextObject);
310     QObject* object = component->beginCreate(m_context.get());
311     if (!object)
312         return;
313
314     m_itemSelector = adoptPtr(qobject_cast<QQuickItem*>(object));
315     if (!m_itemSelector)
316         return;
317
318     connect(contextObject, SIGNAL(acceptedWithOriginalIndex(int)), SLOT(selectIndex(int)));
319
320     // We enqueue these because they are triggered by m_itemSelector and will lead to its destruction.
321     connect(contextObject, SIGNAL(done()), SLOT(hidePopupMenu()), Qt::QueuedConnection);
322     if (m_selectionType == WebPopupMenuProxyQt::SingleSelection)
323         connect(contextObject, SIGNAL(acceptedWithOriginalIndex(int)), SLOT(hidePopupMenu()), Qt::QueuedConnection);
324
325     QQuickWebViewPrivate::get(m_webView)->addAttachedPropertyTo(m_itemSelector.get());
326     m_itemSelector->setParentItem(m_webView);
327
328     // Only fully create the component once we've set both a parent
329     // and the needed context and attached properties, so that the
330     // dialog can do useful stuff in Component.onCompleted().
331     component->completeCreate();
332 }
333
334 void WebPopupMenuProxyQt::createContext(QQmlComponent* component, QObject* contextObject)
335 {
336     QQmlContext* baseContext = component->creationContext();
337     if (!baseContext)
338         baseContext = QQmlEngine::contextForObject(m_webView);
339     m_context = adoptPtr(new QQmlContext(baseContext));
340
341     contextObject->setParent(m_context.get());
342     m_context->setContextProperty(QLatin1String("model"), contextObject);
343     m_context->setContextObject(contextObject);
344 }
345
346 } // namespace WebKit
347
348 // Since we define QObjects in WebPopupMenuProxyQt.cpp, this will trigger moc to run on .cpp.
349 #include "WebPopupMenuProxyQt.moc"
350
351 // And we can't compile the moc for WebPopupMenuProxyQt.h by itself, since it doesn't include "config.h"
352 #include "moc_WebPopupMenuProxyQt.cpp"