1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/gtk/menu_gtk.h" 6 7 #include <map> 8 9 #include "base/bind.h" 10 #include "base/i18n/rtl.h" 11 #include "base/logging.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/stl_util.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "chrome/app/chrome_command_ids.h" 16 #include "chrome/browser/ui/gtk/event_utils.h" 17 #include "chrome/browser/ui/gtk/gtk_custom_menu.h" 18 #include "chrome/browser/ui/gtk/gtk_custom_menu_item.h" 19 #include "chrome/browser/ui/gtk/gtk_util.h" 20 #include "third_party/skia/include/core/SkBitmap.h" 21 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h" 22 #include "ui/base/accelerators/platform_accelerator_gtk.h" 23 #include "ui/base/models/button_menu_item_model.h" 24 #include "ui/base/models/menu_model.h" 25 #include "ui/base/window_open_disposition.h" 26 #include "ui/gfx/gtk_util.h" 27 #include "ui/gfx/image/image.h" 28 29 bool MenuGtk::block_activation_ = false; 30 31 namespace { 32 33 // Sets the ID of a menu item. 34 void SetMenuItemID(GtkWidget* menu_item, int menu_id) { 35 DCHECK_GE(menu_id, 0); 36 37 // Add 1 to the menu_id to avoid setting zero (null) to "menu-id". 38 g_object_set_data(G_OBJECT(menu_item), "menu-id", 39 GINT_TO_POINTER(menu_id + 1)); 40 } 41 42 // Gets the ID of a menu item. 43 // Returns true if the menu item has an ID. 44 bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) { 45 gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id"); 46 if (id_ptr != NULL) { 47 *menu_id = GPOINTER_TO_INT(id_ptr) - 1; 48 return true; 49 } 50 51 return false; 52 } 53 54 ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) { 55 return reinterpret_cast<ui::MenuModel*>( 56 g_object_get_data(G_OBJECT(menu_item), "model")); 57 } 58 59 void SetUpButtonShowHandler(GtkWidget* button, 60 ui::ButtonMenuItemModel* model, 61 int index) { 62 g_object_set_data(G_OBJECT(button), "button-model", 63 model); 64 g_object_set_data(G_OBJECT(button), "button-model-id", 65 GINT_TO_POINTER(index)); 66 } 67 68 void OnSubmenuShowButtonImage(GtkWidget* widget, GtkButton* button) { 69 MenuGtk::Delegate* delegate = reinterpret_cast<MenuGtk::Delegate*>( 70 g_object_get_data(G_OBJECT(button), "menu-gtk-delegate")); 71 int icon_idr = GPOINTER_TO_INT(g_object_get_data( 72 G_OBJECT(button), "button-image-idr")); 73 74 GtkIconSet* icon_set = delegate->GetIconSetForId(icon_idr); 75 if (icon_set) { 76 gtk_button_set_image( 77 button, gtk_image_new_from_icon_set(icon_set, 78 GTK_ICON_SIZE_MENU)); 79 } 80 } 81 82 void SetupImageIcon(GtkWidget* button, 83 GtkWidget* menu, 84 int icon_idr, 85 MenuGtk::Delegate* menu_gtk_delegate) { 86 g_object_set_data(G_OBJECT(button), "button-image-idr", 87 GINT_TO_POINTER(icon_idr)); 88 g_object_set_data(G_OBJECT(button), "menu-gtk-delegate", 89 menu_gtk_delegate); 90 91 g_signal_connect(menu, "show", G_CALLBACK(OnSubmenuShowButtonImage), button); 92 } 93 94 // Popup menus may get squished if they open up too close to the bottom of the 95 // screen. This function takes the size of the screen, the size of the menu, 96 // an optional widget, the Y position of the mouse click, and adjusts the popup 97 // menu's Y position to make it fit if it's possible to do so. 98 // Returns the new Y position of the popup menu. 99 int CalculateMenuYPosition(const GdkRectangle* screen_rect, 100 const GtkRequisition* menu_req, 101 GtkWidget* widget, const int y) { 102 CHECK(screen_rect); 103 CHECK(menu_req); 104 // If the menu would run off the bottom of the screen, and there is enough 105 // screen space upwards to accommodate the menu, then pop upwards. If there 106 // is a widget, then also move the anchor point to the top of the widget 107 // rather than the bottom. 108 const int screen_top = screen_rect->y; 109 const int screen_bottom = screen_rect->y + screen_rect->height; 110 const int menu_bottom = y + menu_req->height; 111 int alternate_y = y - menu_req->height; 112 if (widget) { 113 GtkAllocation allocation; 114 gtk_widget_get_allocation(widget, &allocation); 115 alternate_y -= allocation.height; 116 } 117 if (menu_bottom >= screen_bottom && alternate_y >= screen_top) 118 return alternate_y; 119 return y; 120 } 121 122 } // namespace 123 124 bool MenuGtk::Delegate::AlwaysShowIconForCmd(int command_id) const { 125 return false; 126 } 127 128 GtkIconSet* MenuGtk::Delegate::GetIconSetForId(int idr) { return NULL; } 129 130 GtkWidget* MenuGtk::Delegate::GetDefaultImageForCommandId(int command_id) { 131 const char* stock; 132 switch (command_id) { 133 case IDC_NEW_TAB: 134 case IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB: 135 case IDC_CONTENT_CONTEXT_SEARCHWEBFORIMAGE: 136 case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB: 137 case IDC_CONTENT_CONTEXT_OPENAVNEWTAB: 138 stock = GTK_STOCK_NEW; 139 break; 140 141 case IDC_CLOSE_TAB: 142 stock = GTK_STOCK_CLOSE; 143 break; 144 145 case IDC_CONTENT_CONTEXT_SAVEIMAGEAS: 146 case IDC_CONTENT_CONTEXT_SAVEAVAS: 147 case IDC_CONTENT_CONTEXT_SAVELINKAS: 148 stock = GTK_STOCK_SAVE_AS; 149 break; 150 151 case IDC_SAVE_PAGE: 152 stock = GTK_STOCK_SAVE; 153 break; 154 155 case IDC_COPY: 156 case IDC_CONTENT_CONTEXT_COPYIMAGELOCATION: 157 case IDC_CONTENT_CONTEXT_COPYLINKLOCATION: 158 case IDC_CONTENT_CONTEXT_COPYAVLOCATION: 159 case IDC_CONTENT_CONTEXT_COPYEMAILADDRESS: 160 case IDC_CONTENT_CONTEXT_COPY: 161 stock = GTK_STOCK_COPY; 162 break; 163 164 case IDC_CUT: 165 case IDC_CONTENT_CONTEXT_CUT: 166 stock = GTK_STOCK_CUT; 167 break; 168 169 case IDC_PASTE: 170 case IDC_CONTENT_CONTEXT_PASTE: 171 stock = GTK_STOCK_PASTE; 172 break; 173 174 case IDC_CONTENT_CONTEXT_DELETE: 175 case IDC_BOOKMARK_BAR_REMOVE: 176 stock = GTK_STOCK_DELETE; 177 break; 178 179 case IDC_CONTENT_CONTEXT_UNDO: 180 stock = GTK_STOCK_UNDO; 181 break; 182 183 case IDC_CONTENT_CONTEXT_REDO: 184 stock = GTK_STOCK_REDO; 185 break; 186 187 case IDC_SEARCH: 188 case IDC_FIND: 189 case IDC_CONTENT_CONTEXT_SEARCHWEBFOR: 190 stock = GTK_STOCK_FIND; 191 break; 192 193 case IDC_CONTENT_CONTEXT_SELECTALL: 194 stock = GTK_STOCK_SELECT_ALL; 195 break; 196 197 case IDC_CLEAR_BROWSING_DATA: 198 stock = GTK_STOCK_CLEAR; 199 break; 200 201 case IDC_BACK: 202 stock = GTK_STOCK_GO_BACK; 203 break; 204 205 case IDC_RELOAD: 206 stock = GTK_STOCK_REFRESH; 207 break; 208 209 case IDC_FORWARD: 210 stock = GTK_STOCK_GO_FORWARD; 211 break; 212 213 case IDC_PRINT: 214 stock = GTK_STOCK_PRINT; 215 break; 216 217 case IDC_CONTENT_CONTEXT_VIEWPAGEINFO: 218 stock = GTK_STOCK_INFO; 219 break; 220 221 case IDC_SPELLCHECK_MENU: 222 stock = GTK_STOCK_SPELL_CHECK; 223 break; 224 225 case IDC_RESTORE_TAB: 226 stock = GTK_STOCK_UNDELETE; 227 break; 228 229 case IDC_HOME: 230 stock = GTK_STOCK_HOME; 231 break; 232 233 case IDC_STOP: 234 stock = GTK_STOCK_STOP; 235 break; 236 237 case IDC_ABOUT: 238 stock = GTK_STOCK_ABOUT; 239 break; 240 241 case IDC_EXIT: 242 stock = GTK_STOCK_QUIT; 243 break; 244 245 case IDC_HELP_PAGE_VIA_MENU: 246 stock = GTK_STOCK_HELP; 247 break; 248 249 case IDC_OPTIONS: 250 stock = GTK_STOCK_PREFERENCES; 251 break; 252 253 case IDC_CONTENT_CONTEXT_GOTOURL: 254 stock = GTK_STOCK_JUMP_TO; 255 break; 256 257 case IDC_DEV_TOOLS_INSPECT: 258 case IDC_CONTENT_CONTEXT_INSPECTELEMENT: 259 stock = GTK_STOCK_PROPERTIES; 260 break; 261 262 case IDC_BOOKMARK_BAR_ADD_NEW_BOOKMARK: 263 stock = GTK_STOCK_ADD; 264 break; 265 266 case IDC_BOOKMARK_BAR_RENAME_FOLDER: 267 case IDC_BOOKMARK_BAR_EDIT: 268 stock = GTK_STOCK_EDIT; 269 break; 270 271 case IDC_BOOKMARK_BAR_NEW_FOLDER: 272 stock = GTK_STOCK_DIRECTORY; 273 break; 274 275 case IDC_BOOKMARK_BAR_OPEN_ALL: 276 stock = GTK_STOCK_OPEN; 277 break; 278 279 default: 280 stock = NULL; 281 } 282 283 return stock ? gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU) : NULL; 284 } 285 286 GtkWidget* MenuGtk::Delegate::GetImageForCommandId(int command_id) const { 287 return GetDefaultImageForCommandId(command_id); 288 } 289 290 MenuGtk::MenuGtk(MenuGtk::Delegate* delegate, 291 ui::MenuModel* model) 292 : delegate_(delegate), 293 model_(model), 294 dummy_accel_group_(gtk_accel_group_new()), 295 menu_(gtk_custom_menu_new()), 296 weak_factory_(this) { 297 DCHECK(model); 298 g_object_ref_sink(menu_); 299 ConnectSignalHandlers(); 300 BuildMenuFromModel(); 301 } 302 303 MenuGtk::~MenuGtk() { 304 Cancel(); 305 306 gtk_widget_destroy(menu_); 307 g_object_unref(menu_); 308 309 g_object_unref(dummy_accel_group_); 310 } 311 312 void MenuGtk::ConnectSignalHandlers() { 313 // We connect afterwards because OnMenuShow calls SetMenuItemInfo, which may 314 // take a long time or even start a nested message loop. 315 g_signal_connect(menu_, "show", G_CALLBACK(OnMenuShowThunk), this); 316 g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this); 317 GtkWidget* toplevel_window = gtk_widget_get_toplevel(menu_); 318 signal_.Connect(toplevel_window, "focus-out-event", 319 G_CALLBACK(OnMenuFocusOutThunk), this); 320 } 321 322 GtkWidget* MenuGtk::AppendMenuItemWithLabel(int command_id, 323 const std::string& label) { 324 std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label); 325 GtkWidget* menu_item = BuildMenuItemWithLabel(converted_label, command_id); 326 return AppendMenuItem(command_id, menu_item); 327 } 328 329 GtkWidget* MenuGtk::AppendMenuItemWithIcon(int command_id, 330 const std::string& label, 331 const gfx::Image& icon) { 332 std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label); 333 GtkWidget* menu_item = BuildMenuItemWithImage(converted_label, icon); 334 return AppendMenuItem(command_id, menu_item); 335 } 336 337 GtkWidget* MenuGtk::AppendCheckMenuItemWithLabel(int command_id, 338 const std::string& label) { 339 std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label); 340 GtkWidget* menu_item = 341 gtk_check_menu_item_new_with_mnemonic(converted_label.c_str()); 342 return AppendMenuItem(command_id, menu_item); 343 } 344 345 GtkWidget* MenuGtk::AppendSeparator() { 346 GtkWidget* menu_item = gtk_separator_menu_item_new(); 347 gtk_widget_show(menu_item); 348 gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item); 349 return menu_item; 350 } 351 352 GtkWidget* MenuGtk::InsertSeparator(int position) { 353 GtkWidget* menu_item = gtk_separator_menu_item_new(); 354 gtk_widget_show(menu_item); 355 gtk_menu_shell_insert(GTK_MENU_SHELL(menu_), menu_item, position); 356 return menu_item; 357 } 358 359 GtkWidget* MenuGtk::AppendMenuItem(int command_id, GtkWidget* menu_item) { 360 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) && 361 GTK_IS_IMAGE_MENU_ITEM(menu_item)) 362 gtk_util::SetAlwaysShowImage(menu_item); 363 364 return AppendMenuItemToMenu(command_id, NULL, menu_item, menu_, true); 365 } 366 367 GtkWidget* MenuGtk::InsertMenuItem(int command_id, GtkWidget* menu_item, 368 int position) { 369 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) && 370 GTK_IS_IMAGE_MENU_ITEM(menu_item)) 371 gtk_util::SetAlwaysShowImage(menu_item); 372 373 return InsertMenuItemToMenu(command_id, NULL, menu_item, menu_, position, 374 true); 375 } 376 377 GtkWidget* MenuGtk::AppendMenuItemToMenu(int index, 378 ui::MenuModel* model, 379 GtkWidget* menu_item, 380 GtkWidget* menu, 381 bool connect_to_activate) { 382 int children_count = g_list_length(GTK_MENU_SHELL(menu)->children); 383 return InsertMenuItemToMenu(index, model, menu_item, menu, 384 children_count, connect_to_activate); 385 } 386 387 GtkWidget* MenuGtk::InsertMenuItemToMenu(int index, 388 ui::MenuModel* model, 389 GtkWidget* menu_item, 390 GtkWidget* menu, 391 int position, 392 bool connect_to_activate) { 393 SetMenuItemID(menu_item, index); 394 395 // Native menu items do their own thing, so only selectively listen for the 396 // activate signal. 397 if (connect_to_activate) { 398 g_signal_connect(menu_item, "activate", 399 G_CALLBACK(OnMenuItemActivatedThunk), this); 400 } 401 402 // AppendMenuItemToMenu is used both internally when we control menu creation 403 // from a model (where the model can choose to hide certain menu items), and 404 // with immediate commands which don't provide the option. 405 if (model) { 406 if (model->IsVisibleAt(index)) 407 gtk_widget_show(menu_item); 408 } else { 409 gtk_widget_show(menu_item); 410 } 411 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menu_item, position); 412 return menu_item; 413 } 414 415 void MenuGtk::PopupForWidget(GtkWidget* widget, int button, 416 guint32 event_time) { 417 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, 418 WidgetMenuPositionFunc, 419 widget, 420 button, event_time); 421 } 422 423 void MenuGtk::PopupAsContext(const gfx::Point& point, guint32 event_time) { 424 // gtk_menu_popup doesn't like the "const" qualifier on point. 425 gfx::Point nonconst_point(point); 426 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, 427 PointMenuPositionFunc, &nonconst_point, 428 3, event_time); 429 } 430 431 void MenuGtk::PopupAsContextForStatusIcon(guint32 event_time, guint32 button, 432 GtkStatusIcon* icon) { 433 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, gtk_status_icon_position_menu, 434 icon, button, event_time); 435 } 436 437 void MenuGtk::PopupAsFromKeyEvent(GtkWidget* widget) { 438 PopupForWidget(widget, 0, gtk_get_current_event_time()); 439 gtk_menu_shell_select_first(GTK_MENU_SHELL(menu_), FALSE); 440 } 441 442 void MenuGtk::Cancel() { 443 gtk_menu_popdown(GTK_MENU(menu_)); 444 } 445 446 void MenuGtk::UpdateMenu() { 447 gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this); 448 } 449 450 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label, 451 GtkWidget* image) { 452 GtkWidget* menu_item = 453 gtk_image_menu_item_new_with_mnemonic(label.c_str()); 454 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image); 455 return menu_item; 456 } 457 458 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label, 459 const gfx::Image& icon) { 460 GtkWidget* menu_item = BuildMenuItemWithImage(label, 461 gtk_image_new_from_pixbuf(icon.ToGdkPixbuf())); 462 return menu_item; 463 } 464 465 GtkWidget* MenuGtk::BuildMenuItemWithLabel(const std::string& label, 466 int command_id) { 467 GtkWidget* img = 468 delegate_ ? delegate_->GetImageForCommandId(command_id) : 469 MenuGtk::Delegate::GetDefaultImageForCommandId(command_id); 470 return img ? BuildMenuItemWithImage(label, img) : 471 gtk_menu_item_new_with_mnemonic(label.c_str()); 472 } 473 474 void MenuGtk::BuildMenuFromModel() { 475 BuildSubmenuFromModel(model_, menu_); 476 } 477 478 void MenuGtk::BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu) { 479 std::map<int, GtkWidget*> radio_groups; 480 GtkWidget* menu_item = NULL; 481 for (int i = 0; i < model->GetItemCount(); ++i) { 482 gfx::Image icon; 483 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( 484 UTF16ToUTF8(model->GetLabelAt(i))); 485 bool connect_to_activate = true; 486 487 switch (model->GetTypeAt(i)) { 488 case ui::MenuModel::TYPE_SEPARATOR: 489 menu_item = gtk_separator_menu_item_new(); 490 break; 491 492 case ui::MenuModel::TYPE_CHECK: 493 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); 494 break; 495 496 case ui::MenuModel::TYPE_RADIO: { 497 std::map<int, GtkWidget*>::iterator iter = 498 radio_groups.find(model->GetGroupIdAt(i)); 499 500 if (iter == radio_groups.end()) { 501 menu_item = gtk_radio_menu_item_new_with_mnemonic( 502 NULL, label.c_str()); 503 radio_groups[model->GetGroupIdAt(i)] = menu_item; 504 } else { 505 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( 506 GTK_RADIO_MENU_ITEM(iter->second), label.c_str()); 507 } 508 break; 509 } 510 case ui::MenuModel::TYPE_BUTTON_ITEM: { 511 ui::ButtonMenuItemModel* button_menu_item_model = 512 model->GetButtonMenuItemAt(i); 513 menu_item = BuildButtonMenuItem(button_menu_item_model, menu); 514 connect_to_activate = false; 515 break; 516 } 517 case ui::MenuModel::TYPE_SUBMENU: 518 case ui::MenuModel::TYPE_COMMAND: { 519 int command_id = model->GetCommandIdAt(i); 520 if (model->GetIconAt(i, &icon)) 521 menu_item = BuildMenuItemWithImage(label, icon); 522 else 523 menu_item = BuildMenuItemWithLabel(label, command_id); 524 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) && 525 GTK_IS_IMAGE_MENU_ITEM(menu_item)) { 526 gtk_util::SetAlwaysShowImage(menu_item); 527 } 528 break; 529 } 530 531 default: 532 NOTREACHED(); 533 } 534 535 if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) { 536 GtkWidget* submenu = gtk_menu_new(); 537 g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item); 538 ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i); 539 g_object_set_data(G_OBJECT(menu_item), "submenu-model", submenu_model); 540 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); 541 // We will populate the submenu on demand when shown. 542 g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this); 543 g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this); 544 connect_to_activate = false; 545 } 546 547 ui::Accelerator accelerator; 548 if (model->GetAcceleratorAt(i, &accelerator)) { 549 gtk_widget_add_accelerator(menu_item, 550 "activate", 551 dummy_accel_group_, 552 ui::GetGdkKeyCodeForAccelerator(accelerator), 553 ui::GetGdkModifierForAccelerator(accelerator), 554 GTK_ACCEL_VISIBLE); 555 } 556 557 g_object_set_data(G_OBJECT(menu_item), "model", model); 558 AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate); 559 560 menu_item = NULL; 561 } 562 } 563 564 GtkWidget* MenuGtk::BuildButtonMenuItem(ui::ButtonMenuItemModel* model, 565 GtkWidget* menu) { 566 GtkWidget* menu_item = gtk_custom_menu_item_new( 567 ui::RemoveWindowsStyleAccelerators(UTF16ToUTF8(model->label())).c_str()); 568 569 // Set up the callback to the model for when it is clicked. 570 g_object_set_data(G_OBJECT(menu_item), "button-model", model); 571 g_signal_connect(menu_item, "button-pushed", 572 G_CALLBACK(OnMenuButtonPressedThunk), this); 573 g_signal_connect(menu_item, "try-button-pushed", 574 G_CALLBACK(OnMenuTryButtonPressedThunk), this); 575 576 GtkSizeGroup* group = NULL; 577 for (int i = 0; i < model->GetItemCount(); ++i) { 578 GtkWidget* button = NULL; 579 580 switch (model->GetTypeAt(i)) { 581 case ui::ButtonMenuItemModel::TYPE_SPACE: { 582 gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item)); 583 break; 584 } 585 case ui::ButtonMenuItemModel::TYPE_BUTTON: { 586 button = gtk_custom_menu_item_add_button( 587 GTK_CUSTOM_MENU_ITEM(menu_item), 588 model->GetCommandIdAt(i)); 589 590 int icon_idr; 591 if (model->GetIconAt(i, &icon_idr)) { 592 SetupImageIcon(button, menu, icon_idr, delegate_); 593 } else { 594 gtk_button_set_label( 595 GTK_BUTTON(button), 596 ui::RemoveWindowsStyleAccelerators( 597 UTF16ToUTF8(model->GetLabelAt(i))).c_str()); 598 } 599 600 SetUpButtonShowHandler(button, model, i); 601 break; 602 } 603 case ui::ButtonMenuItemModel::TYPE_BUTTON_LABEL: { 604 button = gtk_custom_menu_item_add_button_label( 605 GTK_CUSTOM_MENU_ITEM(menu_item), 606 model->GetCommandIdAt(i)); 607 gtk_button_set_label( 608 GTK_BUTTON(button), 609 ui::RemoveWindowsStyleAccelerators( 610 UTF16ToUTF8(model->GetLabelAt(i))).c_str()); 611 SetUpButtonShowHandler(button, model, i); 612 break; 613 } 614 } 615 616 if (button && model->PartOfGroup(i)) { 617 if (!group) 618 group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); 619 620 gtk_size_group_add_widget(group, button); 621 } 622 } 623 624 if (group) 625 g_object_unref(group); 626 627 return menu_item; 628 } 629 630 void MenuGtk::OnMenuItemActivated(GtkWidget* menu_item) { 631 if (block_activation_) 632 return; 633 634 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item)); 635 636 if (!model) { 637 // There won't be a model for "native" submenus like the "Input Methods" 638 // context menu. We don't need to handle activation messages for submenus 639 // anyway, so we can just return here. 640 DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item))); 641 return; 642 } 643 644 // The activate signal is sent to radio items as they get deselected; 645 // ignore it in this case. 646 if (GTK_IS_RADIO_MENU_ITEM(menu_item) && 647 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) { 648 return; 649 } 650 651 int id; 652 if (!GetMenuItemID(menu_item, &id)) 653 return; 654 655 // The menu item can still be activated by hotkeys even if it is disabled. 656 if (model->IsEnabledAt(id)) 657 ExecuteCommand(model, id); 658 } 659 660 void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) { 661 ui::ButtonMenuItemModel* model = 662 reinterpret_cast<ui::ButtonMenuItemModel*>( 663 g_object_get_data(G_OBJECT(menu_item), "button-model")); 664 if (model && model->IsCommandIdEnabled(command_id)) { 665 if (delegate_) 666 delegate_->CommandWillBeExecuted(); 667 668 model->ActivatedCommand(command_id); 669 } 670 } 671 672 gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item, 673 int command_id) { 674 gboolean pressed = FALSE; 675 ui::ButtonMenuItemModel* model = 676 reinterpret_cast<ui::ButtonMenuItemModel*>( 677 g_object_get_data(G_OBJECT(menu_item), "button-model")); 678 if (model && 679 model->IsCommandIdEnabled(command_id) && 680 !model->DoesCommandIdDismissMenu(command_id)) { 681 if (delegate_) 682 delegate_->CommandWillBeExecuted(); 683 684 model->ActivatedCommand(command_id); 685 pressed = TRUE; 686 } 687 688 return pressed; 689 } 690 691 // static 692 void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu, 693 int* x, 694 int* y, 695 gboolean* push_in, 696 void* void_widget) { 697 GtkWidget* widget = GTK_WIDGET(void_widget); 698 GtkRequisition menu_req; 699 700 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); 701 702 gdk_window_get_origin(gtk_widget_get_window(widget), x, y); 703 GdkScreen *screen = gtk_widget_get_screen(widget); 704 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y); 705 706 GdkRectangle screen_rect; 707 gdk_screen_get_monitor_geometry(screen, monitor, 708 &screen_rect); 709 710 GtkAllocation allocation; 711 gtk_widget_get_allocation(widget, &allocation); 712 713 if (!gtk_widget_get_has_window(widget)) { 714 *x += allocation.x; 715 *y += allocation.y; 716 } 717 *y += allocation.height; 718 719 bool start_align = 720 !!g_object_get_data(G_OBJECT(widget), "left-align-popup"); 721 if (base::i18n::IsRTL()) 722 start_align = !start_align; 723 724 if (!start_align) 725 *x += allocation.width - menu_req.width; 726 727 *y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y); 728 729 *push_in = FALSE; 730 } 731 732 // static 733 void MenuGtk::PointMenuPositionFunc(GtkMenu* menu, 734 int* x, 735 int* y, 736 gboolean* push_in, 737 gpointer userdata) { 738 *push_in = TRUE; 739 740 gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata); 741 *x = point->x(); 742 *y = point->y(); 743 744 GtkRequisition menu_req; 745 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); 746 GdkScreen* screen; 747 gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL); 748 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y); 749 750 GdkRectangle screen_rect; 751 gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect); 752 753 *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y); 754 } 755 756 void MenuGtk::ExecuteCommand(ui::MenuModel* model, int id) { 757 if (delegate_) 758 delegate_->CommandWillBeExecuted(); 759 760 GdkEvent* event = gtk_get_current_event(); 761 int event_flags = 0; 762 763 if (event && event->type == GDK_BUTTON_RELEASE) 764 event_flags = event_utils::EventFlagsFromGdkState(event->button.state); 765 model->ActivatedAt(id, event_flags); 766 767 if (event) 768 gdk_event_free(event); 769 } 770 771 void MenuGtk::OnMenuShow(GtkWidget* widget) { 772 model_->MenuWillShow(); 773 base::MessageLoop::current()->PostTask( 774 FROM_HERE, base::Bind(&MenuGtk::UpdateMenu, weak_factory_.GetWeakPtr())); 775 } 776 777 void MenuGtk::OnMenuHidden(GtkWidget* widget) { 778 if (delegate_) 779 delegate_->StoppedShowing(); 780 model_->MenuClosed(); 781 } 782 783 gboolean MenuGtk::OnMenuFocusOut(GtkWidget* widget, GdkEventFocus* event) { 784 gtk_widget_hide(menu_); 785 return TRUE; 786 } 787 788 void MenuGtk::OnSubMenuShow(GtkWidget* submenu) { 789 GtkWidget* menu_item = static_cast<GtkWidget*>( 790 g_object_get_data(G_OBJECT(submenu), "menu-item")); 791 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974. 792 CHECK(menu_item); 793 // Notify the submenu model that the menu will be shown. 794 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( 795 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); 796 // We're extra cautious here, and bail out if the submenu model is NULL. In 797 // some cases we clear it out from a parent menu; we shouldn't ever show the 798 // menu after that, but we play it safe since we're dealing with wacky 799 // injected libraries that toy with our menus. (See comments below.) 800 if (!submenu_model) 801 return; 802 803 // If the submenu is already built, then return right away. This means we 804 // recently showed this submenu, and have not yet processed the fact that it 805 // was hidden before being shown again. 806 if (g_object_get_data(G_OBJECT(submenu), "submenu-built")) 807 return; 808 g_object_set_data(G_OBJECT(submenu), "submenu-built", GINT_TO_POINTER(1)); 809 810 submenu_model->MenuWillShow(); 811 812 // Actually build the submenu and attach it to the parent menu item. 813 BuildSubmenuFromModel(submenu_model, submenu); 814 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); 815 816 // Update all the menu item info in the newly-generated menu. 817 gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo, this); 818 } 819 820 void MenuGtk::OnSubMenuHidden(GtkWidget* submenu) { 821 // Increase the reference count of the old submenu, and schedule it to be 822 // deleted later. We get this hide notification before we've processed menu 823 // activations, so if we were to delete the submenu now, we might lose the 824 // activation. This also lets us reuse the menu if it is shown again before 825 // it gets deleted; in that case, OnSubMenuHiddenCallback() just decrements 826 // the reference count again. Note that the delay is just an optimization; we 827 // could use PostTask() and this would still work correctly. 828 g_object_ref(G_OBJECT(submenu)); 829 base::MessageLoop::current()->PostDelayedTask( 830 FROM_HERE, 831 base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu), 832 base::TimeDelta::FromSeconds(2)); 833 } 834 835 namespace { 836 837 // Remove all descendant submenu-model data pointers. 838 void RemoveSubMenuModels(GtkWidget* menu_item, void* unused) { 839 if (!GTK_IS_MENU_ITEM(menu_item)) 840 return; 841 g_object_steal_data(G_OBJECT(menu_item), "submenu-model"); 842 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); 843 if (submenu) 844 gtk_container_foreach(GTK_CONTAINER(submenu), RemoveSubMenuModels, NULL); 845 } 846 847 } // namespace 848 849 // static 850 void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) { 851 if (!gtk_widget_get_visible(submenu)) { 852 // Remove all the children of this menu, clearing out their submenu-model 853 // pointers in case they have pending calls to OnSubMenuHiddenCallback(). 854 // (Normally that won't happen: we'd have hidden them first, and so they'd 855 // have already been deleted. But in some cases [e.g. on Ubuntu 12.04], 856 // GTK menu operations may be hooked to allow external applications to 857 // mirror the menu structure, and the hooks may show and hide menus in 858 // order to trigger exactly the kind of dynamic menu building we're doing. 859 // The result is that we see show and hide events in strange orders.) 860 GList* children = gtk_container_get_children(GTK_CONTAINER(submenu)); 861 for (GList* child = children; child; child = g_list_next(child)) { 862 RemoveSubMenuModels(GTK_WIDGET(child->data), NULL); 863 gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(child->data)); 864 } 865 g_list_free(children); 866 867 // Clear out the bit that says the menu is built. 868 // We'll rebuild it next time it is shown. 869 g_object_steal_data(G_OBJECT(submenu), "submenu-built"); 870 871 // Notify the submenu model that the menu has been hidden. This may cause 872 // it to delete descendant submenu models, which is why we cleared those 873 // pointers out above. 874 GtkWidget* menu_item = static_cast<GtkWidget*>( 875 g_object_get_data(G_OBJECT(submenu), "menu-item")); 876 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110. 877 CHECK(menu_item); 878 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( 879 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); 880 if (submenu_model) 881 submenu_model->MenuClosed(); 882 } 883 884 // Remove the reference we grabbed in OnSubMenuHidden() above. 885 g_object_unref(G_OBJECT(submenu)); 886 } 887 888 // static 889 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) { 890 ui::ButtonMenuItemModel* model = 891 reinterpret_cast<ui::ButtonMenuItemModel*>( 892 g_object_get_data(G_OBJECT(button), "button-model")); 893 int index = GPOINTER_TO_INT(g_object_get_data( 894 G_OBJECT(button), "button-model-id")); 895 896 if (model->IsItemDynamicAt(index)) { 897 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( 898 UTF16ToUTF8(model->GetLabelAt(index))); 899 gtk_button_set_label(GTK_BUTTON(button), label.c_str()); 900 } 901 902 gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index)); 903 } 904 905 // static 906 void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) { 907 if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) { 908 // We need to explicitly handle this case because otherwise we'll ask the 909 // menu delegate about something with an invalid id. 910 return; 911 } 912 913 int id; 914 if (!GetMenuItemID(widget, &id)) 915 return; 916 917 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget)); 918 if (!model) { 919 // If we're not providing the sub menu, then there's no model. For 920 // example, the IME submenu doesn't have a model. 921 return; 922 } 923 924 if (GTK_IS_CHECK_MENU_ITEM(widget)) { 925 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget); 926 927 // gtk_check_menu_item_set_active() will send the activate signal. Touching 928 // the underlying "active" property will also call the "activate" handler 929 // for this menu item. So we prevent the "activate" handler from 930 // being called while we set the checkbox. 931 // Why not use one of the glib signal-blocking functions? Because when we 932 // toggle a radio button, it will deactivate one of the other radio buttons, 933 // which we don't have a pointer to. 934 // Wny not make this a member variable? Because "menu" is a pointer to the 935 // root of the MenuGtk and we want to disable *all* MenuGtks, including 936 // submenus. 937 block_activation_ = true; 938 gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id)); 939 block_activation_ = false; 940 } 941 942 if (GTK_IS_CUSTOM_MENU_ITEM(widget)) { 943 // Iterate across all the buttons to update their visible properties. 944 gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget), 945 SetButtonItemInfo, 946 userdata); 947 } 948 949 if (GTK_IS_MENU_ITEM(widget)) { 950 gtk_widget_set_sensitive(widget, model->IsEnabledAt(id)); 951 952 if (model->IsVisibleAt(id)) { 953 // Update the menu item label if it is dynamic. 954 if (model->IsItemDynamicAt(id)) { 955 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( 956 UTF16ToUTF8(model->GetLabelAt(id))); 957 958 gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str()); 959 if (GTK_IS_IMAGE_MENU_ITEM(widget)) { 960 gfx::Image icon; 961 if (model->GetIconAt(id, &icon)) { 962 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), 963 gtk_image_new_from_pixbuf( 964 icon.ToGdkPixbuf())); 965 } else { 966 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL); 967 } 968 } 969 } 970 971 gtk_widget_show(widget); 972 } else { 973 gtk_widget_hide(widget); 974 } 975 976 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); 977 if (submenu) { 978 gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo, 979 userdata); 980 } 981 } 982 } 983