Changeset 232390 in webkit
- Timestamp:
- Jun 1, 2018 12:44:31 AM (6 years ago)
- Location:
- trunk/Source
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r232375 r232390 1 2018-06-01 Carlos Garcia Campos <cgarcia@igalia.com> 2 3 [GTK] Switch to use a popup window with a tree view instead of a menu for option menu default implementation 4 https://bugs.webkit.org/show_bug.cgi?id=186146 5 6 Reviewed by Michael Catanzaro. 7 8 Make it possible to use GUniquePtr with GtkTreePath. 9 10 * platform/gtk/GUniquePtrGtk.h: 11 1 12 2018-05-31 Per Arne Vollan <pvollan@apple.com> 2 13 -
trunk/Source/WebCore/platform/gtk/GUniquePtrGtk.h
r185502 r232390 28 28 WTF_DEFINE_GPTR_DELETER(GdkEvent, gdk_event_free) 29 29 WTF_DEFINE_GPTR_DELETER(GtkIconInfo, gtk_icon_info_free) 30 WTF_DEFINE_GPTR_DELETER(GtkTreePath, gtk_tree_path_free) 30 31 31 32 } // namespace WTF -
trunk/Source/WebKit/ChangeLog
r232375 r232390 1 2018-06-01 Carlos Garcia Campos <cgarcia@igalia.com> 2 3 [GTK] Switch to use a popup window with a tree view instead of a menu for option menu default implementation 4 https://bugs.webkit.org/show_bug.cgi?id=186146 5 6 Reviewed by Michael Catanzaro. 7 8 It's more convenient to use than the menu. 9 10 * UIProcess/API/gtk/WebKitPopupMenu.cpp: 11 (WebKit::menuCloseCallback): 12 (WebKit::WebKitPopupMenu::activateItem): 13 * UIProcess/API/gtk/WebKitPopupMenu.h: 14 * UIProcess/gtk/WebPopupMenuProxyGtk.cpp: 15 (WebKit::WebPopupMenuProxyGtk::WebPopupMenuProxyGtk): 16 (WebKit::WebPopupMenuProxyGtk::selectItem): 17 (WebKit::WebPopupMenuProxyGtk::activateItem): 18 (WebKit::WebPopupMenuProxyGtk::activateItemAtPath): 19 (WebKit::WebPopupMenuProxyGtk::treeViewRowActivatedCallback): 20 (WebKit::WebPopupMenuProxyGtk::treeViewButtonReleaseEventCallback): 21 (WebKit::WebPopupMenuProxyGtk::buttonPressEventCallback): 22 (WebKit::WebPopupMenuProxyGtk::keyPressEventCallback): 23 (WebKit::WebPopupMenuProxyGtk::createPopupMenu): 24 (WebKit::WebPopupMenuProxyGtk::show): 25 (WebKit::WebPopupMenuProxyGtk::showPopupMenu): 26 (WebKit::WebPopupMenuProxyGtk::hidePopupMenu): 27 (WebKit::WebPopupMenuProxyGtk::cancelTracking): 28 (WebKit::WebPopupMenuProxyGtk::typeAheadFindIndex): 29 (WebKit::WebPopupMenuProxyGtk::typeAheadFind): 30 * UIProcess/gtk/WebPopupMenuProxyGtk.h: 31 1 32 2018-05-31 Per Arne Vollan <pvollan@apple.com> 2 33 -
trunk/Source/WebKit/UIProcess/API/gtk/WebKitPopupMenu.cpp
r218325 r232390 36 36 static void menuCloseCallback(WebKitPopupMenu* popupMenu) 37 37 { 38 popupMenu->activateItem( -1);38 popupMenu->activateItem(std::nullopt); 39 39 } 40 40 … … 70 70 } 71 71 72 void WebKitPopupMenu:: selectItem(unsigneditemIndex)72 void WebKitPopupMenu::activateItem(std::optional<unsigned> itemIndex) 73 73 { 74 if (!m_menu) 75 return; 76 m_client->setTextFromItemForPopupMenu(this, itemIndex); 77 m_selectedItem = itemIndex; 78 } 79 80 void WebKitPopupMenu::activateItem(int32_t itemIndex) 81 { 82 if (!m_menu) 83 return; 84 m_client->valueChangedForPopupMenu(this, itemIndex == -1 ? m_selectedItem.value_or(-1) : itemIndex); 85 g_signal_handlers_disconnect_matched(m_menu.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); 86 m_menu = nullptr; 74 WebPopupMenuProxyGtk::activateItem(itemIndex); 75 if (m_menu) { 76 g_signal_handlers_disconnect_matched(m_menu.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); 77 m_menu = nullptr; 78 } 87 79 } 88 80 -
trunk/Source/WebKit/UIProcess/API/gtk/WebKitPopupMenu.h
r218325 r232390 35 35 ~WebKitPopupMenu() = default; 36 36 37 void selectItem(unsigned); 38 void activateItem(int32_t); 37 void activateItem(std::optional<unsigned> itemIndex) override; 39 38 40 39 private: … … 46 45 47 46 GRefPtr<WebKitOptionMenu> m_menu; 48 std::optional<unsigned> m_selectedItem;49 47 }; 50 48 -
trunk/Source/WebKit/UIProcess/gtk/WebPopupMenuProxyGtk.cpp
r228373 r232390 38 38 using namespace WebCore; 39 39 40 enum Columns { 41 Label, 42 Tooltip, 43 IsGroup, 44 IsSelected, 45 IsEnabled, 46 Index, 47 48 Count 49 }; 50 40 51 WebPopupMenuProxyGtk::WebPopupMenuProxyGtk(GtkWidget* webView, WebPopupMenuProxy::Client& client) 41 52 : WebPopupMenuProxy(client) 42 53 , m_webView(webView) 43 , m_dismissMenuTimer(RunLoop::main(), this, &WebPopupMenuProxyGtk::dismissMenuTimerFired)44 54 { 45 55 } … … 50 60 } 51 61 52 void WebPopupMenuProxyGtk::populatePopupMenu(const Vector<WebPopupItem>& items) 53 { 54 int itemIndex = 0; 62 void WebPopupMenuProxyGtk::selectItem(unsigned itemIndex) 63 { 64 if (m_client) 65 m_client->setTextFromItemForPopupMenu(this, itemIndex); 66 m_selectedItem = itemIndex; 67 } 68 69 void WebPopupMenuProxyGtk::activateItem(std::optional<unsigned> itemIndex) 70 { 71 if (m_client) 72 m_client->valueChangedForPopupMenu(this, itemIndex.value_or(m_selectedItem.value_or(-1))); 73 } 74 75 bool WebPopupMenuProxyGtk::activateItemAtPath(GtkTreePath* path) 76 { 77 auto* model = gtk_tree_view_get_model(GTK_TREE_VIEW(m_treeView)); 78 GtkTreeIter iter; 79 gtk_tree_model_get_iter(model, &iter, path); 80 gboolean isGroup, isEnabled; 81 guint index; 82 gtk_tree_model_get(model, &iter, Columns::IsGroup, &isGroup, Columns::IsEnabled, &isEnabled, Columns::Index, &index, -1); 83 if (isGroup || !isEnabled) 84 return false; 85 86 activateItem(index); 87 hidePopupMenu(); 88 return true; 89 } 90 91 void WebPopupMenuProxyGtk::treeViewRowActivatedCallback(GtkTreeView*, GtkTreePath* path, GtkTreeViewColumn*, WebPopupMenuProxyGtk* popupMenu) 92 { 93 popupMenu->activateItemAtPath(path); 94 } 95 96 gboolean WebPopupMenuProxyGtk::treeViewButtonReleaseEventCallback(GtkWidget* treeView, GdkEventButton* event, WebPopupMenuProxyGtk* popupMenu) 97 { 98 if (event->button != GDK_BUTTON_PRIMARY) 99 return FALSE; 100 101 GUniqueOutPtr<GtkTreePath> path; 102 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeView), event->x, event->y, &path.outPtr(), nullptr, nullptr, nullptr)) 103 return FALSE; 104 105 return popupMenu->activateItemAtPath(path.get()); 106 } 107 108 gboolean WebPopupMenuProxyGtk::buttonPressEventCallback(GtkWidget* widget, GdkEventButton* event, WebPopupMenuProxyGtk* popupMenu) 109 { 110 if (!popupMenu->m_device) 111 return FALSE; 112 113 popupMenu->hidePopupMenu(); 114 return TRUE; 115 } 116 117 gboolean WebPopupMenuProxyGtk::keyPressEventCallback(GtkWidget* widget, GdkEventKey* event, WebPopupMenuProxyGtk* popupMenu) 118 { 119 if (!popupMenu->m_device) 120 return FALSE; 121 122 if (event->keyval == GDK_KEY_Escape) { 123 popupMenu->hidePopupMenu(); 124 return TRUE; 125 } 126 127 if (popupMenu->typeAheadFind(event)) 128 return TRUE; 129 130 // Forward the event to the tree view. 131 gtk_widget_event(popupMenu->m_treeView, reinterpret_cast<GdkEvent*>(event)); 132 return TRUE; 133 } 134 135 void WebPopupMenuProxyGtk::createPopupMenu(const Vector<WebPopupItem>& items, int32_t selectedIndex) 136 { 137 ASSERT(!m_popup); 138 139 GRefPtr<GtkTreeStore> model = adoptGRef(gtk_tree_store_new(Columns::Count, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_UINT)); 140 GtkTreeIter parentIter; 141 unsigned index = 0; 142 m_paths.reserveInitialCapacity(items.size()); 55 143 for (const auto& item : items) { 56 if (item.m_type == WebPopupItem::Separator) { 57 GtkWidget* menuItem = gtk_separator_menu_item_new(); 58 gtk_menu_shell_append(GTK_MENU_SHELL(m_popup), menuItem); 59 gtk_widget_show(menuItem); 144 if (item.m_isLabel) { 145 gtk_tree_store_insert_with_values(model.get(), &parentIter, nullptr, -1, 146 Columns::Label, item.m_text.stripWhiteSpace().utf8().data(), 147 Columns::IsGroup, TRUE, 148 Columns::IsEnabled, TRUE, 149 -1); 150 // We never need the path for group labels. 151 m_paths.uncheckedAppend(nullptr); 60 152 } else { 61 GtkWidget* menuItem = gtk_menu_item_new_with_label(item.m_text.utf8().data()); 62 gtk_widget_set_tooltip_text(menuItem, item.m_toolTip.utf8().data()); 63 gtk_widget_set_sensitive(menuItem, item.m_isEnabled); 64 g_object_set_data(G_OBJECT(menuItem), "popup-menu-item-index", GINT_TO_POINTER(itemIndex)); 65 g_signal_connect(menuItem, "activate", G_CALLBACK(menuItemActivated), this); 66 g_signal_connect(menuItem, "select", G_CALLBACK(selectItemCallback), this); 67 gtk_menu_shell_append(GTK_MENU_SHELL(m_popup), menuItem); 68 gtk_widget_show(menuItem); 153 GtkTreeIter iter; 154 bool isSelected = selectedIndex && static_cast<unsigned>(selectedIndex) == index; 155 gtk_tree_store_insert_with_values(model.get(), &iter, item.m_text.startsWith(" ") ? &parentIter : nullptr, -1, 156 Columns::Label, item.m_text.stripWhiteSpace().utf8().data(), 157 Columns::Tooltip, item.m_toolTip.isEmpty() ? nullptr : item.m_toolTip.utf8().data(), 158 Columns::IsGroup, FALSE, 159 Columns::IsSelected, isSelected, 160 Columns::IsEnabled, item.m_isEnabled, 161 Columns::Index, index, 162 -1); 163 if (isSelected) { 164 ASSERT(!m_selectedItem); 165 m_selectedItem = index; 166 } 167 m_paths.uncheckedAppend(GUniquePtr<GtkTreePath>(gtk_tree_model_get_path(GTK_TREE_MODEL(model.get()), &iter))); 69 168 } 70 itemIndex++; 71 } 169 index++; 170 } 171 172 m_treeView = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model.get())); 173 auto* treeView = GTK_TREE_VIEW(m_treeView); 174 g_signal_connect(treeView, "row-activated", G_CALLBACK(treeViewRowActivatedCallback), this); 175 g_signal_connect_after(treeView, "button-release-event", G_CALLBACK(treeViewButtonReleaseEventCallback), this); 176 gtk_tree_view_set_tooltip_column(treeView, Columns::Tooltip); 177 gtk_tree_view_set_show_expanders(treeView, FALSE); 178 gtk_tree_view_set_level_indentation(treeView, 12); 179 gtk_tree_view_set_enable_search(treeView, FALSE); 180 gtk_tree_view_set_activate_on_single_click(treeView, TRUE); 181 gtk_tree_view_set_hover_selection(treeView, TRUE); 182 gtk_tree_view_set_headers_visible(treeView, FALSE); 183 gtk_tree_view_insert_column_with_data_func(treeView, 0, nullptr, gtk_cell_renderer_text_new(), [](GtkTreeViewColumn*, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, gpointer) { 184 GUniqueOutPtr<char> label; 185 gboolean isGroup, isEnabled; 186 gtk_tree_model_get(model, iter, Columns::Label, &label.outPtr(), Columns::IsGroup, &isGroup, Columns::IsEnabled, &isEnabled, -1); 187 if (isGroup) { 188 GUniquePtr<char> markup(g_strdup_printf("<b>%s</b>", label.get())); 189 g_object_set(renderer, "markup", markup.get(), nullptr); 190 } else 191 g_object_set(renderer, "text", label.get(), "sensitive", isEnabled, nullptr); 192 }, nullptr, nullptr); 193 gtk_tree_view_expand_all(treeView); 194 195 auto* selection = gtk_tree_view_get_selection(treeView); 196 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); 197 gtk_tree_selection_unselect_all(selection); 198 gtk_tree_selection_set_select_function(selection, [](GtkTreeSelection*, GtkTreeModel* model, GtkTreePath* path, gboolean selected, gpointer) -> gboolean { 199 GtkTreeIter iter; 200 gtk_tree_model_get_iter(model, &iter, path); 201 gboolean isGroup, isEnabled; 202 gtk_tree_model_get(model, &iter, Columns::IsGroup, &isGroup, Columns::IsEnabled, &isEnabled, -1); 203 return !isGroup && isEnabled; 204 }, nullptr, nullptr); 205 206 auto* swindow = gtk_scrolled_window_new(nullptr, nullptr); 207 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); 208 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swindow), GTK_SHADOW_ETCHED_IN); 209 gtk_container_add(GTK_CONTAINER(swindow), m_treeView); 210 gtk_widget_show(m_treeView); 211 212 m_popup = gtk_window_new(GTK_WINDOW_POPUP); 213 g_signal_connect(m_popup, "button-press-event", G_CALLBACK(buttonPressEventCallback), this); 214 g_signal_connect(m_popup, "key-press-event", G_CALLBACK(keyPressEventCallback), this); 215 gtk_window_set_type_hint(GTK_WINDOW(m_popup), GDK_WINDOW_TYPE_HINT_COMBO); 216 gtk_window_set_resizable(GTK_WINDOW(m_popup), FALSE); 217 gtk_container_add(GTK_CONTAINER(m_popup), swindow); 218 gtk_widget_show(swindow); 219 } 220 221 void WebPopupMenuProxyGtk::show() 222 { 223 if (m_selectedItem) { 224 auto& selectedPath = m_paths[m_selectedItem.value()]; 225 ASSERT(selectedPath); 226 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(m_treeView), selectedPath.get(), nullptr, TRUE, 0.5, 0); 227 gtk_tree_view_set_cursor(GTK_TREE_VIEW(m_treeView), selectedPath.get(), nullptr, FALSE); 228 } 229 gtk_widget_grab_focus(m_treeView); 230 gtk_widget_show(m_popup); 72 231 } 73 232 74 233 void WebPopupMenuProxyGtk::showPopupMenu(const IntRect& rect, TextDirection, double /* pageScaleFactor */, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex) 75 234 { 76 ASSERT(!m_popup); 77 m_popup = gtk_menu_new(); 78 g_signal_connect(m_popup, "key-press-event", G_CALLBACK(keyPressEventCallback), this); 79 g_signal_connect(m_popup, "unmap", G_CALLBACK(menuUnmappedCallback), this); 80 81 populatePopupMenu(items); 82 gtk_menu_set_active(GTK_MENU(m_popup), selectedIndex); 83 84 resetTypeAheadFindState(); 85 235 createPopupMenu(items, selectedIndex); 236 ASSERT(m_popup); 237 238 GtkRequisition treeViewRequisition; 239 gtk_widget_get_preferred_size(m_treeView, &treeViewRequisition, nullptr); 240 auto* column = gtk_tree_view_get_column(GTK_TREE_VIEW(m_treeView), Columns::Label); 241 gint itemHeight; 242 gtk_tree_view_column_cell_get_size(column, nullptr, nullptr, nullptr, nullptr, &itemHeight); 243 gint verticalSeparator; 244 gtk_widget_style_get(m_treeView, "vertical-separator", &verticalSeparator, nullptr); 245 itemHeight += verticalSeparator; 246 if (!itemHeight) 247 return; 248 249 auto* display = gtk_widget_get_display(m_webView); 250 auto* monitor = gdk_display_get_monitor_at_window(display, gtk_widget_get_window(m_webView)); 251 GdkRectangle area; 252 gdk_monitor_get_workarea(monitor, &area); 253 int width = std::min(rect.width(), area.width); 254 size_t itemCount = std::min<size_t>(items.size(), (area.height / 3) / itemHeight); 255 256 auto* swindow = GTK_SCROLLED_WINDOW(gtk_bin_get_child(GTK_BIN(m_popup))); 257 // Disable scrollbars when there's only one item to ensure the scrolled window doesn't take them into account when calculating its minimum size. 258 gtk_scrolled_window_set_policy(swindow, GTK_POLICY_NEVER, itemCount > 1 ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER); 259 gtk_widget_realize(m_treeView); 260 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(m_treeView)); 261 gtk_scrolled_window_set_min_content_width(swindow, width); 262 gtk_widget_set_size_request(m_popup, width, -1); 263 gtk_scrolled_window_set_min_content_height(swindow, itemCount * itemHeight); 264 265 GtkRequisition menuRequisition; 266 gtk_widget_get_preferred_size(m_popup, &menuRequisition, nullptr); 86 267 IntPoint menuPosition = convertWidgetPointToScreenPoint(m_webView, rect.location()); 87 menuPosition.move(0, rect.height()); 88 89 // This approach follows the one in gtkcombobox.c. 90 GtkRequisition requisition; 91 gtk_widget_set_size_request(m_popup, -1, -1); 92 gtk_widget_get_preferred_size(m_popup, &requisition, nullptr); 93 gtk_widget_set_size_request(m_popup, std::max(rect.width(), requisition.width), -1); 94 95 if (int itemCount = items.size()) { 96 GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup))); 97 int i; 98 GList* child; 99 for (i = 0, child = children.get(); i < itemCount; i++, child = g_list_next(child)) { 100 if (i > selectedIndex) 101 break; 102 103 GtkWidget* item = GTK_WIDGET(child->data); 104 GtkRequisition itemRequisition; 105 gtk_widget_get_preferred_size(item, &itemRequisition, nullptr); 106 menuPosition.setY(menuPosition.y() - itemRequisition.height); 107 } 108 } else { 109 // Center vertically the empty popup in the combo box area. 110 menuPosition.setY(menuPosition.y() - rect.height() / 2); 111 } 112 113 gtk_menu_attach_to_widget(GTK_MENU(m_popup), GTK_WIDGET(m_webView), nullptr); 268 // FIXME: We can't ensure the menu will be on screen in Wayland. 269 // https://blog.gtk.org/2016/07/15/future-of-relative-window-positioning/ 270 // https://gitlab.gnome.org/GNOME/gtk/issues/997 271 if (menuPosition.x() + menuRequisition.width > area.x + area.width) 272 menuPosition.setX(area.x + area.width - menuRequisition.width); 273 274 if (menuPosition.y() + rect.height() + menuRequisition.height <= area.y + area.height 275 || menuPosition.y() - area.y < (area.y + area.height) - (menuPosition.y() + rect.height())) 276 menuPosition.move(0, rect.height()); 277 else 278 menuPosition.move(0, -menuRequisition.height); 279 gtk_window_move(GTK_WINDOW(m_popup), menuPosition.x(), menuPosition.y()); 280 281 auto* toplevel = gtk_widget_get_toplevel(m_webView); 282 if (GTK_IS_WINDOW(toplevel)) { 283 gtk_window_set_transient_for(GTK_WINDOW(m_popup), GTK_WINDOW(toplevel)); 284 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(toplevel)), GTK_WINDOW(m_popup)); 285 } 286 gtk_window_set_attached_to(GTK_WINDOW(m_popup), m_webView); 287 gtk_window_set_screen(GTK_WINDOW(m_popup), gtk_widget_get_screen(m_webView)); 114 288 115 289 const GdkEvent* event = m_client->currentlyProcessedMouseDownEvent() ? m_client->currentlyProcessedMouseDownEvent()->nativeEvent() : nullptr; 116 gtk_menu_popup_for_device(GTK_MENU(m_popup), event ? gdk_event_get_device(event) : nullptr, nullptr, nullptr, 117 [](GtkMenu*, gint* x, gint* y, gboolean* pushIn, gpointer userData) { 118 // We can pass a pointer to the menuPosition local variable because the nested main loop ensures this is called in the function context. 119 IntPoint* menuPosition = static_cast<IntPoint*>(userData); 120 *x = menuPosition->x(); 121 *y = menuPosition->y(); 122 *pushIn = menuPosition->y() < 0; 123 }, &menuPosition, nullptr, event && event->type == GDK_BUTTON_PRESS ? event->button.button : 1, 124 event ? gdk_event_get_time(event) : GDK_CURRENT_TIME); 125 126 // Now that the menu has a position, schedule a resize to make sure it's resized to fit vertically in the work area. 127 gtk_widget_queue_resize(m_popup); 290 m_device = event ? gdk_event_get_device(event) : nullptr; 291 if (!m_device) 292 m_device = gtk_get_current_event_device(); 293 if (m_device && gdk_device_get_display(m_device) != display) 294 m_device = nullptr; 295 if (!m_device) 296 m_device = gdk_seat_get_pointer(gdk_display_get_default_seat(display)); 297 ASSERT(m_device); 298 if (gdk_device_get_source(m_device) == GDK_SOURCE_KEYBOARD) 299 m_device = gdk_device_get_associated_device(m_device); 300 301 #if GTK_CHECK_VERSION(3, 20, 0) 302 gtk_grab_add(m_popup); 303 auto grabResult = gdk_seat_grab(gdk_device_get_seat(m_device), gtk_widget_get_window(m_popup), GDK_SEAT_CAPABILITY_ALL, TRUE, nullptr, nullptr, [](GdkSeat*, GdkWindow*, gpointer userData) { 304 static_cast<WebPopupMenuProxyGtk*>(userData)->show(); 305 }, this); 306 #else 307 gtk_device_grab_add(m_popup, m_device, TRUE); 308 auto grabResult = gdk_device_grab(m_device, gtk_widget_get_window(m_popup), GDK_OWNERSHIP_WINDOW, TRUE, 309 static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK), nullptr, GDK_CURRENT_TIME); 310 show(); 311 #endif 128 312 129 313 // PopupMenu can fail to open when there is no mouse grab. 130 314 // Ensure WebCore does not go into some pesky state. 131 if ( !gtk_widget_get_visible(m_popup)) {315 if (grabResult != GDK_GRAB_SUCCESS) { 132 316 m_client->failedToShowPopupMenu(); 133 317 return; 134 318 } 135 136 // This ensures that the active item gets selected after popping up the menu, and137 // as it says in "gtkcombobox.c" (line ~1606): it's ugly, but gets the job done.138 GtkWidget* activeChild = gtk_menu_get_active(GTK_MENU(m_popup));139 if (activeChild && gtk_widget_get_visible(activeChild))140 gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup), activeChild);141 319 } 142 320 … … 146 324 return; 147 325 148 gtk_menu_popdown(GTK_MENU(m_popup)); 149 resetTypeAheadFindState(); 326 if (m_device) { 327 #if GTK_CHECK_VERSION(3, 20, 0) 328 gdk_seat_ungrab(gdk_device_get_seat(m_device)); 329 gtk_grab_remove(m_popup); 330 #else 331 gdk_device_ungrab(m_device, GDK_CURRENT_TIME); 332 gtk_device_grab_remove(m_popup, m_device); 333 #endif 334 gtk_window_set_transient_for(GTK_WINDOW(m_popup), nullptr); 335 gtk_window_set_attached_to(GTK_WINDOW(m_popup), nullptr); 336 m_device = nullptr; 337 } 338 339 activateItem(std::nullopt); 340 341 if (m_currentSearchString) { 342 g_string_free(m_currentSearchString, TRUE); 343 m_currentSearchString = nullptr; 344 } 345 346 gtk_widget_destroy(m_popup); 347 m_popup = nullptr; 150 348 } 151 349 … … 155 353 return; 156 354 157 m_dismissMenuTimer.stop();158 355 g_signal_handlers_disconnect_matched(m_popup, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); 159 356 hidePopupMenu(); 160 gtk_widget_destroy(m_popup); 161 m_popup = nullptr; 357 } 358 359 std::optional<unsigned> WebPopupMenuProxyGtk::typeAheadFindIndex(GdkEventKey* event) 360 { 361 gunichar keychar = gdk_keyval_to_unicode(event->keyval); 362 if (!g_unichar_isprint(keychar)) 363 return std::nullopt; 364 365 if (event->time < m_previousKeyEventTime) 366 return std::nullopt; 367 368 static const uint32_t typeaheadTimeoutMs = 1000; 369 if (event->time - m_previousKeyEventTime > typeaheadTimeoutMs) { 370 if (m_currentSearchString) 371 g_string_truncate(m_currentSearchString, 0); 372 } 373 m_previousKeyEventTime = event->time; 374 375 if (!m_currentSearchString) 376 m_currentSearchString = g_string_new(nullptr); 377 g_string_append_unichar(m_currentSearchString, keychar); 378 379 int prefixLength = -1; 380 if (keychar == m_repeatingCharacter) 381 prefixLength = 1; 382 else 383 m_repeatingCharacter = m_currentSearchString->len == 1 ? keychar : '\0'; 384 385 GtkTreeModel* model; 386 GtkTreeIter iter; 387 guint selectedIndex = 0; 388 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeView)), &model, &iter)) 389 gtk_tree_model_get(model, &iter, Columns::Index, &selectedIndex, -1); 390 391 unsigned index = selectedIndex; 392 if (m_repeatingCharacter != '\0') 393 index++; 394 auto itemCount = m_paths.size(); 395 index %= itemCount; 396 397 GUniquePtr<char> normalizedPrefix(g_utf8_normalize(m_currentSearchString->str, prefixLength, G_NORMALIZE_ALL)); 398 GUniquePtr<char> prefix(normalizedPrefix ? g_utf8_casefold(normalizedPrefix.get(), -1) : nullptr); 399 if (!prefix) 400 return std::nullopt; 401 402 model = gtk_tree_view_get_model(GTK_TREE_VIEW(m_treeView)); 403 for (unsigned i = 0; i < itemCount; i++, index = (index + 1) % itemCount) { 404 auto& path = m_paths[index]; 405 if (!path || !gtk_tree_model_get_iter(model, &iter, path.get())) 406 continue; 407 408 GUniqueOutPtr<char> label; 409 gboolean isEnabled; 410 gtk_tree_model_get(model, &iter, Columns::Label, &label.outPtr(), Columns::IsEnabled, &isEnabled, -1); 411 if (!isEnabled) 412 continue; 413 414 GUniquePtr<char> normalizedText(g_utf8_normalize(label.get(), -1, G_NORMALIZE_ALL)); 415 GUniquePtr<char> text(normalizedText ? g_utf8_casefold(normalizedText.get(), -1) : nullptr); 416 if (!text) 417 continue; 418 419 if (!strncmp(prefix.get(), text.get(), strlen(prefix.get()))) 420 return index; 421 } 422 423 return std::nullopt; 162 424 } 163 425 164 426 bool WebPopupMenuProxyGtk::typeAheadFind(GdkEventKey* event) 165 427 { 166 // If we were given a non-printable character just skip it. 167 gunichar unicodeCharacter = gdk_keyval_to_unicode(event->keyval); 168 if (!g_unichar_isprint(unicodeCharacter)) { 169 resetTypeAheadFindState(); 428 auto searchIndex = typeAheadFindIndex(event); 429 if (!searchIndex) 170 430 return false; 171 } 172 173 glong charactersWritten; 174 GUniquePtr<gunichar2> utf16String(g_ucs4_to_utf16(&unicodeCharacter, 1, nullptr, &charactersWritten, nullptr)); 175 if (!utf16String) { 176 resetTypeAheadFindState(); 177 return false; 178 } 179 180 // If the character is the same as the last character, the user is probably trying to 181 // cycle through the menulist entries. This matches the WebCore behavior for collapsed menulists. 182 static const uint32_t searchTimeoutMs = 1000; 183 bool repeatingCharacter = unicodeCharacter != m_previousKeyEventCharacter; 184 if (event->time - m_previousKeyEventTimestamp > searchTimeoutMs) 185 m_currentSearchString = String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten); 186 else if (repeatingCharacter) 187 m_currentSearchString.append(String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten)); 188 189 m_previousKeyEventTimestamp = event->time; 190 m_previousKeyEventCharacter = unicodeCharacter; 191 192 GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup))); 193 if (!children) 194 return true; 195 196 // We case fold before searching, because strncmp does not handle non-ASCII characters. 197 GUniquePtr<gchar> searchStringWithCaseFolded(g_utf8_casefold(m_currentSearchString.utf8().data(), -1)); 198 size_t prefixLength = strlen(searchStringWithCaseFolded.get()); 199 200 // If a menu item has already been selected, start searching from the current 201 // item down the list. This will make multiple key presses of the same character 202 // advance the selection. 203 GList* currentChild = children.get(); 204 if (m_currentlySelectedMenuItem) { 205 currentChild = g_list_find(children.get(), m_currentlySelectedMenuItem); 206 if (!currentChild) { 207 m_currentlySelectedMenuItem = nullptr; 208 currentChild = children.get(); 209 } 210 211 // Repeating characters should iterate. 212 if (repeatingCharacter) { 213 if (GList* nextChild = g_list_next(currentChild)) 214 currentChild = nextChild; 215 } 216 } 217 218 GList* firstChild = currentChild; 219 do { 220 currentChild = g_list_next(currentChild); 221 if (!currentChild) 222 currentChild = children.get(); 223 224 GUniquePtr<gchar> itemText(g_utf8_casefold(gtk_menu_item_get_label(GTK_MENU_ITEM(currentChild->data)), -1)); 225 if (!strncmp(searchStringWithCaseFolded.get(), itemText.get(), prefixLength)) { 226 gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup), GTK_WIDGET(currentChild->data)); 227 break; 228 } 229 } while (currentChild != firstChild); 431 432 auto& path = m_paths[searchIndex.value()]; 433 ASSERT(path); 434 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(m_treeView), path.get(), nullptr, TRUE, 0.5, 0); 435 gtk_tree_view_set_cursor(GTK_TREE_VIEW(m_treeView), path.get(), nullptr, FALSE); 436 selectItem(searchIndex.value()); 230 437 231 438 return true; 232 439 } 233 440 234 void WebPopupMenuProxyGtk::resetTypeAheadFindState()235 {236 m_currentlySelectedMenuItem = nullptr;237 m_previousKeyEventCharacter = 0;238 m_previousKeyEventTimestamp = 0;239 m_currentSearchString = emptyString();240 }241 242 void WebPopupMenuProxyGtk::menuItemActivated(GtkMenuItem* menuItem, WebPopupMenuProxyGtk* popupMenu)243 {244 popupMenu->m_dismissMenuTimer.stop();245 if (popupMenu->m_client)246 popupMenu->m_client->valueChangedForPopupMenu(popupMenu, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuItem), "popup-menu-item-index")));247 }248 249 void WebPopupMenuProxyGtk::dismissMenuTimerFired()250 {251 if (m_client)252 m_client->valueChangedForPopupMenu(this, -1);253 }254 255 void WebPopupMenuProxyGtk::menuUnmappedCallback(GtkWidget*, WebPopupMenuProxyGtk* popupMenu)256 {257 if (!popupMenu->m_client)258 return;259 260 // When an item is activated, the menu is first hidden and then activate signal is emitted, so at this point we don't know261 // if the menu has been hidden because an item has been selected or because the menu has been dismissed. Wait until the next262 // main loop iteration to dismiss the menu, if an item is activated the timer will be cancelled.263 popupMenu->m_dismissMenuTimer.startOneShot(0_s);264 }265 266 void WebPopupMenuProxyGtk::selectItemCallback(GtkWidget* item, WebPopupMenuProxyGtk* popupMenu)267 {268 popupMenu->setCurrentlySelectedMenuItem(item);269 }270 271 gboolean WebPopupMenuProxyGtk::keyPressEventCallback(GtkWidget*, GdkEventKey* event, WebPopupMenuProxyGtk* popupMenu)272 {273 return popupMenu->typeAheadFind(event);274 }275 276 441 } // namespace WebKit -
trunk/Source/WebKit/UIProcess/gtk/WebPopupMenuProxyGtk.h
r218329 r232390 18 18 */ 19 19 20 #ifndef WebPopupMenuProxyGtk_h 21 #define WebPopupMenuProxyGtk_h 20 #pragma once 22 21 23 22 #include "WebPopupMenuProxy.h" 24 #include <wtf/RunLoop.h> 23 #include <WebCore/GUniquePtrGtk.h> 24 #include <wtf/Vector.h> 25 25 #include <wtf/glib/GRefPtr.h> 26 26 #include <wtf/text/WTFString.h> 27 27 28 28 typedef struct _GMainLoop GMainLoop; 29 typedef struct _GdkDevice GdkDevice; 30 typedef struct _GdkEventButton GdkEventButton; 29 31 typedef struct _GdkEventKey GdkEventKey; 32 typedef struct _GtkTreePath GtkTreePath; 33 typedef struct _GtkTreeView GtkTreeView; 34 typedef struct _GtkTreeViewColumn GtkTreeViewColumn; 30 35 31 36 namespace WebCore { … … 49 54 void cancelTracking() override; 50 55 56 virtual void selectItem(unsigned itemIndex); 57 virtual void activateItem(std::optional<unsigned> itemIndex); 58 51 59 protected: 52 60 WebPopupMenuProxyGtk(GtkWidget*, WebPopupMenuProxy::Client&); … … 55 63 56 64 private: 57 void setCurrentlySelectedMenuItem(GtkWidget* item) { m_currentlySelectedMenuItem = item; } 58 void populatePopupMenu(const Vector<WebPopupItem>&); 59 void dismissMenuTimerFired(); 65 void createPopupMenu(const Vector<WebPopupItem>&, int32_t selectedIndex); 66 void show(); 67 bool activateItemAtPath(GtkTreePath*); 68 std::optional<unsigned> typeAheadFindIndex(GdkEventKey*); 69 bool typeAheadFind(GdkEventKey*); 60 70 61 bool typeAheadFind(GdkEventKey*); 62 void resetTypeAheadFindState(); 63 64 static void menuItemActivated(GtkMenuItem*, WebPopupMenuProxyGtk*); 65 static void selectItemCallback(GtkWidget*, WebPopupMenuProxyGtk*); 71 static gboolean buttonPressEventCallback(GtkWidget*, GdkEventButton*, WebPopupMenuProxyGtk*); 66 72 static gboolean keyPressEventCallback(GtkWidget*, GdkEventKey*, WebPopupMenuProxyGtk*); 67 static void menuUnmappedCallback(GtkWidget*, WebPopupMenuProxyGtk*); 73 static void treeViewRowActivatedCallback(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, WebPopupMenuProxyGtk*); 74 static gboolean treeViewButtonReleaseEventCallback(GtkWidget*, GdkEventButton*, WebPopupMenuProxyGtk*); 68 75 69 76 GtkWidget* m_popup { nullptr }; 77 GtkWidget* m_treeView { nullptr }; 78 GdkDevice* m_device { nullptr }; 70 79 71 RunLoop::Timer<WebPopupMenuProxyGtk> m_dismissMenuTimer; 80 Vector<GUniquePtr<GtkTreePath>> m_paths; 81 std::optional<unsigned> m_selectedItem; 72 82 73 83 // Typeahead find. 74 unsigned m_previousKeyEventCharacter { 0 }; 75 uint32_t m_previousKeyEventTimestamp { 0 }; 76 GtkWidget* m_currentlySelectedMenuItem { nullptr }; 77 String m_currentSearchString; 84 gunichar m_repeatingCharacter { '\0' }; 85 uint32_t m_previousKeyEventTime { 0 }; 86 GString* m_currentSearchString { nullptr }; 78 87 }; 79 88 80 89 } // namespace WebKit 81 82 83 #endif // WebPopupMenuProxyGtk_h
Note: See TracChangeset
for help on using the changeset viewer.