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