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