| OLD | NEW |
| (Empty) |
| 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 base::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( | |
| 568 base::UTF16ToUTF8(model->label())).c_str()); | |
| 569 | |
| 570 // Set up the callback to the model for when it is clicked. | |
| 571 g_object_set_data(G_OBJECT(menu_item), "button-model", model); | |
| 572 g_signal_connect(menu_item, "button-pushed", | |
| 573 G_CALLBACK(OnMenuButtonPressedThunk), this); | |
| 574 g_signal_connect(menu_item, "try-button-pushed", | |
| 575 G_CALLBACK(OnMenuTryButtonPressedThunk), this); | |
| 576 | |
| 577 GtkSizeGroup* group = NULL; | |
| 578 for (int i = 0; i < model->GetItemCount(); ++i) { | |
| 579 GtkWidget* button = NULL; | |
| 580 | |
| 581 switch (model->GetTypeAt(i)) { | |
| 582 case ui::ButtonMenuItemModel::TYPE_SPACE: { | |
| 583 gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item)); | |
| 584 break; | |
| 585 } | |
| 586 case ui::ButtonMenuItemModel::TYPE_BUTTON: { | |
| 587 button = gtk_custom_menu_item_add_button( | |
| 588 GTK_CUSTOM_MENU_ITEM(menu_item), | |
| 589 model->GetCommandIdAt(i)); | |
| 590 | |
| 591 int icon_idr; | |
| 592 if (model->GetIconAt(i, &icon_idr)) { | |
| 593 SetupImageIcon(button, menu, icon_idr, delegate_); | |
| 594 } else { | |
| 595 gtk_button_set_label( | |
| 596 GTK_BUTTON(button), | |
| 597 ui::RemoveWindowsStyleAccelerators( | |
| 598 base::UTF16ToUTF8(model->GetLabelAt(i))).c_str()); | |
| 599 } | |
| 600 | |
| 601 SetUpButtonShowHandler(button, model, i); | |
| 602 break; | |
| 603 } | |
| 604 case ui::ButtonMenuItemModel::TYPE_BUTTON_LABEL: { | |
| 605 button = gtk_custom_menu_item_add_button_label( | |
| 606 GTK_CUSTOM_MENU_ITEM(menu_item), | |
| 607 model->GetCommandIdAt(i)); | |
| 608 gtk_button_set_label( | |
| 609 GTK_BUTTON(button), | |
| 610 ui::RemoveWindowsStyleAccelerators( | |
| 611 base::UTF16ToUTF8(model->GetLabelAt(i))).c_str()); | |
| 612 SetUpButtonShowHandler(button, model, i); | |
| 613 break; | |
| 614 } | |
| 615 } | |
| 616 | |
| 617 if (button && model->PartOfGroup(i)) { | |
| 618 if (!group) | |
| 619 group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); | |
| 620 | |
| 621 gtk_size_group_add_widget(group, button); | |
| 622 } | |
| 623 } | |
| 624 | |
| 625 if (group) | |
| 626 g_object_unref(group); | |
| 627 | |
| 628 return menu_item; | |
| 629 } | |
| 630 | |
| 631 void MenuGtk::OnMenuItemActivated(GtkWidget* menu_item) { | |
| 632 if (block_activation_) | |
| 633 return; | |
| 634 | |
| 635 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item)); | |
| 636 | |
| 637 if (!model) { | |
| 638 // There won't be a model for "native" submenus like the "Input Methods" | |
| 639 // context menu. We don't need to handle activation messages for submenus | |
| 640 // anyway, so we can just return here. | |
| 641 DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item))); | |
| 642 return; | |
| 643 } | |
| 644 | |
| 645 // The activate signal is sent to radio items as they get deselected; | |
| 646 // ignore it in this case. | |
| 647 if (GTK_IS_RADIO_MENU_ITEM(menu_item) && | |
| 648 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) { | |
| 649 return; | |
| 650 } | |
| 651 | |
| 652 int id; | |
| 653 if (!GetMenuItemID(menu_item, &id)) | |
| 654 return; | |
| 655 | |
| 656 // The menu item can still be activated by hotkeys even if it is disabled. | |
| 657 if (model->IsEnabledAt(id)) | |
| 658 ExecuteCommand(model, id); | |
| 659 } | |
| 660 | |
| 661 void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) { | |
| 662 ui::ButtonMenuItemModel* model = | |
| 663 reinterpret_cast<ui::ButtonMenuItemModel*>( | |
| 664 g_object_get_data(G_OBJECT(menu_item), "button-model")); | |
| 665 if (model && model->IsCommandIdEnabled(command_id)) { | |
| 666 if (delegate_) | |
| 667 delegate_->CommandWillBeExecuted(); | |
| 668 | |
| 669 model->ActivatedCommand(command_id); | |
| 670 } | |
| 671 } | |
| 672 | |
| 673 gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item, | |
| 674 int command_id) { | |
| 675 gboolean pressed = FALSE; | |
| 676 ui::ButtonMenuItemModel* model = | |
| 677 reinterpret_cast<ui::ButtonMenuItemModel*>( | |
| 678 g_object_get_data(G_OBJECT(menu_item), "button-model")); | |
| 679 if (model && | |
| 680 model->IsCommandIdEnabled(command_id) && | |
| 681 !model->DoesCommandIdDismissMenu(command_id)) { | |
| 682 if (delegate_) | |
| 683 delegate_->CommandWillBeExecuted(); | |
| 684 | |
| 685 model->ActivatedCommand(command_id); | |
| 686 pressed = TRUE; | |
| 687 } | |
| 688 | |
| 689 return pressed; | |
| 690 } | |
| 691 | |
| 692 // static | |
| 693 void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu, | |
| 694 int* x, | |
| 695 int* y, | |
| 696 gboolean* push_in, | |
| 697 void* void_widget) { | |
| 698 GtkWidget* widget = GTK_WIDGET(void_widget); | |
| 699 GtkRequisition menu_req; | |
| 700 | |
| 701 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); | |
| 702 | |
| 703 gdk_window_get_origin(gtk_widget_get_window(widget), x, y); | |
| 704 GdkScreen *screen = gtk_widget_get_screen(widget); | |
| 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, | |
| 709 &screen_rect); | |
| 710 | |
| 711 GtkAllocation allocation; | |
| 712 gtk_widget_get_allocation(widget, &allocation); | |
| 713 | |
| 714 if (!gtk_widget_get_has_window(widget)) { | |
| 715 *x += allocation.x; | |
| 716 *y += allocation.y; | |
| 717 } | |
| 718 *y += allocation.height; | |
| 719 | |
| 720 bool start_align = | |
| 721 !!g_object_get_data(G_OBJECT(widget), "left-align-popup"); | |
| 722 if (base::i18n::IsRTL()) | |
| 723 start_align = !start_align; | |
| 724 | |
| 725 if (!start_align) | |
| 726 *x += allocation.width - menu_req.width; | |
| 727 | |
| 728 *y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y); | |
| 729 | |
| 730 *push_in = FALSE; | |
| 731 } | |
| 732 | |
| 733 // static | |
| 734 void MenuGtk::PointMenuPositionFunc(GtkMenu* menu, | |
| 735 int* x, | |
| 736 int* y, | |
| 737 gboolean* push_in, | |
| 738 gpointer userdata) { | |
| 739 *push_in = TRUE; | |
| 740 | |
| 741 gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata); | |
| 742 *x = point->x(); | |
| 743 *y = point->y(); | |
| 744 | |
| 745 GtkRequisition menu_req; | |
| 746 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); | |
| 747 GdkScreen* screen; | |
| 748 gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL); | |
| 749 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y); | |
| 750 | |
| 751 GdkRectangle screen_rect; | |
| 752 gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect); | |
| 753 | |
| 754 *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y); | |
| 755 } | |
| 756 | |
| 757 void MenuGtk::ExecuteCommand(ui::MenuModel* model, int id) { | |
| 758 if (delegate_) | |
| 759 delegate_->CommandWillBeExecuted(); | |
| 760 | |
| 761 GdkEvent* event = gtk_get_current_event(); | |
| 762 int event_flags = 0; | |
| 763 | |
| 764 if (event && event->type == GDK_BUTTON_RELEASE) | |
| 765 event_flags = event_utils::EventFlagsFromGdkState(event->button.state); | |
| 766 model->ActivatedAt(id, event_flags); | |
| 767 | |
| 768 if (event) | |
| 769 gdk_event_free(event); | |
| 770 } | |
| 771 | |
| 772 void MenuGtk::OnMenuShow(GtkWidget* widget) { | |
| 773 model_->MenuWillShow(); | |
| 774 base::MessageLoop::current()->PostTask( | |
| 775 FROM_HERE, base::Bind(&MenuGtk::UpdateMenu, weak_factory_.GetWeakPtr())); | |
| 776 } | |
| 777 | |
| 778 void MenuGtk::OnMenuHidden(GtkWidget* widget) { | |
| 779 if (delegate_) | |
| 780 delegate_->StoppedShowing(); | |
| 781 model_->MenuClosed(); | |
| 782 } | |
| 783 | |
| 784 gboolean MenuGtk::OnMenuFocusOut(GtkWidget* widget, GdkEventFocus* event) { | |
| 785 gtk_widget_hide(menu_); | |
| 786 return TRUE; | |
| 787 } | |
| 788 | |
| 789 void MenuGtk::OnSubMenuShow(GtkWidget* submenu) { | |
| 790 GtkWidget* menu_item = static_cast<GtkWidget*>( | |
| 791 g_object_get_data(G_OBJECT(submenu), "menu-item")); | |
| 792 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974. | |
| 793 CHECK(menu_item); | |
| 794 // Notify the submenu model that the menu will be shown. | |
| 795 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( | |
| 796 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); | |
| 797 // We're extra cautious here, and bail out if the submenu model is NULL. In | |
| 798 // some cases we clear it out from a parent menu; we shouldn't ever show the | |
| 799 // menu after that, but we play it safe since we're dealing with wacky | |
| 800 // injected libraries that toy with our menus. (See comments below.) | |
| 801 if (!submenu_model) | |
| 802 return; | |
| 803 | |
| 804 // If the submenu is already built, then return right away. This means we | |
| 805 // recently showed this submenu, and have not yet processed the fact that it | |
| 806 // was hidden before being shown again. | |
| 807 if (g_object_get_data(G_OBJECT(submenu), "submenu-built")) | |
| 808 return; | |
| 809 g_object_set_data(G_OBJECT(submenu), "submenu-built", GINT_TO_POINTER(1)); | |
| 810 | |
| 811 submenu_model->MenuWillShow(); | |
| 812 | |
| 813 // Actually build the submenu and attach it to the parent menu item. | |
| 814 BuildSubmenuFromModel(submenu_model, submenu); | |
| 815 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | |
| 816 | |
| 817 // Update all the menu item info in the newly-generated menu. | |
| 818 gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo, this); | |
| 819 } | |
| 820 | |
| 821 void MenuGtk::OnSubMenuHidden(GtkWidget* submenu) { | |
| 822 // Increase the reference count of the old submenu, and schedule it to be | |
| 823 // deleted later. We get this hide notification before we've processed menu | |
| 824 // activations, so if we were to delete the submenu now, we might lose the | |
| 825 // activation. This also lets us reuse the menu if it is shown again before | |
| 826 // it gets deleted; in that case, OnSubMenuHiddenCallback() just decrements | |
| 827 // the reference count again. Note that the delay is just an optimization; we | |
| 828 // could use PostTask() and this would still work correctly. | |
| 829 g_object_ref(G_OBJECT(submenu)); | |
| 830 base::MessageLoop::current()->PostDelayedTask( | |
| 831 FROM_HERE, | |
| 832 base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu), | |
| 833 base::TimeDelta::FromSeconds(2)); | |
| 834 } | |
| 835 | |
| 836 namespace { | |
| 837 | |
| 838 // Remove all descendant submenu-model data pointers. | |
| 839 void RemoveSubMenuModels(GtkWidget* menu_item, void* unused) { | |
| 840 if (!GTK_IS_MENU_ITEM(menu_item)) | |
| 841 return; | |
| 842 g_object_steal_data(G_OBJECT(menu_item), "submenu-model"); | |
| 843 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
| 844 if (submenu) | |
| 845 gtk_container_foreach(GTK_CONTAINER(submenu), RemoveSubMenuModels, NULL); | |
| 846 } | |
| 847 | |
| 848 } // namespace | |
| 849 | |
| 850 // static | |
| 851 void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) { | |
| 852 if (!gtk_widget_get_visible(submenu)) { | |
| 853 // Remove all the children of this menu, clearing out their submenu-model | |
| 854 // pointers in case they have pending calls to OnSubMenuHiddenCallback(). | |
| 855 // (Normally that won't happen: we'd have hidden them first, and so they'd | |
| 856 // have already been deleted. But in some cases [e.g. on Ubuntu 12.04], | |
| 857 // GTK menu operations may be hooked to allow external applications to | |
| 858 // mirror the menu structure, and the hooks may show and hide menus in | |
| 859 // order to trigger exactly the kind of dynamic menu building we're doing. | |
| 860 // The result is that we see show and hide events in strange orders.) | |
| 861 GList* children = gtk_container_get_children(GTK_CONTAINER(submenu)); | |
| 862 for (GList* child = children; child; child = g_list_next(child)) { | |
| 863 RemoveSubMenuModels(GTK_WIDGET(child->data), NULL); | |
| 864 gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(child->data)); | |
| 865 } | |
| 866 g_list_free(children); | |
| 867 | |
| 868 // Clear out the bit that says the menu is built. | |
| 869 // We'll rebuild it next time it is shown. | |
| 870 g_object_steal_data(G_OBJECT(submenu), "submenu-built"); | |
| 871 | |
| 872 // Notify the submenu model that the menu has been hidden. This may cause | |
| 873 // it to delete descendant submenu models, which is why we cleared those | |
| 874 // pointers out above. | |
| 875 GtkWidget* menu_item = static_cast<GtkWidget*>( | |
| 876 g_object_get_data(G_OBJECT(submenu), "menu-item")); | |
| 877 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110. | |
| 878 CHECK(menu_item); | |
| 879 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( | |
| 880 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); | |
| 881 if (submenu_model) | |
| 882 submenu_model->MenuClosed(); | |
| 883 } | |
| 884 | |
| 885 // Remove the reference we grabbed in OnSubMenuHidden() above. | |
| 886 g_object_unref(G_OBJECT(submenu)); | |
| 887 } | |
| 888 | |
| 889 // static | |
| 890 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) { | |
| 891 ui::ButtonMenuItemModel* model = | |
| 892 reinterpret_cast<ui::ButtonMenuItemModel*>( | |
| 893 g_object_get_data(G_OBJECT(button), "button-model")); | |
| 894 int index = GPOINTER_TO_INT(g_object_get_data( | |
| 895 G_OBJECT(button), "button-model-id")); | |
| 896 | |
| 897 if (model->IsItemDynamicAt(index)) { | |
| 898 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( | |
| 899 base::UTF16ToUTF8(model->GetLabelAt(index))); | |
| 900 gtk_button_set_label(GTK_BUTTON(button), label.c_str()); | |
| 901 } | |
| 902 | |
| 903 gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index)); | |
| 904 } | |
| 905 | |
| 906 // static | |
| 907 void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) { | |
| 908 if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) { | |
| 909 // We need to explicitly handle this case because otherwise we'll ask the | |
| 910 // menu delegate about something with an invalid id. | |
| 911 return; | |
| 912 } | |
| 913 | |
| 914 int id; | |
| 915 if (!GetMenuItemID(widget, &id)) | |
| 916 return; | |
| 917 | |
| 918 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget)); | |
| 919 if (!model) { | |
| 920 // If we're not providing the sub menu, then there's no model. For | |
| 921 // example, the IME submenu doesn't have a model. | |
| 922 return; | |
| 923 } | |
| 924 | |
| 925 if (GTK_IS_CHECK_MENU_ITEM(widget)) { | |
| 926 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget); | |
| 927 | |
| 928 // gtk_check_menu_item_set_active() will send the activate signal. Touching | |
| 929 // the underlying "active" property will also call the "activate" handler | |
| 930 // for this menu item. So we prevent the "activate" handler from | |
| 931 // being called while we set the checkbox. | |
| 932 // Why not use one of the glib signal-blocking functions? Because when we | |
| 933 // toggle a radio button, it will deactivate one of the other radio buttons, | |
| 934 // which we don't have a pointer to. | |
| 935 // Wny not make this a member variable? Because "menu" is a pointer to the | |
| 936 // root of the MenuGtk and we want to disable *all* MenuGtks, including | |
| 937 // submenus. | |
| 938 block_activation_ = true; | |
| 939 gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id)); | |
| 940 block_activation_ = false; | |
| 941 } | |
| 942 | |
| 943 if (GTK_IS_CUSTOM_MENU_ITEM(widget)) { | |
| 944 // Iterate across all the buttons to update their visible properties. | |
| 945 gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget), | |
| 946 SetButtonItemInfo, | |
| 947 userdata); | |
| 948 } | |
| 949 | |
| 950 if (GTK_IS_MENU_ITEM(widget)) { | |
| 951 gtk_widget_set_sensitive(widget, model->IsEnabledAt(id)); | |
| 952 | |
| 953 if (model->IsVisibleAt(id)) { | |
| 954 // Update the menu item label if it is dynamic. | |
| 955 if (model->IsItemDynamicAt(id)) { | |
| 956 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( | |
| 957 base::UTF16ToUTF8(model->GetLabelAt(id))); | |
| 958 | |
| 959 gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str()); | |
| 960 if (GTK_IS_IMAGE_MENU_ITEM(widget)) { | |
| 961 gfx::Image icon; | |
| 962 if (model->GetIconAt(id, &icon)) { | |
| 963 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), | |
| 964 gtk_image_new_from_pixbuf( | |
| 965 icon.ToGdkPixbuf())); | |
| 966 } else { | |
| 967 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL); | |
| 968 } | |
| 969 } | |
| 970 } | |
| 971 | |
| 972 gtk_widget_show(widget); | |
| 973 } else { | |
| 974 gtk_widget_hide(widget); | |
| 975 } | |
| 976 | |
| 977 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); | |
| 978 if (submenu) { | |
| 979 gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo, | |
| 980 userdata); | |
| 981 } | |
| 982 } | |
| 983 } | |
| OLD | NEW |