File: | build/../Source/WebKit/UIProcess/gtk/WebPopupMenuProxyGtk.cpp |
Warning: | line 50, column 5 Call to virtual function during destruction will not dispatch to derived class |
1 | /* |
2 | * Copyright (C) 2011 Igalia S.L. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "WebPopupMenuProxyGtk.h" |
28 | |
29 | #include "NativeWebMouseEvent.h" |
30 | #include "WebPopupItem.h" |
31 | #include <WebCore/GtkUtilities.h> |
32 | #include <WebCore/IntRect.h> |
33 | #include <gtk/gtk.h> |
34 | #include <wtf/glib/GUniquePtr.h> |
35 | #include <wtf/text/CString.h> |
36 | |
37 | using namespace WebCore; |
38 | |
39 | namespace WebKit { |
40 | |
41 | WebPopupMenuProxyGtk::WebPopupMenuProxyGtk(GtkWidget* webView, WebPopupMenuProxy::Client& client) |
42 | : WebPopupMenuProxy(client) |
43 | , m_webView(webView) |
44 | , m_dismissMenuTimer(RunLoop::main(), this, &WebPopupMenuProxyGtk::dismissMenuTimerFired) |
45 | { |
46 | } |
47 | |
48 | WebPopupMenuProxyGtk::~WebPopupMenuProxyGtk() |
49 | { |
50 | cancelTracking(); |
Call to virtual function during destruction will not dispatch to derived class | |
51 | } |
52 | |
53 | void WebPopupMenuProxyGtk::populatePopupMenu(const Vector<WebPopupItem>& items) |
54 | { |
55 | int itemIndex = 0; |
56 | for (const auto& item : items) { |
57 | if (item.m_type == WebPopupItem::Separator) { |
58 | GtkWidget* menuItem = gtk_separator_menu_item_new(); |
59 | gtk_menu_shell_append(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance *) ((m_popup)), ((gtk_menu_shell_get_type ())))))), menuItem); |
60 | gtk_widget_show(menuItem); |
61 | } else { |
62 | GtkWidget* menuItem = gtk_menu_item_new_with_label(item.m_text.utf8().data()); |
63 | gtk_widget_set_tooltip_text(menuItem, item.m_toolTip.utf8().data()); |
64 | gtk_widget_set_sensitive(menuItem, item.m_isEnabled); |
65 | g_object_set_data(G_OBJECT(menuItem)((((GObject*) g_type_check_instance_cast ((GTypeInstance*) (( menuItem)), (((GType) ((20) << (2)))))))), "popup-menu-item-index", GINT_TO_POINTER(itemIndex)((gpointer) (glong) (itemIndex))); |
66 | g_signal_connect(menuItem, "activate", G_CALLBACK(menuItemActivated), this)g_signal_connect_data ((menuItem), ("activate"), (((GCallback ) (menuItemActivated))), (this), __null, (GConnectFlags) 0); |
67 | g_signal_connect(menuItem, "select", G_CALLBACK(selectItemCallback), this)g_signal_connect_data ((menuItem), ("select"), (((GCallback) ( selectItemCallback))), (this), __null, (GConnectFlags) 0); |
68 | gtk_menu_shell_append(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance *) ((m_popup)), ((gtk_menu_shell_get_type ())))))), menuItem); |
69 | gtk_widget_show(menuItem); |
70 | } |
71 | itemIndex++; |
72 | } |
73 | } |
74 | |
75 | void WebPopupMenuProxyGtk::showPopupMenu(const IntRect& rect, TextDirection, double /* pageScaleFactor */, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex) |
76 | { |
77 | ASSERT(!m_popup)((void)0); |
78 | m_popup = gtk_menu_new(); |
79 | g_signal_connect(m_popup, "key-press-event", G_CALLBACK(keyPressEventCallback), this)g_signal_connect_data ((m_popup), ("key-press-event"), (((GCallback ) (keyPressEventCallback))), (this), __null, (GConnectFlags) 0 ); |
80 | g_signal_connect(m_popup, "unmap", G_CALLBACK(menuUnmappedCallback), this)g_signal_connect_data ((m_popup), ("unmap"), (((GCallback) (menuUnmappedCallback ))), (this), __null, (GConnectFlags) 0); |
81 | |
82 | populatePopupMenu(items); |
83 | gtk_menu_set_active(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) (( m_popup)), ((gtk_menu_get_type ())))))), selectedIndex); |
84 | |
85 | resetTypeAheadFindState(); |
86 | |
87 | IntPoint menuPosition = convertWidgetPointToScreenPoint(m_webView, rect.location()); |
88 | menuPosition.move(0, rect.height()); |
89 | |
90 | // This approach follows the one in gtkcombobox.c. |
91 | GtkRequisition requisition; |
92 | gtk_widget_set_size_request(m_popup, -1, -1); |
93 | gtk_widget_get_preferred_size(m_popup, &requisition, nullptr); |
94 | gtk_widget_set_size_request(m_popup, std::max(rect.width(), requisition.width), -1); |
95 | |
96 | if (int itemCount = items.size()) { |
97 | GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup)((((GtkContainer*) g_type_check_instance_cast ((GTypeInstance *) ((m_popup)), ((gtk_container_get_type ())))))))); |
98 | int i; |
99 | GList* child; |
100 | for (i = 0, child = children.get(); i < itemCount; i++, child = g_list_next(child)((child) ? (((GList *)(child))->next) : __null)) { |
101 | if (i > selectedIndex) |
102 | break; |
103 | |
104 | GtkWidget* item = GTK_WIDGET(child->data)((((GtkWidget*) g_type_check_instance_cast ((GTypeInstance*) ( (child->data)), ((gtk_widget_get_type ())))))); |
105 | GtkRequisition itemRequisition; |
106 | gtk_widget_get_preferred_size(item, &itemRequisition, nullptr); |
107 | menuPosition.setY(menuPosition.y() - itemRequisition.height); |
108 | } |
109 | } else { |
110 | // Center vertically the empty popup in the combo box area. |
111 | menuPosition.setY(menuPosition.y() - rect.height() / 2); |
112 | } |
113 | |
114 | gtk_menu_attach_to_widget(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) (( m_popup)), ((gtk_menu_get_type ())))))), GTK_WIDGET(m_webView)((((GtkWidget*) g_type_check_instance_cast ((GTypeInstance*) ( (m_webView)), ((gtk_widget_get_type ())))))), nullptr); |
115 | |
116 | const GdkEvent* event = m_client->currentlyProcessedMouseDownEvent() ? m_client->currentlyProcessedMouseDownEvent()->nativeEvent() : nullptr; |
117 | gtk_menu_popup_for_device(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) (( m_popup)), ((gtk_menu_get_type ())))))), event ? gdk_event_get_device(event) : nullptr, nullptr, nullptr, |
118 | [](GtkMenu*, gint* x, gint* y, gboolean* pushIn, gpointer userData) { |
119 | // We can pass a pointer to the menuPosition local variable because the nested main loop ensures this is called in the function context. |
120 | IntPoint* menuPosition = static_cast<IntPoint*>(userData); |
121 | *x = menuPosition->x(); |
122 | *y = menuPosition->y(); |
123 | *pushIn = menuPosition->y() < 0; |
124 | }, &menuPosition, nullptr, event && event->type == GDK_BUTTON_PRESS ? event->button.button : 1, |
125 | event ? gdk_event_get_time(event) : GDK_CURRENT_TIME0L); |
126 | |
127 | // Now that the menu has a position, schedule a resize to make sure it's resized to fit vertically in the work area. |
128 | gtk_widget_queue_resize(m_popup); |
129 | |
130 | // PopupMenu can fail to open when there is no mouse grab. |
131 | // Ensure WebCore does not go into some pesky state. |
132 | if (!gtk_widget_get_visible(m_popup)) { |
133 | m_client->failedToShowPopupMenu(); |
134 | return; |
135 | } |
136 | |
137 | // This ensures that the active item gets selected after popping up the menu, and |
138 | // as it says in "gtkcombobox.c" (line ~1606): it's ugly, but gets the job done. |
139 | GtkWidget* activeChild = gtk_menu_get_active(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) (( m_popup)), ((gtk_menu_get_type ()))))))); |
140 | if (activeChild && gtk_widget_get_visible(activeChild)) |
141 | gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance *) ((m_popup)), ((gtk_menu_shell_get_type ())))))), activeChild); |
142 | } |
143 | |
144 | void WebPopupMenuProxyGtk::hidePopupMenu() |
145 | { |
146 | if (!m_popup) |
147 | return; |
148 | |
149 | gtk_menu_popdown(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) (( m_popup)), ((gtk_menu_get_type ()))))))); |
150 | resetTypeAheadFindState(); |
151 | } |
152 | |
153 | void WebPopupMenuProxyGtk::cancelTracking() |
154 | { |
155 | if (!m_popup) |
156 | return; |
157 | |
158 | m_dismissMenuTimer.stop(); |
159 | g_signal_handlers_disconnect_matched(m_popup, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); |
160 | hidePopupMenu(); |
161 | gtk_widget_destroy(m_popup); |
162 | m_popup = nullptr; |
163 | } |
164 | |
165 | bool WebPopupMenuProxyGtk::typeAheadFind(GdkEventKey* event) |
166 | { |
167 | // If we were given a non-printable character just skip it. |
168 | gunichar unicodeCharacter = gdk_keyval_to_unicode(event->keyval); |
169 | if (!g_unichar_isprint(unicodeCharacter)) { |
170 | resetTypeAheadFindState(); |
171 | return false; |
172 | } |
173 | |
174 | glong charactersWritten; |
175 | GUniquePtr<gunichar2> utf16String(g_ucs4_to_utf16(&unicodeCharacter, 1, nullptr, &charactersWritten, nullptr)); |
176 | if (!utf16String) { |
177 | resetTypeAheadFindState(); |
178 | return false; |
179 | } |
180 | |
181 | // If the character is the same as the last character, the user is probably trying to |
182 | // cycle through the menulist entries. This matches the WebCore behavior for collapsed menulists. |
183 | static const uint32_t searchTimeoutMs = 1000; |
184 | bool repeatingCharacter = unicodeCharacter != m_previousKeyEventCharacter; |
185 | if (event->time - m_previousKeyEventTimestamp > searchTimeoutMs) |
186 | m_currentSearchString = String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten); |
187 | else if (repeatingCharacter) |
188 | m_currentSearchString.append(String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten)); |
189 | |
190 | m_previousKeyEventTimestamp = event->time; |
191 | m_previousKeyEventCharacter = unicodeCharacter; |
192 | |
193 | GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup)((((GtkContainer*) g_type_check_instance_cast ((GTypeInstance *) ((m_popup)), ((gtk_container_get_type ())))))))); |
194 | if (!children) |
195 | return true; |
196 | |
197 | // We case fold before searching, because strncmp does not handle non-ASCII characters. |
198 | GUniquePtr<gchar> searchStringWithCaseFolded(g_utf8_casefold(m_currentSearchString.utf8().data(), -1)); |
199 | size_t prefixLength = strlen(searchStringWithCaseFolded.get()); |
200 | |
201 | // If a menu item has already been selected, start searching from the current |
202 | // item down the list. This will make multiple key presses of the same character |
203 | // advance the selection. |
204 | GList* currentChild = children.get(); |
205 | if (m_currentlySelectedMenuItem) { |
206 | currentChild = g_list_find(children.get(), m_currentlySelectedMenuItem); |
207 | if (!currentChild) { |
208 | m_currentlySelectedMenuItem = nullptr; |
209 | currentChild = children.get(); |
210 | } |
211 | |
212 | // Repeating characters should iterate. |
213 | if (repeatingCharacter) { |
214 | if (GList* nextChild = g_list_next(currentChild)((currentChild) ? (((GList *)(currentChild))->next) : __null )) |
215 | currentChild = nextChild; |
216 | } |
217 | } |
218 | |
219 | GList* firstChild = currentChild; |
220 | do { |
221 | currentChild = g_list_next(currentChild)((currentChild) ? (((GList *)(currentChild))->next) : __null ); |
222 | if (!currentChild) |
223 | currentChild = children.get(); |
224 | |
225 | GUniquePtr<gchar> itemText(g_utf8_casefold(gtk_menu_item_get_label(GTK_MENU_ITEM(currentChild->data)((((GtkMenuItem*) g_type_check_instance_cast ((GTypeInstance* ) ((currentChild->data)), ((gtk_menu_item_get_type ()))))) )), -1)); |
226 | if (!strncmp(searchStringWithCaseFolded.get(), itemText.get(), prefixLength)) { |
227 | gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance *) ((m_popup)), ((gtk_menu_shell_get_type ())))))), GTK_WIDGET(currentChild->data)((((GtkWidget*) g_type_check_instance_cast ((GTypeInstance*) ( (currentChild->data)), ((gtk_widget_get_type ()))))))); |
228 | break; |
229 | } |
230 | } while (currentChild != firstChild); |
231 | |
232 | return true; |
233 | } |
234 | |
235 | void WebPopupMenuProxyGtk::resetTypeAheadFindState() |
236 | { |
237 | m_currentlySelectedMenuItem = nullptr; |
238 | m_previousKeyEventCharacter = 0; |
239 | m_previousKeyEventTimestamp = 0; |
240 | m_currentSearchString = emptyString(); |
241 | } |
242 | |
243 | void WebPopupMenuProxyGtk::menuItemActivated(GtkMenuItem* menuItem, WebPopupMenuProxyGtk* popupMenu) |
244 | { |
245 | popupMenu->m_dismissMenuTimer.stop(); |
246 | if (popupMenu->m_client) |
247 | popupMenu->m_client->valueChangedForPopupMenu(popupMenu, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuItem), "popup-menu-item-index"))((gint) (glong) (g_object_get_data(((((GObject*) g_type_check_instance_cast ((GTypeInstance*) ((menuItem)), (((GType) ((20) << (2) ))))))), "popup-menu-item-index")))); |
248 | } |
249 | |
250 | void WebPopupMenuProxyGtk::dismissMenuTimerFired() |
251 | { |
252 | if (m_client) |
253 | m_client->valueChangedForPopupMenu(this, -1); |
254 | } |
255 | |
256 | void WebPopupMenuProxyGtk::menuUnmappedCallback(GtkWidget*, WebPopupMenuProxyGtk* popupMenu) |
257 | { |
258 | if (!popupMenu->m_client) |
259 | return; |
260 | |
261 | // When an item is activated, the menu is first hidden and then activate signal is emitted, so at this point we don't know |
262 | // if the menu has been hidden because an item has been selected or because the menu has been dismissed. Wait until the next |
263 | // main loop iteration to dismiss the menu, if an item is activated the timer will be cancelled. |
264 | popupMenu->m_dismissMenuTimer.startOneShot(0_s); |
265 | } |
266 | |
267 | void WebPopupMenuProxyGtk::selectItemCallback(GtkWidget* item, WebPopupMenuProxyGtk* popupMenu) |
268 | { |
269 | popupMenu->setCurrentlySelectedMenuItem(item); |
270 | } |
271 | |
272 | gboolean WebPopupMenuProxyGtk::keyPressEventCallback(GtkWidget*, GdkEventKey* event, WebPopupMenuProxyGtk* popupMenu) |
273 | { |
274 | return popupMenu->typeAheadFind(event); |
275 | } |
276 | |
277 | } // namespace WebKit |