OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/ui/libgtk2ui/menu_util.h" | |
6 | |
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" | |
14 | |
15 G_GNUC_BEGIN_IGNORE_DEPRECATIONS | |
16 | |
17 namespace libgtk2ui { | |
18 | |
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); | |
22 return menu_item; | |
23 } | |
24 | |
25 GtkWidget* BuildMenuItemWithImage(const std::string& label, | |
26 const gfx::Image& icon) { | |
27 GdkPixbuf* pixbuf = GdkPixbufFromSkBitmap(*icon.ToSkBitmap()); | |
28 | |
29 GtkWidget* menu_item = | |
30 BuildMenuItemWithImage(label, gtk_image_new_from_pixbuf(pixbuf)); | |
31 g_object_unref(pixbuf); | |
32 return menu_item; | |
33 } | |
34 | |
35 GtkWidget* BuildMenuItemWithLabel(const std::string& label) { | |
36 return gtk_menu_item_new_with_mnemonic(label.c_str()); | |
37 } | |
38 | |
39 ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) { | |
40 return reinterpret_cast<ui::MenuModel*>( | |
41 g_object_get_data(G_OBJECT(menu_item), "model")); | |
42 } | |
43 | |
44 GtkWidget* AppendMenuItemToMenu(int index, | |
45 ui::MenuModel* model, | |
46 GtkWidget* menu_item, | |
47 GtkWidget* menu, | |
48 bool connect_to_activate, | |
49 GCallback item_activated_cb, | |
50 void* this_ptr) { | |
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)); | |
54 | |
55 // Native menu items do their own thing, so only selectively listen for the | |
56 // activate signal. | |
57 if (connect_to_activate) { | |
58 g_signal_connect(menu_item, "activate", item_activated_cb, this_ptr); | |
59 } | |
60 | |
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. | |
64 if (model) { | |
65 if (model->IsVisibleAt(index)) | |
66 gtk_widget_show(menu_item); | |
67 } else { | |
68 gtk_widget_show(menu_item); | |
69 } | |
70 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); | |
71 return menu_item; | |
72 } | |
73 | |
74 bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) { | |
75 gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id"); | |
76 if (id_ptr != NULL) { | |
77 *menu_id = GPOINTER_TO_INT(id_ptr) - 1; | |
78 return true; | |
79 } | |
80 | |
81 return false; | |
82 } | |
83 | |
84 void ExecuteCommand(ui::MenuModel* model, int id) { | |
85 GdkEvent* event = gtk_get_current_event(); | |
86 int event_flags = 0; | |
87 | |
88 if (event && event->type == GDK_BUTTON_RELEASE) | |
89 event_flags = EventFlagsFromGdkState(event->button.state); | |
90 model->ActivatedAt(id, event_flags); | |
91 | |
92 if (event) | |
93 gdk_event_free(event); | |
94 } | |
95 | |
96 void BuildSubmenuFromModel(ui::MenuModel* model, | |
97 GtkWidget* menu, | |
98 GCallback item_activated_cb, | |
99 bool* block_activation, | |
100 void* this_ptr) { | |
101 std::map<int, GtkWidget*> radio_groups; | |
102 GtkWidget* menu_item = NULL; | |
103 for (int i = 0; i < model->GetItemCount(); ++i) { | |
104 gfx::Image icon; | |
105 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( | |
106 base::UTF16ToUTF8(model->GetLabelAt(i))); | |
107 | |
108 bool connect_to_activate = true; | |
109 | |
110 switch (model->GetTypeAt(i)) { | |
111 case ui::MenuModel::TYPE_SEPARATOR: | |
112 menu_item = gtk_separator_menu_item_new(); | |
113 break; | |
114 | |
115 case ui::MenuModel::TYPE_CHECK: | |
116 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); | |
117 break; | |
118 | |
119 case ui::MenuModel::TYPE_RADIO: { | |
120 std::map<int, GtkWidget*>::iterator iter = | |
121 radio_groups.find(model->GetGroupIdAt(i)); | |
122 | |
123 if (iter == radio_groups.end()) { | |
124 menu_item = | |
125 gtk_radio_menu_item_new_with_mnemonic(NULL, label.c_str()); | |
126 radio_groups[model->GetGroupIdAt(i)] = menu_item; | |
127 } else { | |
128 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( | |
129 GTK_RADIO_MENU_ITEM(iter->second), label.c_str()); | |
130 } | |
131 break; | |
132 } | |
133 case ui::MenuModel::TYPE_BUTTON_ITEM: { | |
134 NOTIMPLEMENTED(); | |
135 break; | |
136 } | |
137 case ui::MenuModel::TYPE_SUBMENU: | |
138 case ui::MenuModel::TYPE_COMMAND: { | |
139 if (model->GetIconAt(i, &icon)) | |
140 menu_item = BuildMenuItemWithImage(label, icon); | |
141 else | |
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); | |
146 } | |
147 break; | |
148 } | |
149 | |
150 default: | |
151 NOTREACHED(); | |
152 } | |
153 | |
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, | |
158 submenu, | |
159 item_activated_cb, | |
160 block_activation, | |
161 this_ptr); | |
162 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | |
163 | |
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; | |
169 } | |
170 | |
171 ui::Accelerator accelerator; | |
172 if (model->GetAcceleratorAt(i, &accelerator)) { | |
173 gtk_widget_add_accelerator(menu_item, | |
174 "activate", | |
175 NULL, | |
176 GetGdkKeyCodeForAccelerator(accelerator), | |
177 GetGdkModifierForAccelerator(accelerator), | |
178 GTK_ACCEL_VISIBLE); | |
179 } | |
180 | |
181 g_object_set_data(G_OBJECT(menu_item), "model", model); | |
182 AppendMenuItemToMenu(i, | |
183 model, | |
184 menu_item, | |
185 menu, | |
186 connect_to_activate, | |
187 item_activated_cb, | |
188 this_ptr); | |
189 | |
190 menu_item = NULL; | |
191 } | |
192 } | |
193 | |
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. | |
198 return; | |
199 } | |
200 | |
201 int id; | |
202 if (!GetMenuItemID(widget, &id)) | |
203 return; | |
204 | |
205 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget)); | |
206 if (!model) { | |
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. | |
209 return; | |
210 } | |
211 bool* block_activation = static_cast<bool*>(block_activation_ptr); | |
212 | |
213 if (GTK_IS_CHECK_MENU_ITEM(widget)) { | |
214 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget); | |
215 | |
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; | |
226 } | |
227 | |
228 if (GTK_IS_MENU_ITEM(widget)) { | |
229 gtk_widget_set_sensitive(widget, model->IsEnabledAt(id)); | |
230 | |
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))); | |
236 | |
237 gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str()); | |
238 if (GTK_IS_IMAGE_MENU_ITEM(widget)) { | |
239 gfx::Image icon; | |
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); | |
245 } else { | |
246 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL); | |
247 } | |
248 } | |
249 } | |
250 | |
251 gtk_widget_show(widget); | |
252 } else { | |
253 gtk_widget_hide(widget); | |
254 } | |
255 | |
256 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); | |
257 if (submenu) { | |
258 gtk_container_foreach( | |
259 GTK_CONTAINER(submenu), &SetMenuItemInfo, block_activation_ptr); | |
260 } | |
261 } | |
262 } | |
263 | |
264 } // namespace libgtk2ui | |
OLD | NEW |