• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
SetMenuItemID(GtkWidget * menu_item,int menu_id)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.
GetMenuItemID(GtkWidget * menu_item,int * menu_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 
ModelForMenuItem(GtkMenuItem * menu_item)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 
SetupButtonShowHandler(GtkWidget * button,ui::ButtonMenuItemModel * model,int index)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 
OnSubmenuShowButtonImage(GtkWidget * widget,GtkButton * button)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 
SetupImageIcon(GtkWidget * button,GtkWidget * menu,int icon_idr,MenuGtk::Delegate * menu_gtk_delegate)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.
CalculateMenuYPosition(const GdkRectangle * screen_rect,const GtkRequisition * menu_req,const GtkWidget * widget,const int y)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 
GetDefaultImageForCommandId(int command_id)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 
GetImageForCommandId(int command_id) const273 GtkWidget* MenuGtk::Delegate::GetImageForCommandId(int command_id) const {
274   return GetDefaultImageForCommandId(command_id);
275 }
276 
MenuGtk(MenuGtk::Delegate * delegate,ui::MenuModel * model)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 
~MenuGtk()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 
ConnectSignalHandlers()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 
AppendMenuItemWithLabel(int command_id,const std::string & label)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 
AppendMenuItemWithIcon(int command_id,const std::string & label,const SkBitmap & icon)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 
AppendCheckMenuItemWithLabel(int command_id,const std::string & label)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 
AppendSeparator()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 
AppendMenuItem(int command_id,GtkWidget * menu_item)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 
AppendMenuItemToMenu(int index,ui::MenuModel * model,GtkWidget * menu_item,GtkWidget * menu,bool connect_to_activate)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 
PopupForWidget(GtkWidget * widget,int button,guint32 event_time)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 
PopupAsContext(const gfx::Point & point,guint32 event_time)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 
PopupAsContextForStatusIcon(guint32 event_time,guint32 button,GtkStatusIcon * icon)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 
PopupAsFromKeyEvent(GtkWidget * widget)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 
Cancel()399 void MenuGtk::Cancel() {
400   gtk_menu_popdown(GTK_MENU(menu_));
401 }
402 
UpdateMenu()403 void MenuGtk::UpdateMenu() {
404   gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this);
405 }
406 
BuildMenuItemWithImage(const std::string & label,GtkWidget * image)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 
BuildMenuItemWithImage(const std::string & label,const SkBitmap & icon)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 
BuildMenuItemWithLabel(const std::string & label,int command_id)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 
BuildMenuFromModel()433 void MenuGtk::BuildMenuFromModel() {
434   BuildSubmenuFromModel(model_, menu_);
435 }
436 
BuildSubmenuFromModel(ui::MenuModel * model,GtkWidget * menu)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 
BuildButtonMenuItem(ui::ButtonMenuItemModel * model,GtkWidget * menu)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 
OnMenuItemActivated(GtkWidget * menuitem)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 
OnMenuButtonPressed(GtkWidget * menu_item,int command_id)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 
OnMenuTryButtonPressed(GtkWidget * menu_item,int command_id)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
WidgetMenuPositionFunc(GtkMenu * menu,int * x,int * y,gboolean * push_in,void * void_widget)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
PointMenuPositionFunc(GtkMenu * menu,int * x,int * y,gboolean * push_in,gpointer userdata)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 
ExecuteCommand(ui::MenuModel * model,int id)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 
OnMenuShow(GtkWidget * widget)720 void MenuGtk::OnMenuShow(GtkWidget* widget) {
721   model_->MenuWillShow();
722   MessageLoop::current()->PostTask(FROM_HERE,
723       factory_.NewRunnableMethod(&MenuGtk::UpdateMenu));
724 }
725 
OnMenuHidden(GtkWidget * widget)726 void MenuGtk::OnMenuHidden(GtkWidget* widget) {
727   if (delegate_)
728     delegate_->StoppedShowing();
729   model_->MenuClosed();
730 }
731 
732 // static
SetButtonItemInfo(GtkWidget * button,gpointer userdata)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
SetMenuItemInfo(GtkWidget * widget,gpointer userdata)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