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::AppendMenuItem(int command_id, GtkWidget* menu_item) {
    353   if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
    354       GTK_IS_IMAGE_MENU_ITEM(menu_item))
    355     gtk_util::SetAlwaysShowImage(menu_item);
    356 
    357   return AppendMenuItemToMenu(command_id, NULL, menu_item, menu_, true);
    358 }
    359 
    360 GtkWidget* MenuGtk::AppendMenuItemToMenu(int index,
    361                                          ui::MenuModel* model,
    362                                          GtkWidget* menu_item,
    363                                          GtkWidget* menu,
    364                                          bool connect_to_activate) {
    365   SetMenuItemID(menu_item, index);
    366 
    367   // Native menu items do their own thing, so only selectively listen for the
    368   // activate signal.
    369   if (connect_to_activate) {
    370     g_signal_connect(menu_item, "activate",
    371                      G_CALLBACK(OnMenuItemActivatedThunk), this);
    372   }
    373 
    374   // AppendMenuItemToMenu is used both internally when we control menu creation
    375   // from a model (where the model can choose to hide certain menu items), and
    376   // with immediate commands which don't provide the option.
    377   if (model) {
    378     if (model->IsVisibleAt(index))
    379       gtk_widget_show(menu_item);
    380   } else {
    381     gtk_widget_show(menu_item);
    382   }
    383   gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
    384   return menu_item;
    385 }
    386 
    387 void MenuGtk::PopupForWidget(GtkWidget* widget, int button,
    388                              guint32 event_time) {
    389   gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
    390                  WidgetMenuPositionFunc,
    391                  widget,
    392                  button, event_time);
    393 }
    394 
    395 void MenuGtk::PopupAsContext(const gfx::Point& point, guint32 event_time) {
    396   // gtk_menu_popup doesn't like the "const" qualifier on point.
    397   gfx::Point nonconst_point(point);
    398   gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
    399                  PointMenuPositionFunc, &nonconst_point,
    400                  3, event_time);
    401 }
    402 
    403 void MenuGtk::PopupAsContextForStatusIcon(guint32 event_time, guint32 button,
    404                                           GtkStatusIcon* icon) {
    405   gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, gtk_status_icon_position_menu,
    406                  icon, button, event_time);
    407 }
    408 
    409 void MenuGtk::PopupAsFromKeyEvent(GtkWidget* widget) {
    410   PopupForWidget(widget, 0, gtk_get_current_event_time());
    411   gtk_menu_shell_select_first(GTK_MENU_SHELL(menu_), FALSE);
    412 }
    413 
    414 void MenuGtk::Cancel() {
    415   gtk_menu_popdown(GTK_MENU(menu_));
    416 }
    417 
    418 void MenuGtk::UpdateMenu() {
    419   gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this);
    420 }
    421 
    422 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
    423                                            GtkWidget* image) {
    424   GtkWidget* menu_item =
    425       gtk_image_menu_item_new_with_mnemonic(label.c_str());
    426   gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
    427   return menu_item;
    428 }
    429 
    430 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
    431                                            const gfx::Image& icon) {
    432   GtkWidget* menu_item = BuildMenuItemWithImage(label,
    433       gtk_image_new_from_pixbuf(icon.ToGdkPixbuf()));
    434   return menu_item;
    435 }
    436 
    437 GtkWidget* MenuGtk::BuildMenuItemWithLabel(const std::string& label,
    438                                            int command_id) {
    439   GtkWidget* img =
    440       delegate_ ? delegate_->GetImageForCommandId(command_id) :
    441                   MenuGtk::Delegate::GetDefaultImageForCommandId(command_id);
    442   return img ? BuildMenuItemWithImage(label, img) :
    443                gtk_menu_item_new_with_mnemonic(label.c_str());
    444 }
    445 
    446 void MenuGtk::BuildMenuFromModel() {
    447   BuildSubmenuFromModel(model_, menu_);
    448 }
    449 
    450 void MenuGtk::BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu) {
    451   std::map<int, GtkWidget*> radio_groups;
    452   GtkWidget* menu_item = NULL;
    453   for (int i = 0; i < model->GetItemCount(); ++i) {
    454     gfx::Image icon;
    455     std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
    456         UTF16ToUTF8(model->GetLabelAt(i)));
    457     bool connect_to_activate = true;
    458 
    459     switch (model->GetTypeAt(i)) {
    460       case ui::MenuModel::TYPE_SEPARATOR:
    461         menu_item = gtk_separator_menu_item_new();
    462         break;
    463 
    464       case ui::MenuModel::TYPE_CHECK:
    465         menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
    466         break;
    467 
    468       case ui::MenuModel::TYPE_RADIO: {
    469         std::map<int, GtkWidget*>::iterator iter =
    470             radio_groups.find(model->GetGroupIdAt(i));
    471 
    472         if (iter == radio_groups.end()) {
    473           menu_item = gtk_radio_menu_item_new_with_mnemonic(
    474               NULL, label.c_str());
    475           radio_groups[model->GetGroupIdAt(i)] = menu_item;
    476         } else {
    477           menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
    478               GTK_RADIO_MENU_ITEM(iter->second), label.c_str());
    479         }
    480         break;
    481       }
    482       case ui::MenuModel::TYPE_BUTTON_ITEM: {
    483         ui::ButtonMenuItemModel* button_menu_item_model =
    484             model->GetButtonMenuItemAt(i);
    485         menu_item = BuildButtonMenuItem(button_menu_item_model, menu);
    486         connect_to_activate = false;
    487         break;
    488       }
    489       case ui::MenuModel::TYPE_SUBMENU:
    490       case ui::MenuModel::TYPE_COMMAND: {
    491         int command_id = model->GetCommandIdAt(i);
    492         if (model->GetIconAt(i, &icon))
    493           menu_item = BuildMenuItemWithImage(label, icon);
    494         else
    495           menu_item = BuildMenuItemWithLabel(label, command_id);
    496         if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
    497             GTK_IS_IMAGE_MENU_ITEM(menu_item)) {
    498           gtk_util::SetAlwaysShowImage(menu_item);
    499         }
    500         break;
    501       }
    502 
    503       default:
    504         NOTREACHED();
    505     }
    506 
    507     if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
    508       GtkWidget* submenu = gtk_menu_new();
    509       g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item);
    510       ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i);
    511       g_object_set_data(G_OBJECT(menu_item), "submenu-model", submenu_model);
    512       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
    513       // We will populate the submenu on demand when shown.
    514       g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this);
    515       g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this);
    516       connect_to_activate = false;
    517     }
    518 
    519     ui::Accelerator accelerator;
    520     if (model->GetAcceleratorAt(i, &accelerator)) {
    521       gtk_widget_add_accelerator(menu_item,
    522                                  "activate",
    523                                  dummy_accel_group_,
    524                                  ui::GetGdkKeyCodeForAccelerator(accelerator),
    525                                  ui::GetGdkModifierForAccelerator(accelerator),
    526                                  GTK_ACCEL_VISIBLE);
    527     }
    528 
    529     g_object_set_data(G_OBJECT(menu_item), "model", model);
    530     AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate);
    531 
    532     menu_item = NULL;
    533   }
    534 }
    535 
    536 GtkWidget* MenuGtk::BuildButtonMenuItem(ui::ButtonMenuItemModel* model,
    537                                         GtkWidget* menu) {
    538   GtkWidget* menu_item = gtk_custom_menu_item_new(
    539       ui::RemoveWindowsStyleAccelerators(UTF16ToUTF8(model->label())).c_str());
    540 
    541   // Set up the callback to the model for when it is clicked.
    542   g_object_set_data(G_OBJECT(menu_item), "button-model", model);
    543   g_signal_connect(menu_item, "button-pushed",
    544                    G_CALLBACK(OnMenuButtonPressedThunk), this);
    545   g_signal_connect(menu_item, "try-button-pushed",
    546                    G_CALLBACK(OnMenuTryButtonPressedThunk), this);
    547 
    548   GtkSizeGroup* group = NULL;
    549   for (int i = 0; i < model->GetItemCount(); ++i) {
    550     GtkWidget* button = NULL;
    551 
    552     switch (model->GetTypeAt(i)) {
    553       case ui::ButtonMenuItemModel::TYPE_SPACE: {
    554         gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item));
    555         break;
    556       }
    557       case ui::ButtonMenuItemModel::TYPE_BUTTON: {
    558         button = gtk_custom_menu_item_add_button(
    559             GTK_CUSTOM_MENU_ITEM(menu_item),
    560             model->GetCommandIdAt(i));
    561 
    562         int icon_idr;
    563         if (model->GetIconAt(i, &icon_idr)) {
    564           SetupImageIcon(button, menu, icon_idr, delegate_);
    565         } else {
    566           gtk_button_set_label(
    567               GTK_BUTTON(button),
    568               ui::RemoveWindowsStyleAccelerators(
    569                   UTF16ToUTF8(model->GetLabelAt(i))).c_str());
    570         }
    571 
    572         SetUpButtonShowHandler(button, model, i);
    573         break;
    574       }
    575       case ui::ButtonMenuItemModel::TYPE_BUTTON_LABEL: {
    576         button = gtk_custom_menu_item_add_button_label(
    577             GTK_CUSTOM_MENU_ITEM(menu_item),
    578             model->GetCommandIdAt(i));
    579         gtk_button_set_label(
    580             GTK_BUTTON(button),
    581             ui::RemoveWindowsStyleAccelerators(
    582                 UTF16ToUTF8(model->GetLabelAt(i))).c_str());
    583         SetUpButtonShowHandler(button, model, i);
    584         break;
    585       }
    586     }
    587 
    588     if (button && model->PartOfGroup(i)) {
    589       if (!group)
    590         group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
    591 
    592       gtk_size_group_add_widget(group, button);
    593     }
    594   }
    595 
    596   if (group)
    597     g_object_unref(group);
    598 
    599   return menu_item;
    600 }
    601 
    602 void MenuGtk::OnMenuItemActivated(GtkWidget* menu_item) {
    603   if (block_activation_)
    604     return;
    605 
    606   ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
    607 
    608   if (!model) {
    609     // There won't be a model for "native" submenus like the "Input Methods"
    610     // context menu. We don't need to handle activation messages for submenus
    611     // anyway, so we can just return here.
    612     DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
    613     return;
    614   }
    615 
    616   // The activate signal is sent to radio items as they get deselected;
    617   // ignore it in this case.
    618   if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
    619       !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
    620     return;
    621   }
    622 
    623   int id;
    624   if (!GetMenuItemID(menu_item, &id))
    625     return;
    626 
    627   // The menu item can still be activated by hotkeys even if it is disabled.
    628   if (model->IsEnabledAt(id))
    629     ExecuteCommand(model, id);
    630 }
    631 
    632 void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) {
    633   ui::ButtonMenuItemModel* model =
    634       reinterpret_cast<ui::ButtonMenuItemModel*>(
    635           g_object_get_data(G_OBJECT(menu_item), "button-model"));
    636   if (model && model->IsCommandIdEnabled(command_id)) {
    637     if (delegate_)
    638       delegate_->CommandWillBeExecuted();
    639 
    640     model->ActivatedCommand(command_id);
    641   }
    642 }
    643 
    644 gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item,
    645                                          int command_id) {
    646   gboolean pressed = FALSE;
    647   ui::ButtonMenuItemModel* model =
    648       reinterpret_cast<ui::ButtonMenuItemModel*>(
    649           g_object_get_data(G_OBJECT(menu_item), "button-model"));
    650   if (model &&
    651       model->IsCommandIdEnabled(command_id) &&
    652       !model->DoesCommandIdDismissMenu(command_id)) {
    653     if (delegate_)
    654       delegate_->CommandWillBeExecuted();
    655 
    656     model->ActivatedCommand(command_id);
    657     pressed = TRUE;
    658   }
    659 
    660   return pressed;
    661 }
    662 
    663 // static
    664 void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu,
    665                                      int* x,
    666                                      int* y,
    667                                      gboolean* push_in,
    668                                      void* void_widget) {
    669   GtkWidget* widget = GTK_WIDGET(void_widget);
    670   GtkRequisition menu_req;
    671 
    672   gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
    673 
    674   gdk_window_get_origin(gtk_widget_get_window(widget), x, y);
    675   GdkScreen *screen = gtk_widget_get_screen(widget);
    676   gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
    677 
    678   GdkRectangle screen_rect;
    679   gdk_screen_get_monitor_geometry(screen, monitor,
    680                                   &screen_rect);
    681 
    682   GtkAllocation allocation;
    683   gtk_widget_get_allocation(widget, &allocation);
    684 
    685   if (!gtk_widget_get_has_window(widget)) {
    686     *x += allocation.x;
    687     *y += allocation.y;
    688   }
    689   *y += allocation.height;
    690 
    691   bool start_align =
    692     !!g_object_get_data(G_OBJECT(widget), "left-align-popup");
    693   if (base::i18n::IsRTL())
    694     start_align = !start_align;
    695 
    696   if (!start_align)
    697     *x += allocation.width - menu_req.width;
    698 
    699   *y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y);
    700 
    701   *push_in = FALSE;
    702 }
    703 
    704 // static
    705 void MenuGtk::PointMenuPositionFunc(GtkMenu* menu,
    706                                     int* x,
    707                                     int* y,
    708                                     gboolean* push_in,
    709                                     gpointer userdata) {
    710   *push_in = TRUE;
    711 
    712   gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata);
    713   *x = point->x();
    714   *y = point->y();
    715 
    716   GtkRequisition menu_req;
    717   gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
    718   GdkScreen* screen;
    719   gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL);
    720   gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
    721 
    722   GdkRectangle screen_rect;
    723   gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);
    724 
    725   *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y);
    726 }
    727 
    728 void MenuGtk::ExecuteCommand(ui::MenuModel* model, int id) {
    729   if (delegate_)
    730     delegate_->CommandWillBeExecuted();
    731 
    732   GdkEvent* event = gtk_get_current_event();
    733   int event_flags = 0;
    734 
    735   if (event && event->type == GDK_BUTTON_RELEASE)
    736     event_flags = event_utils::EventFlagsFromGdkState(event->button.state);
    737   model->ActivatedAt(id, event_flags);
    738 
    739   if (event)
    740     gdk_event_free(event);
    741 }
    742 
    743 void MenuGtk::OnMenuShow(GtkWidget* widget) {
    744   model_->MenuWillShow();
    745   base::MessageLoop::current()->PostTask(
    746       FROM_HERE, base::Bind(&MenuGtk::UpdateMenu, weak_factory_.GetWeakPtr()));
    747 }
    748 
    749 void MenuGtk::OnMenuHidden(GtkWidget* widget) {
    750   if (delegate_)
    751     delegate_->StoppedShowing();
    752   model_->MenuClosed();
    753 }
    754 
    755 gboolean MenuGtk::OnMenuFocusOut(GtkWidget* widget, GdkEventFocus* event) {
    756   gtk_widget_hide(menu_);
    757   return TRUE;
    758 }
    759 
    760 void MenuGtk::OnSubMenuShow(GtkWidget* submenu) {
    761   GtkWidget* menu_item = static_cast<GtkWidget*>(
    762       g_object_get_data(G_OBJECT(submenu), "menu-item"));
    763   // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974.
    764   CHECK(menu_item);
    765   // Notify the submenu model that the menu will be shown.
    766   ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>(
    767       g_object_get_data(G_OBJECT(menu_item), "submenu-model"));
    768   // We're extra cautious here, and bail out if the submenu model is NULL. In
    769   // some cases we clear it out from a parent menu; we shouldn't ever show the
    770   // menu after that, but we play it safe since we're dealing with wacky
    771   // injected libraries that toy with our menus. (See comments below.)
    772   if (!submenu_model)
    773     return;
    774 
    775   // If the submenu is already built, then return right away. This means we
    776   // recently showed this submenu, and have not yet processed the fact that it
    777   // was hidden before being shown again.
    778   if (g_object_get_data(G_OBJECT(submenu), "submenu-built"))
    779     return;
    780   g_object_set_data(G_OBJECT(submenu), "submenu-built", GINT_TO_POINTER(1));
    781 
    782   submenu_model->MenuWillShow();
    783 
    784   // Actually build the submenu and attach it to the parent menu item.
    785   BuildSubmenuFromModel(submenu_model, submenu);
    786   gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
    787 
    788   // Update all the menu item info in the newly-generated menu.
    789   gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo, this);
    790 }
    791 
    792 void MenuGtk::OnSubMenuHidden(GtkWidget* submenu) {
    793   // Increase the reference count of the old submenu, and schedule it to be
    794   // deleted later. We get this hide notification before we've processed menu
    795   // activations, so if we were to delete the submenu now, we might lose the
    796   // activation. This also lets us reuse the menu if it is shown again before
    797   // it gets deleted; in that case, OnSubMenuHiddenCallback() just decrements
    798   // the reference count again. Note that the delay is just an optimization; we
    799   // could use PostTask() and this would still work correctly.
    800   g_object_ref(G_OBJECT(submenu));
    801   base::MessageLoop::current()->PostDelayedTask(
    802       FROM_HERE,
    803       base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu),
    804       base::TimeDelta::FromSeconds(2));
    805 }
    806 
    807 namespace {
    808 
    809 // Remove all descendant submenu-model data pointers.
    810 void RemoveSubMenuModels(GtkWidget* menu_item, void* unused) {
    811   if (!GTK_IS_MENU_ITEM(menu_item))
    812     return;
    813   g_object_steal_data(G_OBJECT(menu_item), "submenu-model");
    814   GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
    815   if (submenu)
    816     gtk_container_foreach(GTK_CONTAINER(submenu), RemoveSubMenuModels, NULL);
    817 }
    818 
    819 }  // namespace
    820 
    821 // static
    822 void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) {
    823   if (!gtk_widget_get_visible(submenu)) {
    824     // Remove all the children of this menu, clearing out their submenu-model
    825     // pointers in case they have pending calls to OnSubMenuHiddenCallback().
    826     // (Normally that won't happen: we'd have hidden them first, and so they'd
    827     // have already been deleted. But in some cases [e.g. on Ubuntu 12.04],
    828     // GTK menu operations may be hooked to allow external applications to
    829     // mirror the menu structure, and the hooks may show and hide menus in
    830     // order to trigger exactly the kind of dynamic menu building we're doing.
    831     // The result is that we see show and hide events in strange orders.)
    832     GList* children = gtk_container_get_children(GTK_CONTAINER(submenu));
    833     for (GList* child = children; child; child = g_list_next(child)) {
    834       RemoveSubMenuModels(GTK_WIDGET(child->data), NULL);
    835       gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(child->data));
    836     }
    837     g_list_free(children);
    838 
    839     // Clear out the bit that says the menu is built.
    840     // We'll rebuild it next time it is shown.
    841     g_object_steal_data(G_OBJECT(submenu), "submenu-built");
    842 
    843     // Notify the submenu model that the menu has been hidden. This may cause
    844     // it to delete descendant submenu models, which is why we cleared those
    845     // pointers out above.
    846     GtkWidget* menu_item = static_cast<GtkWidget*>(
    847         g_object_get_data(G_OBJECT(submenu), "menu-item"));
    848     // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110.
    849     CHECK(menu_item);
    850     ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>(
    851         g_object_get_data(G_OBJECT(menu_item), "submenu-model"));
    852     if (submenu_model)
    853       submenu_model->MenuClosed();
    854   }
    855 
    856   // Remove the reference we grabbed in OnSubMenuHidden() above.
    857   g_object_unref(G_OBJECT(submenu));
    858 }
    859 
    860 // static
    861 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) {
    862   ui::ButtonMenuItemModel* model =
    863       reinterpret_cast<ui::ButtonMenuItemModel*>(
    864           g_object_get_data(G_OBJECT(button), "button-model"));
    865   int index = GPOINTER_TO_INT(g_object_get_data(
    866       G_OBJECT(button), "button-model-id"));
    867 
    868   if (model->IsItemDynamicAt(index)) {
    869     std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
    870         UTF16ToUTF8(model->GetLabelAt(index)));
    871     gtk_button_set_label(GTK_BUTTON(button), label.c_str());
    872   }
    873 
    874   gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index));
    875 }
    876 
    877 // static
    878 void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) {
    879   if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) {
    880     // We need to explicitly handle this case because otherwise we'll ask the
    881     // menu delegate about something with an invalid id.
    882     return;
    883   }
    884 
    885   int id;
    886   if (!GetMenuItemID(widget, &id))
    887     return;
    888 
    889   ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget));
    890   if (!model) {
    891     // If we're not providing the sub menu, then there's no model.  For
    892     // example, the IME submenu doesn't have a model.
    893     return;
    894   }
    895 
    896   if (GTK_IS_CHECK_MENU_ITEM(widget)) {
    897     GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget);
    898 
    899     // gtk_check_menu_item_set_active() will send the activate signal. Touching
    900     // the underlying "active" property will also call the "activate" handler
    901     // for this menu item. So we prevent the "activate" handler from
    902     // being called while we set the checkbox.
    903     // Why not use one of the glib signal-blocking functions?  Because when we
    904     // toggle a radio button, it will deactivate one of the other radio buttons,
    905     // which we don't have a pointer to.
    906     // Wny not make this a member variable?  Because "menu" is a pointer to the
    907     // root of the MenuGtk and we want to disable *all* MenuGtks, including
    908     // submenus.
    909     block_activation_ = true;
    910     gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id));
    911     block_activation_ = false;
    912   }
    913 
    914   if (GTK_IS_CUSTOM_MENU_ITEM(widget)) {
    915     // Iterate across all the buttons to update their visible properties.
    916     gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget),
    917                                         SetButtonItemInfo,
    918                                         userdata);
    919   }
    920 
    921   if (GTK_IS_MENU_ITEM(widget)) {
    922     gtk_widget_set_sensitive(widget, model->IsEnabledAt(id));
    923 
    924     if (model->IsVisibleAt(id)) {
    925       // Update the menu item label if it is dynamic.
    926       if (model->IsItemDynamicAt(id)) {
    927         std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
    928             UTF16ToUTF8(model->GetLabelAt(id)));
    929 
    930         gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str());
    931         if (GTK_IS_IMAGE_MENU_ITEM(widget)) {
    932           gfx::Image icon;
    933           if (model->GetIconAt(id, &icon)) {
    934             gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget),
    935                                           gtk_image_new_from_pixbuf(
    936                                               icon.ToGdkPixbuf()));
    937           } else {
    938             gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL);
    939           }
    940         }
    941       }
    942 
    943       gtk_widget_show(widget);
    944     } else {
    945       gtk_widget_hide(widget);
    946     }
    947 
    948     GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget));
    949     if (submenu) {
    950       gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo,
    951                             userdata);
    952     }
    953   }
    954 }
    955