1 // Copyright (c) 2011 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/i18n/rtl.h" 10 #include "base/logging.h" 11 #include "base/message_loop.h" 12 #include "base/stl_util-inl.h" 13 #include "base/utf_string_conversions.h" 14 #include "chrome/app/chrome_command_ids.h" 15 #include "chrome/browser/ui/gtk/gtk_custom_menu.h" 16 #include "chrome/browser/ui/gtk/gtk_custom_menu_item.h" 17 #include "chrome/browser/ui/gtk/gtk_util.h" 18 #include "third_party/skia/include/core/SkBitmap.h" 19 #include "ui/base/models/accelerator_gtk.h" 20 #include "ui/base/models/button_menu_item_model.h" 21 #include "ui/base/models/menu_model.h" 22 #include "ui/gfx/gtk_util.h" 23 #include "webkit/glue/window_open_disposition.h" 24 25 bool MenuGtk::block_activation_ = false; 26 27 namespace { 28 29 // Sets the ID of a menu item. 30 void SetMenuItemID(GtkWidget* menu_item, int menu_id) { 31 DCHECK_GE(menu_id, 0); 32 33 // Add 1 to the menu_id to avoid setting zero (null) to "menu-id". 34 g_object_set_data(G_OBJECT(menu_item), "menu-id", 35 GINT_TO_POINTER(menu_id + 1)); 36 } 37 38 // Gets the ID of a menu item. 39 // Returns true if the menu item has an ID. 40 bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) { 41 gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id"); 42 if (id_ptr != NULL) { 43 *menu_id = GPOINTER_TO_INT(id_ptr) - 1; 44 return true; 45 } 46 47 return false; 48 } 49 50 ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) { 51 return reinterpret_cast<ui::MenuModel*>( 52 g_object_get_data(G_OBJECT(menu_item), "model")); 53 } 54 55 void SetupButtonShowHandler(GtkWidget* button, 56 ui::ButtonMenuItemModel* model, 57 int index) { 58 g_object_set_data(G_OBJECT(button), "button-model", 59 model); 60 g_object_set_data(G_OBJECT(button), "button-model-id", 61 GINT_TO_POINTER(index)); 62 } 63 64 void OnSubmenuShowButtonImage(GtkWidget* widget, GtkButton* button) { 65 MenuGtk::Delegate* delegate = reinterpret_cast<MenuGtk::Delegate*>( 66 g_object_get_data(G_OBJECT(button), "menu-gtk-delegate")); 67 int icon_idr = GPOINTER_TO_INT(g_object_get_data( 68 G_OBJECT(button), "button-image-idr")); 69 70 GtkIconSet* icon_set = delegate->GetIconSetForId(icon_idr); 71 if (icon_set) { 72 gtk_button_set_image( 73 button, gtk_image_new_from_icon_set(icon_set, 74 GTK_ICON_SIZE_MENU)); 75 } 76 } 77 78 void SetupImageIcon(GtkWidget* button, 79 GtkWidget* menu, 80 int icon_idr, 81 MenuGtk::Delegate* menu_gtk_delegate) { 82 g_object_set_data(G_OBJECT(button), "button-image-idr", 83 GINT_TO_POINTER(icon_idr)); 84 g_object_set_data(G_OBJECT(button), "menu-gtk-delegate", 85 menu_gtk_delegate); 86 87 g_signal_connect(menu, "show", G_CALLBACK(OnSubmenuShowButtonImage), button); 88 } 89 90 // Popup menus may get squished if they open up too close to the bottom of the 91 // screen. This function takes the size of the screen, the size of the menu, 92 // an optional widget, the Y position of the mouse click, and adjusts the popup 93 // menu's Y position to make it fit if it's possible to do so. 94 // Returns the new Y position of the popup menu. 95 int CalculateMenuYPosition(const GdkRectangle* screen_rect, 96 const GtkRequisition* menu_req, 97 const GtkWidget* widget, const int y) { 98 CHECK(screen_rect); 99 CHECK(menu_req); 100 // If the menu would run off the bottom of the screen, and there is enough 101 // screen space upwards to accommodate the menu, then pop upwards. If there 102 // is a widget, then also move the anchor point to the top of the widget 103 // rather than the bottom. 104 const int screen_top = screen_rect->y; 105 const int screen_bottom = screen_rect->y + screen_rect->height; 106 const int menu_bottom = y + menu_req->height; 107 int alternate_y = y - menu_req->height; 108 if (widget) 109 alternate_y -= widget->allocation.height; 110 if (menu_bottom >= screen_bottom && alternate_y >= screen_top) 111 return alternate_y; 112 return y; 113 } 114 115 } // namespace 116 117 GtkWidget* MenuGtk::Delegate::GetDefaultImageForCommandId(int command_id) { 118 const char* stock; 119 switch (command_id) { 120 case IDC_NEW_TAB: 121 case IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB: 122 case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB: 123 case IDC_CONTENT_CONTEXT_OPENAVNEWTAB: 124 stock = GTK_STOCK_NEW; 125 break; 126 127 case IDC_CLOSE_TAB: 128 stock = GTK_STOCK_CLOSE; 129 break; 130 131 case IDC_CONTENT_CONTEXT_SAVEIMAGEAS: 132 case IDC_CONTENT_CONTEXT_SAVEAVAS: 133 case IDC_CONTENT_CONTEXT_SAVELINKAS: 134 stock = GTK_STOCK_SAVE_AS; 135 break; 136 137 case IDC_SAVE_PAGE: 138 stock = GTK_STOCK_SAVE; 139 break; 140 141 case IDC_COPY: 142 case IDC_COPY_URL: 143 case IDC_CONTENT_CONTEXT_COPYIMAGELOCATION: 144 case IDC_CONTENT_CONTEXT_COPYLINKLOCATION: 145 case IDC_CONTENT_CONTEXT_COPYAVLOCATION: 146 case IDC_CONTENT_CONTEXT_COPYEMAILADDRESS: 147 case IDC_CONTENT_CONTEXT_COPY: 148 stock = GTK_STOCK_COPY; 149 break; 150 151 case IDC_CUT: 152 case IDC_CONTENT_CONTEXT_CUT: 153 stock = GTK_STOCK_CUT; 154 break; 155 156 case IDC_PASTE: 157 case IDC_CONTENT_CONTEXT_PASTE: 158 stock = GTK_STOCK_PASTE; 159 break; 160 161 case IDC_CONTENT_CONTEXT_DELETE: 162 case IDC_BOOKMARK_BAR_REMOVE: 163 stock = GTK_STOCK_DELETE; 164 break; 165 166 case IDC_CONTENT_CONTEXT_UNDO: 167 stock = GTK_STOCK_UNDO; 168 break; 169 170 case IDC_CONTENT_CONTEXT_REDO: 171 stock = GTK_STOCK_REDO; 172 break; 173 174 case IDC_SEARCH: 175 case IDC_FIND: 176 case IDC_CONTENT_CONTEXT_SEARCHWEBFOR: 177 stock = GTK_STOCK_FIND; 178 break; 179 180 case IDC_CONTENT_CONTEXT_SELECTALL: 181 stock = GTK_STOCK_SELECT_ALL; 182 break; 183 184 case IDC_CLEAR_BROWSING_DATA: 185 stock = GTK_STOCK_CLEAR; 186 break; 187 188 case IDC_BACK: 189 stock = GTK_STOCK_GO_BACK; 190 break; 191 192 case IDC_RELOAD: 193 stock = GTK_STOCK_REFRESH; 194 break; 195 196 case IDC_FORWARD: 197 stock = GTK_STOCK_GO_FORWARD; 198 break; 199 200 case IDC_PRINT: 201 stock = GTK_STOCK_PRINT; 202 break; 203 204 case IDC_CONTENT_CONTEXT_VIEWPAGEINFO: 205 stock = GTK_STOCK_INFO; 206 break; 207 208 case IDC_SPELLCHECK_MENU: 209 stock = GTK_STOCK_SPELL_CHECK; 210 break; 211 212 case IDC_RESTORE_TAB: 213 stock = GTK_STOCK_UNDELETE; 214 break; 215 216 case IDC_HOME: 217 stock = GTK_STOCK_HOME; 218 break; 219 220 case IDC_STOP: 221 stock = GTK_STOCK_STOP; 222 break; 223 224 case IDC_ABOUT: 225 stock = GTK_STOCK_ABOUT; 226 break; 227 228 case IDC_EXIT: 229 stock = GTK_STOCK_QUIT; 230 break; 231 232 case IDC_HELP_PAGE: 233 stock = GTK_STOCK_HELP; 234 break; 235 236 case IDC_OPTIONS: 237 stock = GTK_STOCK_PREFERENCES; 238 break; 239 240 case IDC_CONTENT_CONTEXT_GOTOURL: 241 stock = GTK_STOCK_JUMP_TO; 242 break; 243 244 case IDC_DEV_TOOLS_INSPECT: 245 case IDC_CONTENT_CONTEXT_INSPECTELEMENT: 246 stock = GTK_STOCK_PROPERTIES; 247 break; 248 249 case IDC_BOOKMARK_BAR_ADD_NEW_BOOKMARK: 250 stock = GTK_STOCK_ADD; 251 break; 252 253 case IDC_BOOKMARK_BAR_RENAME_FOLDER: 254 case IDC_BOOKMARK_BAR_EDIT: 255 stock = GTK_STOCK_EDIT; 256 break; 257 258 case IDC_BOOKMARK_BAR_NEW_FOLDER: 259 stock = GTK_STOCK_DIRECTORY; 260 break; 261 262 case IDC_BOOKMARK_BAR_OPEN_ALL: 263 stock = GTK_STOCK_OPEN; 264 break; 265 266 default: 267 stock = NULL; 268 } 269 270 return stock ? gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU) : NULL; 271 } 272 273 GtkWidget* MenuGtk::Delegate::GetImageForCommandId(int command_id) const { 274 return GetDefaultImageForCommandId(command_id); 275 } 276 277 MenuGtk::MenuGtk(MenuGtk::Delegate* delegate, 278 ui::MenuModel* model) 279 : delegate_(delegate), 280 model_(model), 281 dummy_accel_group_(gtk_accel_group_new()), 282 menu_(gtk_custom_menu_new()), 283 factory_(this) { 284 DCHECK(model); 285 g_object_ref_sink(menu_); 286 ConnectSignalHandlers(); 287 BuildMenuFromModel(); 288 } 289 290 MenuGtk::~MenuGtk() { 291 Cancel(); 292 293 gtk_widget_destroy(menu_); 294 g_object_unref(menu_); 295 296 STLDeleteContainerPointers(submenus_we_own_.begin(), submenus_we_own_.end()); 297 g_object_unref(dummy_accel_group_); 298 } 299 300 void MenuGtk::ConnectSignalHandlers() { 301 // We connect afterwards because OnMenuShow calls SetMenuItemInfo, which may 302 // take a long time or even start a nested message loop. 303 g_signal_connect(menu_, "show", G_CALLBACK(OnMenuShowThunk), this); 304 g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this); 305 } 306 307 GtkWidget* MenuGtk::AppendMenuItemWithLabel(int command_id, 308 const std::string& label) { 309 std::string converted_label = gfx::ConvertAcceleratorsFromWindowsStyle(label); 310 GtkWidget* menu_item = BuildMenuItemWithLabel(label, command_id); 311 return AppendMenuItem(command_id, menu_item); 312 } 313 314 GtkWidget* MenuGtk::AppendMenuItemWithIcon(int command_id, 315 const std::string& label, 316 const SkBitmap& icon) { 317 std::string converted_label = gfx::ConvertAcceleratorsFromWindowsStyle(label); 318 GtkWidget* menu_item = BuildMenuItemWithImage(converted_label, icon); 319 return AppendMenuItem(command_id, menu_item); 320 } 321 322 GtkWidget* MenuGtk::AppendCheckMenuItemWithLabel(int command_id, 323 const std::string& label) { 324 std::string converted_label = gfx::ConvertAcceleratorsFromWindowsStyle(label); 325 GtkWidget* menu_item = 326 gtk_check_menu_item_new_with_mnemonic(converted_label.c_str()); 327 return AppendMenuItem(command_id, menu_item); 328 } 329 330 GtkWidget* MenuGtk::AppendSeparator() { 331 GtkWidget* menu_item = gtk_separator_menu_item_new(); 332 gtk_widget_show(menu_item); 333 gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item); 334 return menu_item; 335 } 336 337 GtkWidget* MenuGtk::AppendMenuItem(int command_id, GtkWidget* menu_item) { 338 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) && 339 GTK_IS_IMAGE_MENU_ITEM(menu_item)) 340 gtk_util::SetAlwaysShowImage(menu_item); 341 342 return AppendMenuItemToMenu(command_id, NULL, menu_item, menu_, true); 343 } 344 345 GtkWidget* MenuGtk::AppendMenuItemToMenu(int index, 346 ui::MenuModel* model, 347 GtkWidget* menu_item, 348 GtkWidget* menu, 349 bool connect_to_activate) { 350 SetMenuItemID(menu_item, index); 351 352 // Native menu items do their own thing, so only selectively listen for the 353 // activate signal. 354 if (connect_to_activate) { 355 g_signal_connect(menu_item, "activate", 356 G_CALLBACK(OnMenuItemActivatedThunk), this); 357 } 358 359 // AppendMenuItemToMenu is used both internally when we control menu creation 360 // from a model (where the model can choose to hide certain menu items), and 361 // with immediate commands which don't provide the option. 362 if (model) { 363 if (model->IsVisibleAt(index)) 364 gtk_widget_show(menu_item); 365 } else { 366 gtk_widget_show(menu_item); 367 } 368 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); 369 return menu_item; 370 } 371 372 void MenuGtk::PopupForWidget(GtkWidget* widget, int button, 373 guint32 event_time) { 374 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, 375 WidgetMenuPositionFunc, 376 widget, 377 button, event_time); 378 } 379 380 void MenuGtk::PopupAsContext(const gfx::Point& point, guint32 event_time) { 381 // gtk_menu_popup doesn't like the "const" qualifier on point. 382 gfx::Point nonconst_point(point); 383 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, 384 PointMenuPositionFunc, &nonconst_point, 385 3, event_time); 386 } 387 388 void MenuGtk::PopupAsContextForStatusIcon(guint32 event_time, guint32 button, 389 GtkStatusIcon* icon) { 390 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, gtk_status_icon_position_menu, 391 icon, button, event_time); 392 } 393 394 void MenuGtk::PopupAsFromKeyEvent(GtkWidget* widget) { 395 PopupForWidget(widget, 0, gtk_get_current_event_time()); 396 gtk_menu_shell_select_first(GTK_MENU_SHELL(menu_), FALSE); 397 } 398 399 void MenuGtk::Cancel() { 400 gtk_menu_popdown(GTK_MENU(menu_)); 401 } 402 403 void MenuGtk::UpdateMenu() { 404 gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this); 405 } 406 407 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label, 408 GtkWidget* image) { 409 GtkWidget* menu_item = 410 gtk_image_menu_item_new_with_mnemonic(label.c_str()); 411 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image); 412 return menu_item; 413 } 414 415 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label, 416 const SkBitmap& icon) { 417 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); 418 GtkWidget* menu_item = BuildMenuItemWithImage(label, 419 gtk_image_new_from_pixbuf(pixbuf)); 420 g_object_unref(pixbuf); 421 return menu_item; 422 } 423 424 GtkWidget* MenuGtk::BuildMenuItemWithLabel(const std::string& label, 425 int command_id) { 426 GtkWidget* img = 427 delegate_ ? delegate_->GetImageForCommandId(command_id) : 428 MenuGtk::Delegate::GetDefaultImageForCommandId(command_id); 429 return img ? BuildMenuItemWithImage(label, img) : 430 gtk_menu_item_new_with_mnemonic(label.c_str()); 431 } 432 433 void MenuGtk::BuildMenuFromModel() { 434 BuildSubmenuFromModel(model_, menu_); 435 } 436 437 void MenuGtk::BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu) { 438 std::map<int, GtkWidget*> radio_groups; 439 GtkWidget* menu_item = NULL; 440 for (int i = 0; i < model->GetItemCount(); ++i) { 441 SkBitmap icon; 442 std::string label = 443 gfx::ConvertAcceleratorsFromWindowsStyle( 444 UTF16ToUTF8(model->GetLabelAt(i))); 445 bool connect_to_activate = true; 446 447 switch (model->GetTypeAt(i)) { 448 case ui::MenuModel::TYPE_SEPARATOR: 449 menu_item = gtk_separator_menu_item_new(); 450 break; 451 452 case ui::MenuModel::TYPE_CHECK: 453 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); 454 break; 455 456 case ui::MenuModel::TYPE_RADIO: { 457 std::map<int, GtkWidget*>::iterator iter = 458 radio_groups.find(model->GetGroupIdAt(i)); 459 460 if (iter == radio_groups.end()) { 461 menu_item = gtk_radio_menu_item_new_with_mnemonic( 462 NULL, label.c_str()); 463 radio_groups[model->GetGroupIdAt(i)] = menu_item; 464 } else { 465 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( 466 GTK_RADIO_MENU_ITEM(iter->second), label.c_str()); 467 } 468 break; 469 } 470 case ui::MenuModel::TYPE_BUTTON_ITEM: { 471 ui::ButtonMenuItemModel* button_menu_item_model = 472 model->GetButtonMenuItemAt(i); 473 menu_item = BuildButtonMenuItem(button_menu_item_model, menu); 474 connect_to_activate = false; 475 break; 476 } 477 case ui::MenuModel::TYPE_SUBMENU: 478 case ui::MenuModel::TYPE_COMMAND: { 479 int command_id = model->GetCommandIdAt(i); 480 if (model->GetIconAt(i, &icon)) 481 menu_item = BuildMenuItemWithImage(label, icon); 482 else 483 menu_item = BuildMenuItemWithLabel(label, command_id); 484 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) && 485 GTK_IS_IMAGE_MENU_ITEM(menu_item)) 486 gtk_util::SetAlwaysShowImage(menu_item); 487 break; 488 } 489 490 default: 491 NOTREACHED(); 492 } 493 494 if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) { 495 GtkWidget* submenu = gtk_menu_new(); 496 BuildSubmenuFromModel(model->GetSubmenuModelAt(i), submenu); 497 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); 498 } 499 500 ui::AcceleratorGtk accelerator; 501 if (model->GetAcceleratorAt(i, &accelerator)) { 502 gtk_widget_add_accelerator(menu_item, 503 "activate", 504 dummy_accel_group_, 505 accelerator.GetGdkKeyCode(), 506 accelerator.gdk_modifier_type(), 507 GTK_ACCEL_VISIBLE); 508 } 509 510 g_object_set_data(G_OBJECT(menu_item), "model", model); 511 AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate); 512 513 menu_item = NULL; 514 } 515 } 516 517 GtkWidget* MenuGtk::BuildButtonMenuItem(ui::ButtonMenuItemModel* model, 518 GtkWidget* menu) { 519 GtkWidget* menu_item = gtk_custom_menu_item_new( 520 gfx::RemoveWindowsStyleAccelerators(UTF16ToUTF8(model->label())).c_str()); 521 522 // Set up the callback to the model for when it is clicked. 523 g_object_set_data(G_OBJECT(menu_item), "button-model", model); 524 g_signal_connect(menu_item, "button-pushed", 525 G_CALLBACK(OnMenuButtonPressedThunk), this); 526 g_signal_connect(menu_item, "try-button-pushed", 527 G_CALLBACK(OnMenuTryButtonPressedThunk), this); 528 529 GtkSizeGroup* group = NULL; 530 for (int i = 0; i < model->GetItemCount(); ++i) { 531 GtkWidget* button = NULL; 532 533 switch (model->GetTypeAt(i)) { 534 case ui::ButtonMenuItemModel::TYPE_SPACE: { 535 gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item)); 536 break; 537 } 538 case ui::ButtonMenuItemModel::TYPE_BUTTON: { 539 button = gtk_custom_menu_item_add_button( 540 GTK_CUSTOM_MENU_ITEM(menu_item), 541 model->GetCommandIdAt(i)); 542 543 int icon_idr; 544 if (model->GetIconAt(i, &icon_idr)) { 545 SetupImageIcon(button, menu, icon_idr, delegate_); 546 } else { 547 gtk_button_set_label( 548 GTK_BUTTON(button), 549 gfx::RemoveWindowsStyleAccelerators( 550 UTF16ToUTF8(model->GetLabelAt(i))).c_str()); 551 } 552 553 SetupButtonShowHandler(button, model, i); 554 break; 555 } 556 case ui::ButtonMenuItemModel::TYPE_BUTTON_LABEL: { 557 button = gtk_custom_menu_item_add_button_label( 558 GTK_CUSTOM_MENU_ITEM(menu_item), 559 model->GetCommandIdAt(i)); 560 gtk_button_set_label( 561 GTK_BUTTON(button), 562 gfx::RemoveWindowsStyleAccelerators( 563 UTF16ToUTF8(model->GetLabelAt(i))).c_str()); 564 SetupButtonShowHandler(button, model, i); 565 break; 566 } 567 } 568 569 if (button && model->PartOfGroup(i)) { 570 if (!group) 571 group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); 572 573 gtk_size_group_add_widget(group, button); 574 } 575 } 576 577 if (group) { 578 g_object_unref(group); 579 } 580 581 return menu_item; 582 } 583 584 void MenuGtk::OnMenuItemActivated(GtkWidget* menuitem) { 585 if (block_activation_) 586 return; 587 588 // We receive activation messages when highlighting a menu that has a 589 // submenu. Ignore them. 590 if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menuitem))) 591 return; 592 593 // The activate signal is sent to radio items as they get deselected; 594 // ignore it in this case. 595 if (GTK_IS_RADIO_MENU_ITEM(menuitem) && 596 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) { 597 return; 598 } 599 600 int id; 601 if (!GetMenuItemID(menuitem, &id)) 602 return; 603 604 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menuitem)); 605 606 // The menu item can still be activated by hotkeys even if it is disabled. 607 if (model->IsEnabledAt(id)) 608 ExecuteCommand(model, id); 609 } 610 611 void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) { 612 ui::ButtonMenuItemModel* model = 613 reinterpret_cast<ui::ButtonMenuItemModel*>( 614 g_object_get_data(G_OBJECT(menu_item), "button-model")); 615 if (model && model->IsCommandIdEnabled(command_id)) { 616 if (delegate_) 617 delegate_->CommandWillBeExecuted(); 618 619 model->ActivatedCommand(command_id); 620 } 621 } 622 623 gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item, 624 int command_id) { 625 gboolean pressed = FALSE; 626 ui::ButtonMenuItemModel* model = 627 reinterpret_cast<ui::ButtonMenuItemModel*>( 628 g_object_get_data(G_OBJECT(menu_item), "button-model")); 629 if (model && 630 model->IsCommandIdEnabled(command_id) && 631 !model->DoesCommandIdDismissMenu(command_id)) { 632 if (delegate_) 633 delegate_->CommandWillBeExecuted(); 634 635 model->ActivatedCommand(command_id); 636 pressed = TRUE; 637 } 638 639 return pressed; 640 } 641 642 // static 643 void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu, 644 int* x, 645 int* y, 646 gboolean* push_in, 647 void* void_widget) { 648 GtkWidget* widget = GTK_WIDGET(void_widget); 649 GtkRequisition menu_req; 650 651 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); 652 653 gdk_window_get_origin(widget->window, x, y); 654 GdkScreen *screen = gtk_widget_get_screen(widget); 655 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y); 656 657 GdkRectangle screen_rect; 658 gdk_screen_get_monitor_geometry(screen, monitor, 659 &screen_rect); 660 661 if (GTK_WIDGET_NO_WINDOW(widget)) { 662 *x += widget->allocation.x; 663 *y += widget->allocation.y; 664 } 665 *y += widget->allocation.height; 666 667 bool start_align = 668 !!g_object_get_data(G_OBJECT(widget), "left-align-popup"); 669 if (base::i18n::IsRTL()) 670 start_align = !start_align; 671 672 if (!start_align) 673 *x += widget->allocation.width - menu_req.width; 674 675 *y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y); 676 677 *push_in = FALSE; 678 } 679 680 // static 681 void MenuGtk::PointMenuPositionFunc(GtkMenu* menu, 682 int* x, 683 int* y, 684 gboolean* push_in, 685 gpointer userdata) { 686 *push_in = TRUE; 687 688 gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata); 689 *x = point->x(); 690 *y = point->y(); 691 692 GtkRequisition menu_req; 693 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); 694 GdkScreen* screen; 695 gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL); 696 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y); 697 698 GdkRectangle screen_rect; 699 gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect); 700 701 *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y); 702 } 703 704 void MenuGtk::ExecuteCommand(ui::MenuModel* model, int id) { 705 if (delegate_) 706 delegate_->CommandWillBeExecuted(); 707 708 GdkEvent* event = gtk_get_current_event(); 709 if (event && event->type == GDK_BUTTON_RELEASE) { 710 model->ActivatedAtWithDisposition( 711 id, event_utils::DispositionFromEventFlags(event->button.state)); 712 } else { 713 model->ActivatedAt(id); 714 } 715 716 if (event) 717 gdk_event_free(event); 718 } 719 720 void MenuGtk::OnMenuShow(GtkWidget* widget) { 721 model_->MenuWillShow(); 722 MessageLoop::current()->PostTask(FROM_HERE, 723 factory_.NewRunnableMethod(&MenuGtk::UpdateMenu)); 724 } 725 726 void MenuGtk::OnMenuHidden(GtkWidget* widget) { 727 if (delegate_) 728 delegate_->StoppedShowing(); 729 model_->MenuClosed(); 730 } 731 732 // static 733 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) { 734 ui::ButtonMenuItemModel* model = 735 reinterpret_cast<ui::ButtonMenuItemModel*>( 736 g_object_get_data(G_OBJECT(button), "button-model")); 737 int index = GPOINTER_TO_INT(g_object_get_data( 738 G_OBJECT(button), "button-model-id")); 739 740 if (model->IsItemDynamicAt(index)) { 741 std::string label = 742 gfx::ConvertAcceleratorsFromWindowsStyle( 743 UTF16ToUTF8(model->GetLabelAt(index))); 744 gtk_button_set_label(GTK_BUTTON(button), label.c_str()); 745 } 746 747 gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index)); 748 } 749 750 // static 751 void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) { 752 if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) { 753 // We need to explicitly handle this case because otherwise we'll ask the 754 // menu delegate about something with an invalid id. 755 return; 756 } 757 758 int id; 759 if (!GetMenuItemID(widget, &id)) 760 return; 761 762 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget)); 763 if (!model) { 764 // If we're not providing the sub menu, then there's no model. For 765 // example, the IME submenu doesn't have a model. 766 return; 767 } 768 769 if (GTK_IS_CHECK_MENU_ITEM(widget)) { 770 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget); 771 772 // gtk_check_menu_item_set_active() will send the activate signal. Touching 773 // the underlying "active" property will also call the "activate" handler 774 // for this menu item. So we prevent the "activate" handler from 775 // being called while we set the checkbox. 776 // Why not use one of the glib signal-blocking functions? Because when we 777 // toggle a radio button, it will deactivate one of the other radio buttons, 778 // which we don't have a pointer to. 779 // Wny not make this a member variable? Because "menu" is a pointer to the 780 // root of the MenuGtk and we want to disable *all* MenuGtks, including 781 // submenus. 782 block_activation_ = true; 783 gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id)); 784 block_activation_ = false; 785 } 786 787 if (GTK_IS_CUSTOM_MENU_ITEM(widget)) { 788 // Iterate across all the buttons to update their visible properties. 789 gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget), 790 SetButtonItemInfo, 791 userdata); 792 } 793 794 if (GTK_IS_MENU_ITEM(widget)) { 795 gtk_widget_set_sensitive(widget, model->IsEnabledAt(id)); 796 797 if (model->IsVisibleAt(id)) { 798 // Update the menu item label if it is dynamic. 799 if (model->IsItemDynamicAt(id)) { 800 std::string label = 801 gfx::ConvertAcceleratorsFromWindowsStyle( 802 UTF16ToUTF8(model->GetLabelAt(id))); 803 804 #if GTK_CHECK_VERSION(2, 16, 0) 805 gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str()); 806 #else 807 gtk_label_set_label(GTK_LABEL(GTK_BIN(widget)->child), label.c_str()); 808 #endif 809 if (GTK_IS_IMAGE_MENU_ITEM(widget)) { 810 SkBitmap icon; 811 if (model->GetIconAt(id, &icon)) { 812 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); 813 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), 814 gtk_image_new_from_pixbuf(pixbuf)); 815 g_object_unref(pixbuf); 816 } else { 817 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL); 818 } 819 } 820 } 821 822 gtk_widget_show(widget); 823 } else { 824 gtk_widget_hide(widget); 825 } 826 827 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); 828 if (submenu) { 829 gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo, 830 userdata); 831 } 832 } 833 } 834