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