| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/gtk/menu_gtk.h" | |
| 6 | |
| 7 #include <map> | |
| 8 | |
| 9 #include "app/menus/accelerator_gtk.h" | |
| 10 #include "app/menus/button_menu_item_model.h" | |
| 11 #include "app/menus/menu_model.h" | |
| 12 #include "base/i18n/rtl.h" | |
| 13 #include "base/logging.h" | |
| 14 #include "base/message_loop.h" | |
| 15 #include "base/stl_util-inl.h" | |
| 16 #include "base/utf_string_conversions.h" | |
| 17 #include "chrome/app/chrome_command_ids.h" | |
| 18 #include "chrome/browser/gtk/gtk_custom_menu.h" | |
| 19 #include "chrome/browser/gtk/gtk_custom_menu_item.h" | |
| 20 #include "chrome/browser/gtk/gtk_util.h" | |
| 21 #include "gfx/gtk_util.h" | |
| 22 #include "third_party/skia/include/core/SkBitmap.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 menus::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) { | |
| 51 return reinterpret_cast<menus::MenuModel*>( | |
| 52 g_object_get_data(G_OBJECT(menu_item), "model")); | |
| 53 } | |
| 54 | |
| 55 void SetupButtonShowHandler(GtkWidget* button, | |
| 56 menus::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 menus::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 menus::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::Popup(GtkWidget* widget, GdkEvent* event) { | |
| 373 DCHECK(event->type == GDK_BUTTON_PRESS) | |
| 374 << "Non-button press event sent to RunMenuAt"; | |
| 375 | |
| 376 Popup(widget, event->button.button, event->button.time); | |
| 377 } | |
| 378 | |
| 379 void MenuGtk::Popup(GtkWidget* widget, gint button_type, guint32 timestamp) { | |
| 380 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, | |
| 381 WidgetMenuPositionFunc, | |
| 382 widget, | |
| 383 button_type, timestamp); | |
| 384 } | |
| 385 | |
| 386 void MenuGtk::PopupAsContext(guint32 event_time) { | |
| 387 // TODO(estade): |button| value of 3 (6th argument) is not strictly true, | |
| 388 // but does it matter? | |
| 389 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, NULL, NULL, 3, event_time); | |
| 390 } | |
| 391 | |
| 392 void MenuGtk::PopupAsContextAt(guint32 event_time, gfx::Point point) { | |
| 393 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, | |
| 394 PointMenuPositionFunc, &point, 3, event_time); | |
| 395 } | |
| 396 | |
| 397 void MenuGtk::PopupAsContextForStatusIcon(guint32 event_time, guint32 button, | |
| 398 GtkStatusIcon* icon) { | |
| 399 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, gtk_status_icon_position_menu, | |
| 400 icon, button, event_time); | |
| 401 } | |
| 402 | |
| 403 void MenuGtk::PopupAsFromKeyEvent(GtkWidget* widget) { | |
| 404 Popup(widget, 0, gtk_get_current_event_time()); | |
| 405 gtk_menu_shell_select_first(GTK_MENU_SHELL(menu_), FALSE); | |
| 406 } | |
| 407 | |
| 408 void MenuGtk::Cancel() { | |
| 409 gtk_menu_popdown(GTK_MENU(menu_)); | |
| 410 } | |
| 411 | |
| 412 void MenuGtk::UpdateMenu() { | |
| 413 gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this); | |
| 414 } | |
| 415 | |
| 416 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label, | |
| 417 GtkWidget* image) { | |
| 418 GtkWidget* menu_item = | |
| 419 gtk_image_menu_item_new_with_mnemonic(label.c_str()); | |
| 420 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image); | |
| 421 return menu_item; | |
| 422 } | |
| 423 | |
| 424 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label, | |
| 425 const SkBitmap& icon) { | |
| 426 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); | |
| 427 GtkWidget* menu_item = BuildMenuItemWithImage(label, | |
| 428 gtk_image_new_from_pixbuf(pixbuf)); | |
| 429 g_object_unref(pixbuf); | |
| 430 return menu_item; | |
| 431 } | |
| 432 | |
| 433 GtkWidget* MenuGtk::BuildMenuItemWithLabel(const std::string& label, | |
| 434 int command_id) { | |
| 435 GtkWidget* img = | |
| 436 delegate_ ? delegate_->GetImageForCommandId(command_id) : | |
| 437 MenuGtk::Delegate::GetDefaultImageForCommandId(command_id); | |
| 438 return img ? BuildMenuItemWithImage(label, img) : | |
| 439 gtk_menu_item_new_with_mnemonic(label.c_str()); | |
| 440 } | |
| 441 | |
| 442 void MenuGtk::BuildMenuFromModel() { | |
| 443 BuildSubmenuFromModel(model_, menu_); | |
| 444 } | |
| 445 | |
| 446 void MenuGtk::BuildSubmenuFromModel(menus::MenuModel* model, GtkWidget* menu) { | |
| 447 std::map<int, GtkWidget*> radio_groups; | |
| 448 GtkWidget* menu_item = NULL; | |
| 449 for (int i = 0; i < model->GetItemCount(); ++i) { | |
| 450 SkBitmap icon; | |
| 451 std::string label = | |
| 452 gfx::ConvertAcceleratorsFromWindowsStyle( | |
| 453 UTF16ToUTF8(model->GetLabelAt(i))); | |
| 454 bool connect_to_activate = true; | |
| 455 | |
| 456 switch (model->GetTypeAt(i)) { | |
| 457 case menus::MenuModel::TYPE_SEPARATOR: | |
| 458 menu_item = gtk_separator_menu_item_new(); | |
| 459 break; | |
| 460 | |
| 461 case menus::MenuModel::TYPE_CHECK: | |
| 462 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); | |
| 463 break; | |
| 464 | |
| 465 case menus::MenuModel::TYPE_RADIO: { | |
| 466 std::map<int, GtkWidget*>::iterator iter = | |
| 467 radio_groups.find(model->GetGroupIdAt(i)); | |
| 468 | |
| 469 if (iter == radio_groups.end()) { | |
| 470 menu_item = gtk_radio_menu_item_new_with_mnemonic( | |
| 471 NULL, label.c_str()); | |
| 472 radio_groups[model->GetGroupIdAt(i)] = menu_item; | |
| 473 } else { | |
| 474 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( | |
| 475 GTK_RADIO_MENU_ITEM(iter->second), label.c_str()); | |
| 476 } | |
| 477 break; | |
| 478 } | |
| 479 case menus::MenuModel::TYPE_BUTTON_ITEM: { | |
| 480 menus::ButtonMenuItemModel* button_menu_item_model = | |
| 481 model->GetButtonMenuItemAt(i); | |
| 482 menu_item = BuildButtomMenuItem(button_menu_item_model, menu); | |
| 483 connect_to_activate = false; | |
| 484 break; | |
| 485 } | |
| 486 case menus::MenuModel::TYPE_SUBMENU: | |
| 487 case menus::MenuModel::TYPE_COMMAND: { | |
| 488 int command_id = model->GetCommandIdAt(i); | |
| 489 if (model->GetIconAt(i, &icon)) | |
| 490 menu_item = BuildMenuItemWithImage(label, icon); | |
| 491 else | |
| 492 menu_item = BuildMenuItemWithLabel(label, command_id); | |
| 493 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) && | |
| 494 GTK_IS_IMAGE_MENU_ITEM(menu_item)) | |
| 495 gtk_util::SetAlwaysShowImage(menu_item); | |
| 496 break; | |
| 497 } | |
| 498 | |
| 499 default: | |
| 500 NOTREACHED(); | |
| 501 } | |
| 502 | |
| 503 if (model->GetTypeAt(i) == menus::MenuModel::TYPE_SUBMENU) { | |
| 504 GtkWidget* submenu = gtk_menu_new(); | |
| 505 BuildSubmenuFromModel(model->GetSubmenuModelAt(i), submenu); | |
| 506 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | |
| 507 } | |
| 508 | |
| 509 menus::AcceleratorGtk accelerator; | |
| 510 if (model->GetAcceleratorAt(i, &accelerator)) { | |
| 511 gtk_widget_add_accelerator(menu_item, | |
| 512 "activate", | |
| 513 dummy_accel_group_, | |
| 514 accelerator.GetGdkKeyCode(), | |
| 515 accelerator.gdk_modifier_type(), | |
| 516 GTK_ACCEL_VISIBLE); | |
| 517 } | |
| 518 | |
| 519 g_object_set_data(G_OBJECT(menu_item), "model", model); | |
| 520 AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate); | |
| 521 | |
| 522 menu_item = NULL; | |
| 523 } | |
| 524 } | |
| 525 | |
| 526 GtkWidget* MenuGtk::BuildButtomMenuItem(menus::ButtonMenuItemModel* model, | |
| 527 GtkWidget* menu) { | |
| 528 GtkWidget* menu_item = gtk_custom_menu_item_new( | |
| 529 gfx::RemoveWindowsStyleAccelerators(UTF16ToUTF8(model->label())).c_str()); | |
| 530 | |
| 531 // Set up the callback to the model for when it is clicked. | |
| 532 g_object_set_data(G_OBJECT(menu_item), "button-model", model); | |
| 533 g_signal_connect(menu_item, "button-pushed", | |
| 534 G_CALLBACK(OnMenuButtonPressedThunk), this); | |
| 535 g_signal_connect(menu_item, "try-button-pushed", | |
| 536 G_CALLBACK(OnMenuTryButtonPressedThunk), this); | |
| 537 | |
| 538 GtkSizeGroup* group = NULL; | |
| 539 for (int i = 0; i < model->GetItemCount(); ++i) { | |
| 540 GtkWidget* button = NULL; | |
| 541 | |
| 542 switch (model->GetTypeAt(i)) { | |
| 543 case menus::ButtonMenuItemModel::TYPE_SPACE: { | |
| 544 gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item)); | |
| 545 break; | |
| 546 } | |
| 547 case menus::ButtonMenuItemModel::TYPE_BUTTON: { | |
| 548 button = gtk_custom_menu_item_add_button( | |
| 549 GTK_CUSTOM_MENU_ITEM(menu_item), | |
| 550 model->GetCommandIdAt(i)); | |
| 551 | |
| 552 int icon_idr; | |
| 553 if (model->GetIconAt(i, &icon_idr)) { | |
| 554 SetupImageIcon(button, menu, icon_idr, delegate_); | |
| 555 } else { | |
| 556 gtk_button_set_label( | |
| 557 GTK_BUTTON(button), | |
| 558 gfx::RemoveWindowsStyleAccelerators( | |
| 559 UTF16ToUTF8(model->GetLabelAt(i))).c_str()); | |
| 560 } | |
| 561 | |
| 562 SetupButtonShowHandler(button, model, i); | |
| 563 break; | |
| 564 } | |
| 565 case menus::ButtonMenuItemModel::TYPE_BUTTON_LABEL: { | |
| 566 button = gtk_custom_menu_item_add_button_label( | |
| 567 GTK_CUSTOM_MENU_ITEM(menu_item), | |
| 568 model->GetCommandIdAt(i)); | |
| 569 gtk_button_set_label( | |
| 570 GTK_BUTTON(button), | |
| 571 gfx::RemoveWindowsStyleAccelerators( | |
| 572 UTF16ToUTF8(model->GetLabelAt(i))).c_str()); | |
| 573 SetupButtonShowHandler(button, model, i); | |
| 574 break; | |
| 575 } | |
| 576 } | |
| 577 | |
| 578 if (button && model->PartOfGroup(i)) { | |
| 579 if (!group) | |
| 580 group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); | |
| 581 | |
| 582 gtk_size_group_add_widget(group, button); | |
| 583 } | |
| 584 } | |
| 585 | |
| 586 if (group) { | |
| 587 g_object_unref(group); | |
| 588 } | |
| 589 | |
| 590 return menu_item; | |
| 591 } | |
| 592 | |
| 593 void MenuGtk::OnMenuItemActivated(GtkWidget* menuitem) { | |
| 594 if (block_activation_) | |
| 595 return; | |
| 596 | |
| 597 // We receive activation messages when highlighting a menu that has a | |
| 598 // submenu. Ignore them. | |
| 599 if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menuitem))) | |
| 600 return; | |
| 601 | |
| 602 // The activate signal is sent to radio items as they get deselected; | |
| 603 // ignore it in this case. | |
| 604 if (GTK_IS_RADIO_MENU_ITEM(menuitem) && | |
| 605 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) { | |
| 606 return; | |
| 607 } | |
| 608 | |
| 609 int id; | |
| 610 if (!GetMenuItemID(menuitem, &id)) | |
| 611 return; | |
| 612 | |
| 613 menus::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menuitem)); | |
| 614 | |
| 615 // The menu item can still be activated by hotkeys even if it is disabled. | |
| 616 if (model->IsEnabledAt(id)) | |
| 617 ExecuteCommand(model, id); | |
| 618 } | |
| 619 | |
| 620 void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) { | |
| 621 menus::ButtonMenuItemModel* model = | |
| 622 reinterpret_cast<menus::ButtonMenuItemModel*>( | |
| 623 g_object_get_data(G_OBJECT(menu_item), "button-model")); | |
| 624 if (model && model->IsCommandIdEnabled(command_id)) { | |
| 625 if (delegate_) | |
| 626 delegate_->CommandWillBeExecuted(); | |
| 627 | |
| 628 model->ActivatedCommand(command_id); | |
| 629 } | |
| 630 } | |
| 631 | |
| 632 gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item, | |
| 633 int command_id) { | |
| 634 gboolean pressed = FALSE; | |
| 635 menus::ButtonMenuItemModel* model = | |
| 636 reinterpret_cast<menus::ButtonMenuItemModel*>( | |
| 637 g_object_get_data(G_OBJECT(menu_item), "button-model")); | |
| 638 if (model && | |
| 639 model->IsCommandIdEnabled(command_id) && | |
| 640 !model->DoesCommandIdDismissMenu(command_id)) { | |
| 641 if (delegate_) | |
| 642 delegate_->CommandWillBeExecuted(); | |
| 643 | |
| 644 model->ActivatedCommand(command_id); | |
| 645 pressed = TRUE; | |
| 646 } | |
| 647 | |
| 648 return pressed; | |
| 649 } | |
| 650 | |
| 651 // static | |
| 652 void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu, | |
| 653 int* x, | |
| 654 int* y, | |
| 655 gboolean* push_in, | |
| 656 void* void_widget) { | |
| 657 GtkWidget* widget = GTK_WIDGET(void_widget); | |
| 658 GtkRequisition menu_req; | |
| 659 | |
| 660 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); | |
| 661 | |
| 662 gdk_window_get_origin(widget->window, x, y); | |
| 663 GdkScreen *screen = gtk_widget_get_screen(widget); | |
| 664 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y); | |
| 665 | |
| 666 GdkRectangle screen_rect; | |
| 667 gdk_screen_get_monitor_geometry(screen, monitor, | |
| 668 &screen_rect); | |
| 669 | |
| 670 if (GTK_WIDGET_NO_WINDOW(widget)) { | |
| 671 *x += widget->allocation.x; | |
| 672 *y += widget->allocation.y; | |
| 673 } | |
| 674 *y += widget->allocation.height; | |
| 675 | |
| 676 bool start_align = | |
| 677 !!g_object_get_data(G_OBJECT(widget), "left-align-popup"); | |
| 678 if (base::i18n::IsRTL()) | |
| 679 start_align = !start_align; | |
| 680 | |
| 681 if (!start_align) | |
| 682 *x += widget->allocation.width - menu_req.width; | |
| 683 | |
| 684 *y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y); | |
| 685 | |
| 686 *push_in = FALSE; | |
| 687 } | |
| 688 | |
| 689 // static | |
| 690 void MenuGtk::PointMenuPositionFunc(GtkMenu* menu, | |
| 691 int* x, | |
| 692 int* y, | |
| 693 gboolean* push_in, | |
| 694 gpointer userdata) { | |
| 695 *push_in = TRUE; | |
| 696 | |
| 697 gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata); | |
| 698 *x = point->x(); | |
| 699 *y = point->y(); | |
| 700 | |
| 701 GtkRequisition menu_req; | |
| 702 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); | |
| 703 GdkScreen* screen; | |
| 704 gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL); | |
| 705 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y); | |
| 706 | |
| 707 GdkRectangle screen_rect; | |
| 708 gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect); | |
| 709 | |
| 710 *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y); | |
| 711 } | |
| 712 | |
| 713 void MenuGtk::ExecuteCommand(menus::MenuModel* model, int id) { | |
| 714 if (delegate_) | |
| 715 delegate_->CommandWillBeExecuted(); | |
| 716 | |
| 717 GdkEvent* event = gtk_get_current_event(); | |
| 718 if (event && event->type == GDK_BUTTON_RELEASE) { | |
| 719 model->ActivatedAtWithDisposition( | |
| 720 id, event_utils::DispositionFromEventFlags(event->button.state)); | |
| 721 } else { | |
| 722 model->ActivatedAt(id); | |
| 723 } | |
| 724 | |
| 725 if (event) | |
| 726 gdk_event_free(event); | |
| 727 } | |
| 728 | |
| 729 void MenuGtk::OnMenuShow(GtkWidget* widget) { | |
| 730 MessageLoop::current()->PostTask(FROM_HERE, | |
| 731 factory_.NewRunnableMethod(&MenuGtk::UpdateMenu)); | |
| 732 } | |
| 733 | |
| 734 void MenuGtk::OnMenuHidden(GtkWidget* widget) { | |
| 735 if (delegate_) | |
| 736 delegate_->StoppedShowing(); | |
| 737 } | |
| 738 | |
| 739 // static | |
| 740 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) { | |
| 741 menus::ButtonMenuItemModel* model = | |
| 742 reinterpret_cast<menus::ButtonMenuItemModel*>( | |
| 743 g_object_get_data(G_OBJECT(button), "button-model")); | |
| 744 int index = GPOINTER_TO_INT(g_object_get_data( | |
| 745 G_OBJECT(button), "button-model-id")); | |
| 746 | |
| 747 if (model->IsItemDynamicAt(index)) { | |
| 748 std::string label = | |
| 749 gfx::ConvertAcceleratorsFromWindowsStyle( | |
| 750 UTF16ToUTF8(model->GetLabelAt(index))); | |
| 751 gtk_button_set_label(GTK_BUTTON(button), label.c_str()); | |
| 752 } | |
| 753 | |
| 754 gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index)); | |
| 755 } | |
| 756 | |
| 757 // static | |
| 758 void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) { | |
| 759 if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) { | |
| 760 // We need to explicitly handle this case because otherwise we'll ask the | |
| 761 // menu delegate about something with an invalid id. | |
| 762 return; | |
| 763 } | |
| 764 | |
| 765 int id; | |
| 766 if (!GetMenuItemID(widget, &id)) | |
| 767 return; | |
| 768 | |
| 769 menus::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget)); | |
| 770 if (!model) { | |
| 771 // If we're not providing the sub menu, then there's no model. For | |
| 772 // example, the IME submenu doesn't have a model. | |
| 773 return; | |
| 774 } | |
| 775 | |
| 776 if (GTK_IS_CHECK_MENU_ITEM(widget)) { | |
| 777 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget); | |
| 778 | |
| 779 // gtk_check_menu_item_set_active() will send the activate signal. Touching | |
| 780 // the underlying "active" property will also call the "activate" handler | |
| 781 // for this menu item. So we prevent the "activate" handler from | |
| 782 // being called while we set the checkbox. | |
| 783 // Why not use one of the glib signal-blocking functions? Because when we | |
| 784 // toggle a radio button, it will deactivate one of the other radio buttons, | |
| 785 // which we don't have a pointer to. | |
| 786 // Wny not make this a member variable? Because "menu" is a pointer to the | |
| 787 // root of the MenuGtk and we want to disable *all* MenuGtks, including | |
| 788 // submenus. | |
| 789 block_activation_ = true; | |
| 790 gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id)); | |
| 791 block_activation_ = false; | |
| 792 } | |
| 793 | |
| 794 if (GTK_IS_CUSTOM_MENU_ITEM(widget)) { | |
| 795 // Iterate across all the buttons to update their visible properties. | |
| 796 gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget), | |
| 797 SetButtonItemInfo, | |
| 798 userdata); | |
| 799 } | |
| 800 | |
| 801 if (GTK_IS_MENU_ITEM(widget)) { | |
| 802 gtk_widget_set_sensitive(widget, model->IsEnabledAt(id)); | |
| 803 | |
| 804 if (model->IsVisibleAt(id)) { | |
| 805 // Update the menu item label if it is dynamic. | |
| 806 if (model->IsItemDynamicAt(id)) { | |
| 807 std::string label = | |
| 808 gfx::ConvertAcceleratorsFromWindowsStyle( | |
| 809 UTF16ToUTF8(model->GetLabelAt(id))); | |
| 810 | |
| 811 #if GTK_CHECK_VERSION(2, 16, 0) | |
| 812 gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str()); | |
| 813 #else | |
| 814 gtk_label_set_label(GTK_LABEL(GTK_BIN(widget)->child), label.c_str()); | |
| 815 #endif | |
| 816 if (GTK_IS_IMAGE_MENU_ITEM(widget)) { | |
| 817 SkBitmap icon; | |
| 818 if (model->GetIconAt(id, &icon)) { | |
| 819 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); | |
| 820 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), | |
| 821 gtk_image_new_from_pixbuf(pixbuf)); | |
| 822 g_object_unref(pixbuf); | |
| 823 } else { | |
| 824 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL); | |
| 825 } | |
| 826 } | |
| 827 } | |
| 828 | |
| 829 gtk_widget_show(widget); | |
| 830 } else { | |
| 831 gtk_widget_hide(widget); | |
| 832 } | |
| 833 | |
| 834 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); | |
| 835 if (submenu) { | |
| 836 gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo, | |
| 837 userdata); | |
| 838 } | |
| 839 } | |
| 840 } | |
| OLD | NEW |