| 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 |