1 // Copyright 2013 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/libgtk2ui/menu_util.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
10 #include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h"
11 #include "ui/base/accelerators/accelerator.h"
12 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
13 #include "ui/base/models/menu_model.h"
17 GtkWidget
* BuildMenuItemWithImage(const std::string
& label
, GtkWidget
* image
) {
18 GtkWidget
* menu_item
= gtk_image_menu_item_new_with_mnemonic(label
.c_str());
19 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item
), image
);
23 GtkWidget
* BuildMenuItemWithImage(const std::string
& label
,
24 const gfx::Image
& icon
) {
25 GdkPixbuf
* pixbuf
= GdkPixbufFromSkBitmap(*icon
.ToSkBitmap());
27 GtkWidget
* menu_item
=
28 BuildMenuItemWithImage(label
, gtk_image_new_from_pixbuf(pixbuf
));
29 g_object_unref(pixbuf
);
33 GtkWidget
* BuildMenuItemWithLabel(const std::string
& label
) {
34 return gtk_menu_item_new_with_mnemonic(label
.c_str());
37 ui::MenuModel
* ModelForMenuItem(GtkMenuItem
* menu_item
) {
38 return reinterpret_cast<ui::MenuModel
*>(
39 g_object_get_data(G_OBJECT(menu_item
), "model"));
42 GtkWidget
* AppendMenuItemToMenu(int index
,
46 bool connect_to_activate
,
47 GCallback item_activated_cb
,
49 // Set the ID of a menu item.
50 // Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
51 g_object_set_data(G_OBJECT(menu_item
), "menu-id", GINT_TO_POINTER(index
+ 1));
53 // Native menu items do their own thing, so only selectively listen for the
55 if (connect_to_activate
) {
56 g_signal_connect(menu_item
, "activate", item_activated_cb
, this_ptr
);
59 // AppendMenuItemToMenu is used both internally when we control menu creation
60 // from a model (where the model can choose to hide certain menu items), and
61 // with immediate commands which don't provide the option.
63 if (model
->IsVisibleAt(index
))
64 gtk_widget_show(menu_item
);
66 gtk_widget_show(menu_item
);
68 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_item
);
72 bool GetMenuItemID(GtkWidget
* menu_item
, int* menu_id
) {
73 gpointer id_ptr
= g_object_get_data(G_OBJECT(menu_item
), "menu-id");
75 *menu_id
= GPOINTER_TO_INT(id_ptr
) - 1;
82 void ExecuteCommand(ui::MenuModel
* model
, int id
) {
83 GdkEvent
* event
= gtk_get_current_event();
86 if (event
&& event
->type
== GDK_BUTTON_RELEASE
)
87 event_flags
= EventFlagsFromGdkState(event
->button
.state
);
88 model
->ActivatedAt(id
, event_flags
);
91 gdk_event_free(event
);
94 void BuildSubmenuFromModel(ui::MenuModel
* model
,
96 GCallback item_activated_cb
,
97 bool* block_activation
,
99 std::map
<int, GtkWidget
*> radio_groups
;
100 GtkWidget
* menu_item
= NULL
;
101 for (int i
= 0; i
< model
->GetItemCount(); ++i
) {
103 std::string label
= ui::ConvertAcceleratorsFromWindowsStyle(
104 base::UTF16ToUTF8(model
->GetLabelAt(i
)));
106 bool connect_to_activate
= true;
108 switch (model
->GetTypeAt(i
)) {
109 case ui::MenuModel::TYPE_SEPARATOR
:
110 menu_item
= gtk_separator_menu_item_new();
113 case ui::MenuModel::TYPE_CHECK
:
114 menu_item
= gtk_check_menu_item_new_with_mnemonic(label
.c_str());
117 case ui::MenuModel::TYPE_RADIO
: {
118 std::map
<int, GtkWidget
*>::iterator iter
=
119 radio_groups
.find(model
->GetGroupIdAt(i
));
121 if (iter
== radio_groups
.end()) {
123 gtk_radio_menu_item_new_with_mnemonic(NULL
, label
.c_str());
124 radio_groups
[model
->GetGroupIdAt(i
)] = menu_item
;
126 menu_item
= gtk_radio_menu_item_new_with_mnemonic_from_widget(
127 GTK_RADIO_MENU_ITEM(iter
->second
), label
.c_str());
131 case ui::MenuModel::TYPE_BUTTON_ITEM
: {
135 case ui::MenuModel::TYPE_SUBMENU
:
136 case ui::MenuModel::TYPE_COMMAND
: {
137 if (model
->GetIconAt(i
, &icon
))
138 menu_item
= BuildMenuItemWithImage(label
, icon
);
140 menu_item
= BuildMenuItemWithLabel(label
);
141 if (GTK_IS_IMAGE_MENU_ITEM(menu_item
)) {
142 SetAlwaysShowImage(menu_item
);
151 if (model
->GetTypeAt(i
) == ui::MenuModel::TYPE_SUBMENU
) {
152 GtkWidget
* submenu
= gtk_menu_new();
153 ui::MenuModel
* submenu_model
= model
->GetSubmenuModelAt(i
);
154 BuildSubmenuFromModel(submenu_model
,
159 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item
), submenu
);
161 // Update all the menu item info in the newly-generated menu.
162 gtk_container_foreach(
163 GTK_CONTAINER(submenu
), SetMenuItemInfo
, block_activation
);
164 submenu_model
->MenuWillShow();
165 connect_to_activate
= false;
168 ui::Accelerator accelerator
;
169 if (model
->GetAcceleratorAt(i
, &accelerator
)) {
170 gtk_widget_add_accelerator(menu_item
,
173 GetGdkKeyCodeForAccelerator(accelerator
),
174 GetGdkModifierForAccelerator(accelerator
),
178 g_object_set_data(G_OBJECT(menu_item
), "model", model
);
179 AppendMenuItemToMenu(i
,
191 void SetMenuItemInfo(GtkWidget
* widget
, void* block_activation_ptr
) {
192 if (GTK_IS_SEPARATOR_MENU_ITEM(widget
)) {
193 // We need to explicitly handle this case because otherwise we'll ask the
194 // menu delegate about something with an invalid id.
199 if (!GetMenuItemID(widget
, &id
))
202 ui::MenuModel
* model
= ModelForMenuItem(GTK_MENU_ITEM(widget
));
204 // If we're not providing the sub menu, then there's no model. For
205 // example, the IME submenu doesn't have a model.
208 bool* block_activation
= static_cast<bool*>(block_activation_ptr
);
210 if (GTK_IS_CHECK_MENU_ITEM(widget
)) {
211 GtkCheckMenuItem
* item
= GTK_CHECK_MENU_ITEM(widget
);
213 // gtk_check_menu_item_set_active() will send the activate signal. Touching
214 // the underlying "active" property will also call the "activate" handler
215 // for this menu item. So we prevent the "activate" handler from
216 // being called while we set the checkbox.
217 // Why not use one of the glib signal-blocking functions? Because when we
218 // toggle a radio button, it will deactivate one of the other radio buttons,
219 // which we don't have a pointer to.
220 *block_activation
= true;
221 gtk_check_menu_item_set_active(item
, model
->IsItemCheckedAt(id
));
222 *block_activation
= false;
225 if (GTK_IS_MENU_ITEM(widget
)) {
226 gtk_widget_set_sensitive(widget
, model
->IsEnabledAt(id
));
228 if (model
->IsVisibleAt(id
)) {
229 // Update the menu item label if it is dynamic.
230 if (model
->IsItemDynamicAt(id
)) {
231 std::string label
= ui::ConvertAcceleratorsFromWindowsStyle(
232 base::UTF16ToUTF8(model
->GetLabelAt(id
)));
234 gtk_menu_item_set_label(GTK_MENU_ITEM(widget
), label
.c_str());
235 if (GTK_IS_IMAGE_MENU_ITEM(widget
)) {
237 if (model
->GetIconAt(id
, &icon
)) {
238 gtk_image_menu_item_set_image(
239 GTK_IMAGE_MENU_ITEM(widget
),
240 gtk_image_new_from_pixbuf(
241 GdkPixbufFromSkBitmap(*icon
.ToSkBitmap())));
243 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget
), NULL
);
248 gtk_widget_show(widget
);
250 gtk_widget_hide(widget
);
253 GtkWidget
* submenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget
));
255 gtk_container_foreach(
256 GTK_CONTAINER(submenu
), &SetMenuItemInfo
, block_activation_ptr
);
261 } // namespace libgtk2ui