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"
15 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
19 GtkWidget
* BuildMenuItemWithImage(const std::string
& label
, GtkWidget
* image
) {
20 GtkWidget
* menu_item
= gtk_image_menu_item_new_with_mnemonic(label
.c_str());
21 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item
), image
);
25 GtkWidget
* BuildMenuItemWithImage(const std::string
& label
,
26 const gfx::Image
& icon
) {
27 GdkPixbuf
* pixbuf
= GdkPixbufFromSkBitmap(*icon
.ToSkBitmap());
29 GtkWidget
* menu_item
=
30 BuildMenuItemWithImage(label
, gtk_image_new_from_pixbuf(pixbuf
));
31 g_object_unref(pixbuf
);
35 GtkWidget
* BuildMenuItemWithLabel(const std::string
& label
) {
36 return gtk_menu_item_new_with_mnemonic(label
.c_str());
39 ui::MenuModel
* ModelForMenuItem(GtkMenuItem
* menu_item
) {
40 return reinterpret_cast<ui::MenuModel
*>(
41 g_object_get_data(G_OBJECT(menu_item
), "model"));
44 GtkWidget
* AppendMenuItemToMenu(int index
,
48 bool connect_to_activate
,
49 GCallback item_activated_cb
,
51 // Set the ID of a menu item.
52 // Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
53 g_object_set_data(G_OBJECT(menu_item
), "menu-id", GINT_TO_POINTER(index
+ 1));
55 // Native menu items do their own thing, so only selectively listen for the
57 if (connect_to_activate
) {
58 g_signal_connect(menu_item
, "activate", item_activated_cb
, this_ptr
);
61 // AppendMenuItemToMenu is used both internally when we control menu creation
62 // from a model (where the model can choose to hide certain menu items), and
63 // with immediate commands which don't provide the option.
65 if (model
->IsVisibleAt(index
))
66 gtk_widget_show(menu_item
);
68 gtk_widget_show(menu_item
);
70 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_item
);
74 bool GetMenuItemID(GtkWidget
* menu_item
, int* menu_id
) {
75 gpointer id_ptr
= g_object_get_data(G_OBJECT(menu_item
), "menu-id");
77 *menu_id
= GPOINTER_TO_INT(id_ptr
) - 1;
84 void ExecuteCommand(ui::MenuModel
* model
, int id
) {
85 GdkEvent
* event
= gtk_get_current_event();
88 if (event
&& event
->type
== GDK_BUTTON_RELEASE
)
89 event_flags
= EventFlagsFromGdkState(event
->button
.state
);
90 model
->ActivatedAt(id
, event_flags
);
93 gdk_event_free(event
);
96 void BuildSubmenuFromModel(ui::MenuModel
* model
,
98 GCallback item_activated_cb
,
99 bool* block_activation
,
101 std::map
<int, GtkWidget
*> radio_groups
;
102 GtkWidget
* menu_item
= NULL
;
103 for (int i
= 0; i
< model
->GetItemCount(); ++i
) {
105 std::string label
= ui::ConvertAcceleratorsFromWindowsStyle(
106 base::UTF16ToUTF8(model
->GetLabelAt(i
)));
108 bool connect_to_activate
= true;
110 switch (model
->GetTypeAt(i
)) {
111 case ui::MenuModel::TYPE_SEPARATOR
:
112 menu_item
= gtk_separator_menu_item_new();
115 case ui::MenuModel::TYPE_CHECK
:
116 menu_item
= gtk_check_menu_item_new_with_mnemonic(label
.c_str());
119 case ui::MenuModel::TYPE_RADIO
: {
120 std::map
<int, GtkWidget
*>::iterator iter
=
121 radio_groups
.find(model
->GetGroupIdAt(i
));
123 if (iter
== radio_groups
.end()) {
125 gtk_radio_menu_item_new_with_mnemonic(NULL
, label
.c_str());
126 radio_groups
[model
->GetGroupIdAt(i
)] = menu_item
;
128 menu_item
= gtk_radio_menu_item_new_with_mnemonic_from_widget(
129 GTK_RADIO_MENU_ITEM(iter
->second
), label
.c_str());
133 case ui::MenuModel::TYPE_BUTTON_ITEM
: {
137 case ui::MenuModel::TYPE_SUBMENU
:
138 case ui::MenuModel::TYPE_COMMAND
: {
139 if (model
->GetIconAt(i
, &icon
))
140 menu_item
= BuildMenuItemWithImage(label
, icon
);
142 menu_item
= BuildMenuItemWithLabel(label
);
143 if (GTK_IS_IMAGE_MENU_ITEM(menu_item
)) {
144 gtk_image_menu_item_set_always_show_image(
145 GTK_IMAGE_MENU_ITEM(menu_item
), TRUE
);
154 if (model
->GetTypeAt(i
) == ui::MenuModel::TYPE_SUBMENU
) {
155 GtkWidget
* submenu
= gtk_menu_new();
156 ui::MenuModel
* submenu_model
= model
->GetSubmenuModelAt(i
);
157 BuildSubmenuFromModel(submenu_model
,
162 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item
), submenu
);
164 // Update all the menu item info in the newly-generated menu.
165 gtk_container_foreach(
166 GTK_CONTAINER(submenu
), SetMenuItemInfo
, block_activation
);
167 submenu_model
->MenuWillShow();
168 connect_to_activate
= false;
171 ui::Accelerator accelerator
;
172 if (model
->GetAcceleratorAt(i
, &accelerator
)) {
173 gtk_widget_add_accelerator(menu_item
,
176 GetGdkKeyCodeForAccelerator(accelerator
),
177 GetGdkModifierForAccelerator(accelerator
),
181 g_object_set_data(G_OBJECT(menu_item
), "model", model
);
182 AppendMenuItemToMenu(i
,
194 void SetMenuItemInfo(GtkWidget
* widget
, void* block_activation_ptr
) {
195 if (GTK_IS_SEPARATOR_MENU_ITEM(widget
)) {
196 // We need to explicitly handle this case because otherwise we'll ask the
197 // menu delegate about something with an invalid id.
202 if (!GetMenuItemID(widget
, &id
))
205 ui::MenuModel
* model
= ModelForMenuItem(GTK_MENU_ITEM(widget
));
207 // If we're not providing the sub menu, then there's no model. For
208 // example, the IME submenu doesn't have a model.
211 bool* block_activation
= static_cast<bool*>(block_activation_ptr
);
213 if (GTK_IS_CHECK_MENU_ITEM(widget
)) {
214 GtkCheckMenuItem
* item
= GTK_CHECK_MENU_ITEM(widget
);
216 // gtk_check_menu_item_set_active() will send the activate signal. Touching
217 // the underlying "active" property will also call the "activate" handler
218 // for this menu item. So we prevent the "activate" handler from
219 // being called while we set the checkbox.
220 // Why not use one of the glib signal-blocking functions? Because when we
221 // toggle a radio button, it will deactivate one of the other radio buttons,
222 // which we don't have a pointer to.
223 *block_activation
= true;
224 gtk_check_menu_item_set_active(item
, model
->IsItemCheckedAt(id
));
225 *block_activation
= false;
228 if (GTK_IS_MENU_ITEM(widget
)) {
229 gtk_widget_set_sensitive(widget
, model
->IsEnabledAt(id
));
231 if (model
->IsVisibleAt(id
)) {
232 // Update the menu item label if it is dynamic.
233 if (model
->IsItemDynamicAt(id
)) {
234 std::string label
= ui::ConvertAcceleratorsFromWindowsStyle(
235 base::UTF16ToUTF8(model
->GetLabelAt(id
)));
237 gtk_menu_item_set_label(GTK_MENU_ITEM(widget
), label
.c_str());
238 if (GTK_IS_IMAGE_MENU_ITEM(widget
)) {
240 if (model
->GetIconAt(id
, &icon
)) {
241 GdkPixbuf
* pixbuf
= GdkPixbufFromSkBitmap(*icon
.ToSkBitmap());
242 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget
),
243 gtk_image_new_from_pixbuf(pixbuf
));
244 g_object_unref(pixbuf
);
246 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget
), NULL
);
251 gtk_widget_show(widget
);
253 gtk_widget_hide(widget
);
256 GtkWidget
* submenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget
));
258 gtk_container_foreach(
259 GTK_CONTAINER(submenu
), &SetMenuItemInfo
, block_activation_ptr
);
264 } // namespace libgtk2ui