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.
5 #include "chrome/browser/ui/gtk/menu_gtk.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"
29 bool MenuGtk::block_activation_
= false;
33 // Sets the ID of a menu item.
34 void SetMenuItemID(GtkWidget
* menu_item
, int menu_id
) {
35 DCHECK_GE(menu_id
, 0);
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));
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");
47 *menu_id
= GPOINTER_TO_INT(id_ptr
) - 1;
54 ui::MenuModel
* ModelForMenuItem(GtkMenuItem
* menu_item
) {
55 return reinterpret_cast<ui::MenuModel
*>(
56 g_object_get_data(G_OBJECT(menu_item
), "model"));
59 void SetUpButtonShowHandler(GtkWidget
* button
,
60 ui::ButtonMenuItemModel
* model
,
62 g_object_set_data(G_OBJECT(button
), "button-model",
64 g_object_set_data(G_OBJECT(button
), "button-model-id",
65 GINT_TO_POINTER(index
));
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"));
74 GtkIconSet
* icon_set
= delegate
->GetIconSetForId(icon_idr
);
77 button
, gtk_image_new_from_icon_set(icon_set
,
82 void SetupImageIcon(GtkWidget
* button
,
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",
91 g_signal_connect(menu
, "show", G_CALLBACK(OnSubmenuShowButtonImage
), button
);
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
) {
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
;
113 GtkAllocation allocation
;
114 gtk_widget_get_allocation(widget
, &allocation
);
115 alternate_y
-= allocation
.height
;
117 if (menu_bottom
>= screen_bottom
&& alternate_y
>= screen_top
)
124 bool MenuGtk::Delegate::AlwaysShowIconForCmd(int command_id
) const {
128 GtkIconSet
* MenuGtk::Delegate::GetIconSetForId(int idr
) { return NULL
; }
130 GtkWidget
* MenuGtk::Delegate::GetDefaultImageForCommandId(int command_id
) {
132 switch (command_id
) {
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
;
142 stock
= GTK_STOCK_CLOSE
;
145 case IDC_CONTENT_CONTEXT_SAVEIMAGEAS
:
146 case IDC_CONTENT_CONTEXT_SAVEAVAS
:
147 case IDC_CONTENT_CONTEXT_SAVELINKAS
:
148 stock
= GTK_STOCK_SAVE_AS
;
152 stock
= GTK_STOCK_SAVE
;
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
;
165 case IDC_CONTENT_CONTEXT_CUT
:
166 stock
= GTK_STOCK_CUT
;
170 case IDC_CONTENT_CONTEXT_PASTE
:
171 stock
= GTK_STOCK_PASTE
;
174 case IDC_CONTENT_CONTEXT_DELETE
:
175 case IDC_BOOKMARK_BAR_REMOVE
:
176 stock
= GTK_STOCK_DELETE
;
179 case IDC_CONTENT_CONTEXT_UNDO
:
180 stock
= GTK_STOCK_UNDO
;
183 case IDC_CONTENT_CONTEXT_REDO
:
184 stock
= GTK_STOCK_REDO
;
189 case IDC_CONTENT_CONTEXT_SEARCHWEBFOR
:
190 stock
= GTK_STOCK_FIND
;
193 case IDC_CONTENT_CONTEXT_SELECTALL
:
194 stock
= GTK_STOCK_SELECT_ALL
;
197 case IDC_CLEAR_BROWSING_DATA
:
198 stock
= GTK_STOCK_CLEAR
;
202 stock
= GTK_STOCK_GO_BACK
;
206 stock
= GTK_STOCK_REFRESH
;
210 stock
= GTK_STOCK_GO_FORWARD
;
214 stock
= GTK_STOCK_PRINT
;
217 case IDC_CONTENT_CONTEXT_VIEWPAGEINFO
:
218 stock
= GTK_STOCK_INFO
;
221 case IDC_SPELLCHECK_MENU
:
222 stock
= GTK_STOCK_SPELL_CHECK
;
225 case IDC_RESTORE_TAB
:
226 stock
= GTK_STOCK_UNDELETE
;
230 stock
= GTK_STOCK_HOME
;
234 stock
= GTK_STOCK_STOP
;
238 stock
= GTK_STOCK_ABOUT
;
242 stock
= GTK_STOCK_QUIT
;
245 case IDC_HELP_PAGE_VIA_MENU
:
246 stock
= GTK_STOCK_HELP
;
250 stock
= GTK_STOCK_PREFERENCES
;
253 case IDC_CONTENT_CONTEXT_GOTOURL
:
254 stock
= GTK_STOCK_JUMP_TO
;
257 case IDC_DEV_TOOLS_INSPECT
:
258 case IDC_CONTENT_CONTEXT_INSPECTELEMENT
:
259 stock
= GTK_STOCK_PROPERTIES
;
262 case IDC_BOOKMARK_BAR_ADD_NEW_BOOKMARK
:
263 stock
= GTK_STOCK_ADD
;
266 case IDC_BOOKMARK_BAR_RENAME_FOLDER
:
267 case IDC_BOOKMARK_BAR_EDIT
:
268 stock
= GTK_STOCK_EDIT
;
271 case IDC_BOOKMARK_BAR_NEW_FOLDER
:
272 stock
= GTK_STOCK_DIRECTORY
;
275 case IDC_BOOKMARK_BAR_OPEN_ALL
:
276 stock
= GTK_STOCK_OPEN
;
283 return stock
? gtk_image_new_from_stock(stock
, GTK_ICON_SIZE_MENU
) : NULL
;
286 GtkWidget
* MenuGtk::Delegate::GetImageForCommandId(int command_id
) const {
287 return GetDefaultImageForCommandId(command_id
);
290 MenuGtk::MenuGtk(MenuGtk::Delegate
* delegate
,
291 ui::MenuModel
* model
)
292 : delegate_(delegate
),
294 dummy_accel_group_(gtk_accel_group_new()),
295 menu_(gtk_custom_menu_new()),
296 weak_factory_(this) {
298 g_object_ref_sink(menu_
);
299 ConnectSignalHandlers();
300 BuildMenuFromModel();
303 MenuGtk::~MenuGtk() {
306 gtk_widget_destroy(menu_
);
307 g_object_unref(menu_
);
309 g_object_unref(dummy_accel_group_
);
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);
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
);
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
);
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
);
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
);
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
);
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
);
364 return AppendMenuItemToMenu(command_id
, NULL
, menu_item
, menu_
, true);
367 GtkWidget
* MenuGtk::InsertMenuItem(int command_id
, GtkWidget
* menu_item
,
369 if (delegate_
&& delegate_
->AlwaysShowIconForCmd(command_id
) &&
370 GTK_IS_IMAGE_MENU_ITEM(menu_item
))
371 gtk_util::SetAlwaysShowImage(menu_item
);
373 return InsertMenuItemToMenu(command_id
, NULL
, menu_item
, menu_
, position
,
377 GtkWidget
* MenuGtk::AppendMenuItemToMenu(int index
,
378 ui::MenuModel
* model
,
379 GtkWidget
* menu_item
,
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
);
387 GtkWidget
* MenuGtk::InsertMenuItemToMenu(int index
,
388 ui::MenuModel
* model
,
389 GtkWidget
* menu_item
,
392 bool connect_to_activate
) {
393 SetMenuItemID(menu_item
, index
);
395 // Native menu items do their own thing, so only selectively listen for the
397 if (connect_to_activate
) {
398 g_signal_connect(menu_item
, "activate",
399 G_CALLBACK(OnMenuItemActivatedThunk
), this);
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.
406 if (model
->IsVisibleAt(index
))
407 gtk_widget_show(menu_item
);
409 gtk_widget_show(menu_item
);
411 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menu_item
, position
);
415 void MenuGtk::PopupForWidget(GtkWidget
* widget
, int button
,
416 guint32 event_time
) {
417 gtk_menu_popup(GTK_MENU(menu_
), NULL
, NULL
,
418 WidgetMenuPositionFunc
,
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
,
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
);
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
);
442 void MenuGtk::Cancel() {
443 gtk_menu_popdown(GTK_MENU(menu_
));
446 void MenuGtk::UpdateMenu() {
447 gtk_container_foreach(GTK_CONTAINER(menu_
), SetMenuItemInfo
, this);
450 GtkWidget
* MenuGtk::BuildMenuItemWithImage(const std::string
& label
,
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
);
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()));
465 GtkWidget
* MenuGtk::BuildMenuItemWithLabel(const std::string
& label
,
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());
474 void MenuGtk::BuildMenuFromModel() {
475 BuildSubmenuFromModel(model_
, menu_
);
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
) {
483 std::string label
= ui::ConvertAcceleratorsFromWindowsStyle(
484 base::UTF16ToUTF8(model
->GetLabelAt(i
)));
485 bool connect_to_activate
= true;
487 switch (model
->GetTypeAt(i
)) {
488 case ui::MenuModel::TYPE_SEPARATOR
:
489 menu_item
= gtk_separator_menu_item_new();
492 case ui::MenuModel::TYPE_CHECK
:
493 menu_item
= gtk_check_menu_item_new_with_mnemonic(label
.c_str());
496 case ui::MenuModel::TYPE_RADIO
: {
497 std::map
<int, GtkWidget
*>::iterator iter
=
498 radio_groups
.find(model
->GetGroupIdAt(i
));
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
;
505 menu_item
= gtk_radio_menu_item_new_with_mnemonic_from_widget(
506 GTK_RADIO_MENU_ITEM(iter
->second
), label
.c_str());
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;
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
);
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
);
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;
547 ui::Accelerator accelerator
;
548 if (model
->GetAcceleratorAt(i
, &accelerator
)) {
549 gtk_widget_add_accelerator(menu_item
,
552 ui::GetGdkKeyCodeForAccelerator(accelerator
),
553 ui::GetGdkModifierForAccelerator(accelerator
),
557 g_object_set_data(G_OBJECT(menu_item
), "model", model
);
558 AppendMenuItemToMenu(i
, model
, menu_item
, menu
, connect_to_activate
);
564 GtkWidget
* MenuGtk::BuildButtonMenuItem(ui::ButtonMenuItemModel
* model
,
566 GtkWidget
* menu_item
= gtk_custom_menu_item_new(
567 ui::RemoveWindowsStyleAccelerators(
568 base::UTF16ToUTF8(model
->label())).c_str());
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);
577 GtkSizeGroup
* group
= NULL
;
578 for (int i
= 0; i
< model
->GetItemCount(); ++i
) {
579 GtkWidget
* button
= NULL
;
581 switch (model
->GetTypeAt(i
)) {
582 case ui::ButtonMenuItemModel::TYPE_SPACE
: {
583 gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item
));
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
));
592 if (model
->GetIconAt(i
, &icon_idr
)) {
593 SetupImageIcon(button
, menu
, icon_idr
, delegate_
);
595 gtk_button_set_label(
597 ui::RemoveWindowsStyleAccelerators(
598 base::UTF16ToUTF8(model
->GetLabelAt(i
))).c_str());
601 SetUpButtonShowHandler(button
, model
, i
);
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(
610 ui::RemoveWindowsStyleAccelerators(
611 base::UTF16ToUTF8(model
->GetLabelAt(i
))).c_str());
612 SetUpButtonShowHandler(button
, model
, i
);
617 if (button
&& model
->PartOfGroup(i
)) {
619 group
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
621 gtk_size_group_add_widget(group
, button
);
626 g_object_unref(group
);
631 void MenuGtk::OnMenuItemActivated(GtkWidget
* menu_item
) {
632 if (block_activation_
)
635 ui::MenuModel
* model
= ModelForMenuItem(GTK_MENU_ITEM(menu_item
));
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
)));
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
))) {
653 if (!GetMenuItemID(menu_item
, &id
))
656 // The menu item can still be activated by hotkeys even if it is disabled.
657 if (model
->IsEnabledAt(id
))
658 ExecuteCommand(model
, id
);
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
)) {
667 delegate_
->CommandWillBeExecuted();
669 model
->ActivatedCommand(command_id
);
673 gboolean
MenuGtk::OnMenuTryButtonPressed(GtkWidget
* menu_item
,
675 gboolean pressed
= FALSE
;
676 ui::ButtonMenuItemModel
* model
=
677 reinterpret_cast<ui::ButtonMenuItemModel
*>(
678 g_object_get_data(G_OBJECT(menu_item
), "button-model"));
680 model
->IsCommandIdEnabled(command_id
) &&
681 !model
->DoesCommandIdDismissMenu(command_id
)) {
683 delegate_
->CommandWillBeExecuted();
685 model
->ActivatedCommand(command_id
);
693 void MenuGtk::WidgetMenuPositionFunc(GtkMenu
* menu
,
698 GtkWidget
* widget
= GTK_WIDGET(void_widget
);
699 GtkRequisition menu_req
;
701 gtk_widget_size_request(GTK_WIDGET(menu
), &menu_req
);
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
);
707 GdkRectangle screen_rect
;
708 gdk_screen_get_monitor_geometry(screen
, monitor
,
711 GtkAllocation allocation
;
712 gtk_widget_get_allocation(widget
, &allocation
);
714 if (!gtk_widget_get_has_window(widget
)) {
718 *y
+= allocation
.height
;
721 !!g_object_get_data(G_OBJECT(widget
), "left-align-popup");
722 if (base::i18n::IsRTL())
723 start_align
= !start_align
;
726 *x
+= allocation
.width
- menu_req
.width
;
728 *y
= CalculateMenuYPosition(&screen_rect
, &menu_req
, widget
, *y
);
734 void MenuGtk::PointMenuPositionFunc(GtkMenu
* menu
,
741 gfx::Point
* point
= reinterpret_cast<gfx::Point
*>(userdata
);
745 GtkRequisition menu_req
;
746 gtk_widget_size_request(GTK_WIDGET(menu
), &menu_req
);
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
);
751 GdkRectangle screen_rect
;
752 gdk_screen_get_monitor_geometry(screen
, monitor
, &screen_rect
);
754 *y
= CalculateMenuYPosition(&screen_rect
, &menu_req
, NULL
, *y
);
757 void MenuGtk::ExecuteCommand(ui::MenuModel
* model
, int id
) {
759 delegate_
->CommandWillBeExecuted();
761 GdkEvent
* event
= gtk_get_current_event();
764 if (event
&& event
->type
== GDK_BUTTON_RELEASE
)
765 event_flags
= event_utils::EventFlagsFromGdkState(event
->button
.state
);
766 model
->ActivatedAt(id
, event_flags
);
769 gdk_event_free(event
);
772 void MenuGtk::OnMenuShow(GtkWidget
* widget
) {
773 model_
->MenuWillShow();
774 base::MessageLoop::current()->PostTask(
775 FROM_HERE
, base::Bind(&MenuGtk::UpdateMenu
, weak_factory_
.GetWeakPtr()));
778 void MenuGtk::OnMenuHidden(GtkWidget
* widget
) {
780 delegate_
->StoppedShowing();
781 model_
->MenuClosed();
784 gboolean
MenuGtk::OnMenuFocusOut(GtkWidget
* widget
, GdkEventFocus
* event
) {
785 gtk_widget_hide(menu_
);
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.
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.)
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"))
809 g_object_set_data(G_OBJECT(submenu
), "submenu-built", GINT_TO_POINTER(1));
811 submenu_model
->MenuWillShow();
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
);
817 // Update all the menu item info in the newly-generated menu.
818 gtk_container_foreach(GTK_CONTAINER(submenu
), SetMenuItemInfo
, this);
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(
832 base::Bind(&MenuGtk::OnSubMenuHiddenCallback
, submenu
),
833 base::TimeDelta::FromSeconds(2));
838 // Remove all descendant submenu-model data pointers.
839 void RemoveSubMenuModels(GtkWidget
* menu_item
, void* unused
) {
840 if (!GTK_IS_MENU_ITEM(menu_item
))
842 g_object_steal_data(G_OBJECT(menu_item
), "submenu-model");
843 GtkWidget
* submenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item
));
845 gtk_container_foreach(GTK_CONTAINER(submenu
), RemoveSubMenuModels
, NULL
);
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
));
866 g_list_free(children
);
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");
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.
879 ui::MenuModel
* submenu_model
= static_cast<ui::MenuModel
*>(
880 g_object_get_data(G_OBJECT(menu_item
), "submenu-model"));
882 submenu_model
->MenuClosed();
885 // Remove the reference we grabbed in OnSubMenuHidden() above.
886 g_object_unref(G_OBJECT(submenu
));
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"));
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());
903 gtk_widget_set_sensitive(GTK_WIDGET(button
), model
->IsEnabledAt(index
));
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.
915 if (!GetMenuItemID(widget
, &id
))
918 ui::MenuModel
* model
= ModelForMenuItem(GTK_MENU_ITEM(widget
));
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.
925 if (GTK_IS_CHECK_MENU_ITEM(widget
)) {
926 GtkCheckMenuItem
* item
= GTK_CHECK_MENU_ITEM(widget
);
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
938 block_activation_
= true;
939 gtk_check_menu_item_set_active(item
, model
->IsItemCheckedAt(id
));
940 block_activation_
= false;
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
),
950 if (GTK_IS_MENU_ITEM(widget
)) {
951 gtk_widget_set_sensitive(widget
, model
->IsEnabledAt(id
));
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
)));
959 gtk_menu_item_set_label(GTK_MENU_ITEM(widget
), label
.c_str());
960 if (GTK_IS_IMAGE_MENU_ITEM(widget
)) {
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()));
967 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget
), NULL
);
972 gtk_widget_show(widget
);
974 gtk_widget_hide(widget
);
977 GtkWidget
* submenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget
));
979 gtk_container_foreach(GTK_CONTAINER(submenu
), &SetMenuItemInfo
,