void GtkPopupMenu::popUp(const IntSize& menuSize, const IntPoint& menuPosition, int itemCount, int selectedItem, const GdkEvent* event) { resetTypeAheadFindState(); m_menuPosition = menuPosition; gtk_menu_set_active(GTK_MENU(m_popup.get()), selectedItem); // This approach follows the one in gtkcombobox.c. GtkRequisition requisition; gtk_widget_set_size_request(m_popup.get(), -1, -1); #ifdef GTK_API_VERSION_2 gtk_widget_size_request(m_popup.get(), &requisition); #else gtk_widget_get_preferred_size(m_popup.get(), &requisition, 0); #endif gtk_widget_set_size_request(m_popup.get(), std::max(menuSize.width(), requisition.width), -1); GList* children = gtk_container_get_children(GTK_CONTAINER(m_popup.get())); GList* p = children; if (itemCount) { for (int i = 0; i < itemCount; i++) { if (i > selectedItem) break; GtkWidget* item = reinterpret_cast<GtkWidget*>(p->data); GtkRequisition itemRequisition; #ifdef GTK_API_VERSION_2 gtk_widget_get_child_requisition(item, &itemRequisition); #else gtk_widget_get_preferred_size(item, &itemRequisition, 0); #endif m_menuPosition.setY(m_menuPosition.y() - itemRequisition.height); p = g_list_next(p); } } else { // Center vertically the empty popup in the combo box area. m_menuPosition.setY(m_menuPosition.y() - menuSize.height() / 2); } g_list_free(children); guint button; guint32 activateTime; if (event) { button = event->type == GDK_BUTTON_PRESS ? event->button.button : 1; activateTime = gdk_event_get_time(event); } else { button = 1; activateTime = GDK_CURRENT_TIME; } #ifdef GTK_API_VERSION_2 gtk_menu_popup(GTK_MENU(m_popup.get()), 0, 0, reinterpret_cast<GtkMenuPositionFunc>(menuPositionFunction), this, button, activateTime); #else gtk_menu_popup_for_device(GTK_MENU(m_popup.get()), event ? gdk_event_get_device(event) : 0, 0, 0, reinterpret_cast<GtkMenuPositionFunc>(menuPositionFunction), this, 0, button, activateTime); #endif }
void WebPopupMenuProxyGtk::showPopupMenu(const IntRect& rect, TextDirection, double /* pageScaleFactor */, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex) { populatePopupMenu(items); gtk_menu_set_active(GTK_MENU(m_popup), selectedIndex); resetTypeAheadFindState(); IntPoint menuPosition = convertWidgetPointToScreenPoint(m_webView, rect.location()); menuPosition.move(0, rect.height()); // This approach follows the one in gtkcombobox.c. GtkRequisition requisition; gtk_widget_set_size_request(m_popup, -1, -1); gtk_widget_get_preferred_size(m_popup, &requisition, nullptr); gtk_widget_set_size_request(m_popup, std::max(rect.width(), requisition.width), -1); if (int itemCount = items.size()) { GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup))); int i; GList* child; for (i = 0, child = children.get(); i < itemCount; i++, child = g_list_next(child)) { if (i > selectedIndex) break; GtkWidget* item = GTK_WIDGET(child->data); GtkRequisition itemRequisition; gtk_widget_get_preferred_size(item, &itemRequisition, nullptr); menuPosition.setY(menuPosition.y() - itemRequisition.height); } } else { // Center vertically the empty popup in the combo box area. menuPosition.setY(menuPosition.y() - rect.height() / 2); } const GdkEvent* event = m_client->currentlyProcessedMouseDownEvent() ? m_client->currentlyProcessedMouseDownEvent()->nativeEvent() : nullptr; gtk_menu_popup_for_device(GTK_MENU(m_popup), event ? gdk_event_get_device(event) : nullptr, nullptr, nullptr, [](GtkMenu*, gint* x, gint* y, gboolean* pushIn, gpointer userData) { // We can pass a pointer to the menuPosition local variable because the nested main loop ensures this is called in the function context. IntPoint* menuPosition = static_cast<IntPoint*>(userData); *x = menuPosition->x(); *y = menuPosition->y(); *pushIn = menuPosition->y() < 0; }, &menuPosition, nullptr, event && event->type == GDK_BUTTON_PRESS ? event->button.button : 1, event ? gdk_event_get_time(event) : GDK_CURRENT_TIME); // Now that the menu has a position, schedule a resize to make sure it's resized to fit vertically in the work area. gtk_widget_queue_resize(m_popup); // PopupMenu can fail to open when there is no mouse grab. // Ensure WebCore does not go into some pesky state. if (!gtk_widget_get_visible(m_popup)) { m_client->failedToShowPopupMenu(); return; } }
bool WebPopupMenuProxyGtk::typeAheadFind(GdkEventKey* event) { // If we were given a non-printable character just skip it. gunichar unicodeCharacter = gdk_keyval_to_unicode(event->keyval); if (!g_unichar_isprint(unicodeCharacter)) { resetTypeAheadFindState(); return false; } glong charactersWritten; GUniquePtr<gunichar2> utf16String(g_ucs4_to_utf16(&unicodeCharacter, 1, nullptr, &charactersWritten, nullptr)); if (!utf16String) { resetTypeAheadFindState(); return false; } // If the character is the same as the last character, the user is probably trying to // cycle through the menulist entries. This matches the WebCore behavior for collapsed menulists. static const uint32_t searchTimeoutMs = 1000; bool repeatingCharacter = unicodeCharacter != m_previousKeyEventCharacter; if (event->time - m_previousKeyEventTimestamp > searchTimeoutMs) m_currentSearchString = String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten); else if (repeatingCharacter) m_currentSearchString.append(String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten)); m_previousKeyEventTimestamp = event->time; m_previousKeyEventCharacter = unicodeCharacter; GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup))); if (!children) return true; // We case fold before searching, because strncmp does not handle non-ASCII characters. GUniquePtr<gchar> searchStringWithCaseFolded(g_utf8_casefold(m_currentSearchString.utf8().data(), -1)); size_t prefixLength = strlen(searchStringWithCaseFolded.get()); // If a menu item has already been selected, start searching from the current // item down the list. This will make multiple key presses of the same character // advance the selection. GList* currentChild = children.get(); if (m_currentlySelectedMenuItem) { currentChild = g_list_find(children.get(), m_currentlySelectedMenuItem); if (!currentChild) { m_currentlySelectedMenuItem = nullptr; currentChild = children.get(); } // Repeating characters should iterate. if (repeatingCharacter) { if (GList* nextChild = g_list_next(currentChild)) currentChild = nextChild; } } GList* firstChild = currentChild; do { currentChild = g_list_next(currentChild); if (!currentChild) currentChild = children.get(); GUniquePtr<gchar> itemText(g_utf8_casefold(gtk_menu_item_get_label(GTK_MENU_ITEM(currentChild->data)), -1)); if (!strncmp(searchStringWithCaseFolded.get(), itemText.get(), prefixLength)) { gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup), GTK_WIDGET(currentChild->data)); break; } } while (currentChild != firstChild); return true; }
void WebPopupMenuProxyGtk::hidePopupMenu() { gtk_menu_popdown(GTK_MENU(m_popup)); resetTypeAheadFindState(); }
void WebPopupMenuProxyGtk::showPopupMenu(const IntRect& rect, TextDirection, double /* pageScaleFactor */, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex) { populatePopupMenu(items); gtk_menu_set_active(GTK_MENU(m_popup), selectedIndex); resetTypeAheadFindState(); IntPoint menuPosition = convertWidgetPointToScreenPoint(m_webView, rect.location()); menuPosition.move(0, rect.height()); // This approach follows the one in gtkcombobox.c. GtkRequisition requisition; gtk_widget_set_size_request(m_popup, -1, -1); gtk_widget_get_preferred_size(m_popup, &requisition, nullptr); gtk_widget_set_size_request(m_popup, std::max(rect.width(), requisition.width), -1); if (int itemCount = items.size()) { GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup))); int i; GList* child; for (i = 0, child = children.get(); i < itemCount; i++, child = g_list_next(child)) { if (i > selectedIndex) break; GtkWidget* item = GTK_WIDGET(child->data); GtkRequisition itemRequisition; gtk_widget_get_preferred_size(item, &itemRequisition, nullptr); menuPosition.setY(menuPosition.y() - itemRequisition.height); } } else { // Center vertically the empty popup in the combo box area. menuPosition.setY(menuPosition.y() - rect.height() / 2); } gulong unmapHandler = g_signal_connect(m_popup, "unmap", G_CALLBACK(menuUnmapped), this); const GdkEvent* event = m_client->currentlyProcessedMouseDownEvent() ? m_client->currentlyProcessedMouseDownEvent()->nativeEvent() : nullptr; gtk_menu_popup_for_device(GTK_MENU(m_popup), event ? gdk_event_get_device(event) : nullptr, nullptr, nullptr, [](GtkMenu*, gint* x, gint* y, gboolean* pushIn, gpointer userData) { // We can pass a pointer to the menuPosition local variable because the nested main loop ensures this is called in the function context. IntPoint* menuPosition = static_cast<IntPoint*>(userData); *x = menuPosition->x(); *y = menuPosition->y(); *pushIn = TRUE; }, &menuPosition, nullptr, event && event->type == GDK_BUTTON_PRESS ? event->button.button : 1, event ? gdk_event_get_time(event) : GDK_CURRENT_TIME); // Now that the menu has a position, schedule a resize to make sure it's resized to fit vertically in the work area. gtk_widget_queue_resize(m_popup); // PopupMenu can fail to open when there is no mouse grab. // Ensure WebCore does not go into some pesky state. if (!gtk_widget_get_visible(m_popup)) { m_client->failedToShowPopupMenu(); return; } // WebPageProxy expects the menu to run in a nested run loop, since it invalidates the // menu right after calling WebPopupMenuProxy::showPopupMenu(). m_runLoop = adoptGRef(g_main_loop_new(nullptr, FALSE)); // This is to suppress warnings about gdk_threads_leave and gdk_threads_enter. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" gdk_threads_leave(); g_main_loop_run(m_runLoop.get()); gdk_threads_enter(); #pragma GCC diagnostic pop m_runLoop.clear(); g_signal_handler_disconnect(m_popup, unmapHandler); if (!m_client) return; m_client->valueChangedForPopupMenu(this, m_activeItem); }
void GtkPopupMenu::popDown() { gtk_menu_popdown(GTK_MENU(m_popup.get())); resetTypeAheadFindState(); }