OLD | NEW |
| (Empty) |
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 "ui/views/controls/menu/native_menu_gtk.h" | |
6 | |
7 #include <algorithm> | |
8 #include <map> | |
9 #include <string> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/i18n/rtl.h" | |
13 #include "base/message_loop.h" | |
14 #include "base/time.h" | |
15 #include "base/utf_string_conversions.h" | |
16 #include "third_party/skia/include/core/SkBitmap.h" | |
17 #include "ui/base/accelerators/accelerator.h" | |
18 #include "ui/base/keycodes/keyboard_code_conversion_gtk.h" | |
19 #include "ui/base/keycodes/keyboard_codes.h" | |
20 #include "ui/base/models/menu_model.h" | |
21 #include "ui/gfx/font.h" | |
22 #include "ui/gfx/gtk_util.h" | |
23 #include "ui/views/controls/menu/menu_2.h" | |
24 #include "ui/views/controls/menu/menu_listener.h" | |
25 #include "ui/views/controls/menu/nested_dispatcher_gtk.h" | |
26 #include "ui/views/views_delegate.h" | |
27 #include "ui/views/widget/native_widget_gtk.h" | |
28 | |
29 namespace { | |
30 | |
31 const char kPositionString[] = "position"; | |
32 const char kAccelGroupString[] = "accel_group"; | |
33 | |
34 // Key for the property set on the gtk menu that gives the handle to the hosting | |
35 // NativeMenuGtk. | |
36 const char kNativeMenuGtkString[] = "native_menu_gtk"; | |
37 | |
38 // Data passed to the MenuPositionFunc from gtk_menu_popup | |
39 struct Position { | |
40 // The point to run the menu at. | |
41 gfx::Point point; | |
42 // The alignment of the menu at that point. | |
43 views::Menu2::Alignment alignment; | |
44 }; | |
45 | |
46 // Returns true if the menu item type specified can be executed as a command. | |
47 bool MenuTypeCanExecute(ui::MenuModel::ItemType type) { | |
48 return type == ui::MenuModel::TYPE_COMMAND || | |
49 type == ui::MenuModel::TYPE_CHECK || | |
50 type == ui::MenuModel::TYPE_RADIO; | |
51 } | |
52 | |
53 // A callback to gtk_container_foreach to remove all children. | |
54 // See |NativeMenuGtk::ResetMenu| for the usage. | |
55 void RemoveChildWidget(GtkWidget* widget, gpointer data) { | |
56 GtkWidget* parent = gtk_widget_get_parent(widget); | |
57 gtk_container_remove(GTK_CONTAINER(parent), widget); | |
58 } | |
59 | |
60 } // namespace | |
61 | |
62 namespace views { | |
63 | |
64 //////////////////////////////////////////////////////////////////////////////// | |
65 // NativeMenuGtk, public: | |
66 | |
67 NativeMenuGtk::NativeMenuGtk(Menu2* menu) | |
68 : parent_(NULL), | |
69 model_(menu->model()), | |
70 menu_(NULL), | |
71 menu_hidden_(true), | |
72 suppress_activate_signal_(false), | |
73 activated_menu_(NULL), | |
74 activated_index_(-1), | |
75 activate_factory_(this), | |
76 host_menu_(menu), | |
77 destroy_handler_id_(0), | |
78 expose_handler_id_(0), | |
79 menu_action_(MENU_ACTION_NONE), | |
80 nested_dispatcher_(NULL), | |
81 ignore_button_release_(true) { | |
82 } | |
83 | |
84 NativeMenuGtk::~NativeMenuGtk() { | |
85 if (nested_dispatcher_) { | |
86 // Menu is destroyed while its in message loop. | |
87 // Let nested dispatcher know the creator is deleted. | |
88 nested_dispatcher_->CreatorDestroyed(); | |
89 } | |
90 if (menu_) { | |
91 DCHECK(destroy_handler_id_); | |
92 // Don't call MenuDestroyed because menu2 has already been destroyed. | |
93 g_signal_handler_disconnect(menu_, destroy_handler_id_); | |
94 gtk_widget_destroy(menu_); | |
95 } | |
96 } | |
97 | |
98 //////////////////////////////////////////////////////////////////////////////// | |
99 // NativeMenuGtk, MenuWrapper implementation: | |
100 | |
101 void NativeMenuGtk::RunMenuAt(const gfx::Point& point, int alignment) { | |
102 activated_menu_ = NULL; | |
103 activated_index_ = -1; | |
104 menu_action_ = MENU_ACTION_NONE; | |
105 // ignore button release event unless mouse is pressed or moved. | |
106 ignore_button_release_ = true; | |
107 | |
108 UpdateStates(); | |
109 // Set the FREEZE UPDATE property to the menu's window so that WM maps | |
110 // the menu after the menu painted itself. | |
111 GtkWidget* popup_window = gtk_widget_get_ancestor(menu_, GTK_TYPE_WINDOW); | |
112 CHECK(popup_window); | |
113 NativeWidgetGtk::UpdateFreezeUpdatesProperty(GTK_WINDOW(popup_window), | |
114 true /* add */); | |
115 expose_handler_id_ = g_signal_connect_after(G_OBJECT(menu_), "expose_event", | |
116 G_CALLBACK(&OnExposeThunk), this); | |
117 | |
118 Position position = { point, static_cast<Menu2::Alignment>(alignment) }; | |
119 // TODO(beng): value of '1' will not work for context menus! | |
120 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, MenuPositionFunc, &position, 1, | |
121 gtk_get_current_event_time()); | |
122 DCHECK(menu_hidden_); | |
123 menu_hidden_ = false; | |
124 | |
125 FOR_EACH_OBSERVER(MenuListener, listeners_, OnMenuOpened()); | |
126 | |
127 // Listen for "hide" signal so that we know when to return from the blocking | |
128 // RunMenuAt call. | |
129 gint hide_handle_id = | |
130 g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this); | |
131 | |
132 gint move_handle_id = | |
133 g_signal_connect(menu_, "move-current", | |
134 G_CALLBACK(OnMenuMoveCurrentThunk), this); | |
135 gint after_move_handle_id = | |
136 g_signal_connect_after(menu_, "move-current", | |
137 G_CALLBACK(AfterMenuMoveCurrentThunk), this); | |
138 | |
139 model_->MenuWillShow(); | |
140 | |
141 // Block until menu is no longer shown by running a nested message loop. | |
142 nested_dispatcher_ = new NestedDispatcherGtk(this, true); | |
143 bool deleted = nested_dispatcher_->RunAndSelfDestruct(); | |
144 if (deleted) { | |
145 // The menu was destryed while menu is shown, so return immediately. | |
146 // Don't touch the instance which is already deleted. | |
147 return; | |
148 } | |
149 nested_dispatcher_ = NULL; | |
150 if (!menu_hidden_) { | |
151 // If this happens it means we haven't yet gotten the hide signal and | |
152 // someone else quit the message loop on us. | |
153 NOTREACHED(); | |
154 menu_hidden_ = true; | |
155 } | |
156 | |
157 g_signal_handler_disconnect(G_OBJECT(menu_), hide_handle_id); | |
158 g_signal_handler_disconnect(G_OBJECT(menu_), move_handle_id); | |
159 g_signal_handler_disconnect(G_OBJECT(menu_), after_move_handle_id); | |
160 | |
161 if (activated_menu_) { | |
162 MessageLoop::current()->PostTask(FROM_HERE, | |
163 base::Bind(&NativeMenuGtk::ProcessActivate, | |
164 activate_factory_.GetWeakPtr())); | |
165 } | |
166 | |
167 model_->MenuClosed(); | |
168 } | |
169 | |
170 void NativeMenuGtk::CancelMenu() { | |
171 gtk_widget_hide(menu_); | |
172 } | |
173 | |
174 void NativeMenuGtk::Rebuild() { | |
175 activated_menu_ = NULL; | |
176 | |
177 ResetMenu(); | |
178 | |
179 // Try to retrieve accelerator group as data from menu_; if null, create new | |
180 // one and store it as data into menu_. | |
181 // We store it as data so as to use the destroy notifier to get rid of initial | |
182 // reference count. For some reason, when we unref it ourselves (even in | |
183 // destructor), it would cause random crashes, depending on when gtk tries to | |
184 // access it. | |
185 GtkAccelGroup* accel_group = static_cast<GtkAccelGroup*>( | |
186 g_object_get_data(G_OBJECT(menu_), kAccelGroupString)); | |
187 if (!accel_group) { | |
188 accel_group = gtk_accel_group_new(); | |
189 g_object_set_data_full(G_OBJECT(menu_), kAccelGroupString, accel_group, | |
190 g_object_unref); | |
191 } | |
192 | |
193 std::map<int, GtkRadioMenuItem*> radio_groups_; | |
194 for (int i = 0; i < model_->GetItemCount(); ++i) { | |
195 ui::MenuModel::ItemType type = model_->GetTypeAt(i); | |
196 if (type == ui::MenuModel::TYPE_SEPARATOR) { | |
197 AddSeparatorAt(i); | |
198 } else if (type == ui::MenuModel::TYPE_RADIO) { | |
199 const int radio_group_id = model_->GetGroupIdAt(i); | |
200 std::map<int, GtkRadioMenuItem*>::const_iterator iter | |
201 = radio_groups_.find(radio_group_id); | |
202 if (iter == radio_groups_.end()) { | |
203 GtkWidget* new_menu_item = AddMenuItemAt(i, NULL, accel_group); | |
204 // |new_menu_item| is the first menu item for |radio_group_id| group. | |
205 radio_groups_.insert( | |
206 std::make_pair(radio_group_id, GTK_RADIO_MENU_ITEM(new_menu_item))); | |
207 } else { | |
208 AddMenuItemAt(i, iter->second, accel_group); | |
209 } | |
210 } else { | |
211 AddMenuItemAt(i, NULL, accel_group); | |
212 } | |
213 } | |
214 if (!menu_hidden_) | |
215 gtk_menu_reposition(GTK_MENU(menu_)); | |
216 } | |
217 | |
218 void NativeMenuGtk::UpdateStates() { | |
219 gtk_container_foreach(GTK_CONTAINER(menu_), &UpdateStateCallback, this); | |
220 } | |
221 | |
222 gfx::NativeMenu NativeMenuGtk::GetNativeMenu() const { | |
223 return menu_; | |
224 } | |
225 | |
226 NativeMenuGtk::MenuAction NativeMenuGtk::GetMenuAction() const { | |
227 return menu_action_; | |
228 } | |
229 | |
230 void NativeMenuGtk::AddMenuListener(MenuListener* listener) { | |
231 listeners_.AddObserver(listener); | |
232 } | |
233 | |
234 void NativeMenuGtk::RemoveMenuListener(MenuListener* listener) { | |
235 listeners_.RemoveObserver(listener); | |
236 } | |
237 | |
238 void NativeMenuGtk::SetMinimumWidth(int width) { | |
239 gtk_widget_set_size_request(menu_, width, -1); | |
240 } | |
241 | |
242 bool NativeMenuGtk::Dispatch(GdkEvent* event) { | |
243 if (menu_hidden_) { | |
244 // The menu has been closed but the message loop is still nested. Don't | |
245 // dispatch a message, otherwise we might spawn another message loop. | |
246 return false; // Exits the nested message loop. | |
247 } | |
248 switch (event->type) { | |
249 case GDK_BUTTON_PRESS: | |
250 case GDK_2BUTTON_PRESS: | |
251 case GDK_3BUTTON_PRESS: { | |
252 ignore_button_release_ = false; | |
253 gpointer data = NULL; | |
254 gdk_window_get_user_data(((GdkEventAny*)event)->window, &data); | |
255 GtkWidget* widget = reinterpret_cast<GtkWidget*>(data); | |
256 if (widget) { | |
257 GtkWidget* root = gtk_widget_get_toplevel(widget); | |
258 if (!g_object_get_data(G_OBJECT(root), kNativeMenuGtkString)) { | |
259 // The button event is not targeted at a menu, hide the menu and eat | |
260 // the event. If we didn't do this the button press is dispatched from | |
261 // the nested message loop and bad things can happen (like trying to | |
262 // spawn another menu. | |
263 gtk_menu_popdown(GTK_MENU(menu_)); | |
264 // In some cases we may not have gotten the hide event, but the menu | |
265 // will be in the process of hiding so set menu_hidden_ anyway. | |
266 menu_hidden_ = true; | |
267 return false; // Exits the nested message loop. | |
268 } | |
269 } | |
270 break; | |
271 } | |
272 case GDK_MOTION_NOTIFY: { | |
273 ignore_button_release_ = false; | |
274 break; | |
275 } | |
276 case GDK_BUTTON_RELEASE: { | |
277 if (ignore_button_release_) { | |
278 // Ignore if a release event happened without press event. | |
279 // Normally, release event is eaten by gtk when menu is opened | |
280 // in response to mouse press event. Since the renderer opens | |
281 // the context menu asyncrhonous after press event is handled, | |
282 // gtk sometimes does not eat it, which causes the menu to be | |
283 // closed. | |
284 return true; | |
285 } | |
286 break; | |
287 } | |
288 default: | |
289 break; | |
290 } | |
291 gtk_main_do_event(event); | |
292 return true; | |
293 } | |
294 | |
295 //////////////////////////////////////////////////////////////////////////////// | |
296 // NativeMenuGtk, private: | |
297 | |
298 void NativeMenuGtk::OnMenuHidden(GtkWidget* widget) { | |
299 if (menu_hidden_) { | |
300 // The menu has been already hidden by us and we're in the process of | |
301 // quiting the message loop.. | |
302 return; | |
303 } | |
304 // Quit the nested message loop we spawned in RunMenuAt. | |
305 MessageLoop::current()->Quit(); | |
306 | |
307 // Menu can be closed before the menu is shown. | |
308 if (expose_handler_id_) { | |
309 g_signal_handler_disconnect(menu_, expose_handler_id_); | |
310 expose_handler_id_ = 0; | |
311 } | |
312 | |
313 menu_hidden_ = true; | |
314 } | |
315 | |
316 void NativeMenuGtk::OnMenuMoveCurrent(GtkWidget* menu_widget, | |
317 GtkMenuDirectionType focus_direction) { | |
318 GtkWidget* parent = GTK_MENU_SHELL(menu_widget)->parent_menu_shell; | |
319 GtkWidget* menu_item = GTK_MENU_SHELL(menu_widget)->active_menu_item; | |
320 GtkWidget* submenu = NULL; | |
321 if (menu_item) { | |
322 submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
323 } | |
324 if (focus_direction == GTK_MENU_DIR_CHILD && submenu == NULL) { | |
325 GetAncestor()->menu_action_ = MENU_ACTION_NEXT; | |
326 gtk_menu_popdown(GTK_MENU(menu_widget)); | |
327 } else if (focus_direction == GTK_MENU_DIR_PARENT && parent == NULL) { | |
328 GetAncestor()->menu_action_ = MENU_ACTION_PREVIOUS; | |
329 gtk_menu_popdown(GTK_MENU(menu_widget)); | |
330 } | |
331 } | |
332 | |
333 void NativeMenuGtk::AfterMenuMoveCurrent(GtkWidget* menu_widget, | |
334 GtkMenuDirectionType focus_direction) { | |
335 SendAccessibilityEvent(); | |
336 } | |
337 | |
338 gboolean NativeMenuGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) { | |
339 GtkWidget* popup_window = gtk_widget_get_ancestor(menu_, GTK_TYPE_WINDOW); | |
340 CHECK(popup_window); | |
341 DCHECK(expose_handler_id_); | |
342 NativeWidgetGtk::UpdateFreezeUpdatesProperty(GTK_WINDOW(popup_window), | |
343 false /* remove */); | |
344 if (expose_handler_id_) { | |
345 g_signal_handler_disconnect(menu_, expose_handler_id_); | |
346 expose_handler_id_ = 0; | |
347 } | |
348 return false; | |
349 } | |
350 | |
351 void NativeMenuGtk::AddSeparatorAt(int index) { | |
352 GtkWidget* separator = gtk_separator_menu_item_new(); | |
353 gtk_widget_show(separator); | |
354 gtk_menu_append(menu_, separator); | |
355 } | |
356 | |
357 GtkWidget* NativeMenuGtk::AddMenuItemAt(int index, | |
358 GtkRadioMenuItem* radio_group, | |
359 GtkAccelGroup* accel_group) { | |
360 GtkWidget* menu_item = NULL; | |
361 std::string label = gfx::ConvertAcceleratorsFromWindowsStyle(UTF16ToUTF8( | |
362 model_->GetLabelAt(index))); | |
363 | |
364 ui::MenuModel::ItemType type = model_->GetTypeAt(index); | |
365 switch (type) { | |
366 case ui::MenuModel::TYPE_CHECK: | |
367 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); | |
368 break; | |
369 case ui::MenuModel::TYPE_RADIO: | |
370 if (radio_group) { | |
371 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( | |
372 radio_group, label.c_str()); | |
373 } else { | |
374 // The item does not belong to any existing radio button groups. | |
375 menu_item = gtk_radio_menu_item_new_with_mnemonic(NULL, label.c_str()); | |
376 } | |
377 break; | |
378 case ui::MenuModel::TYPE_SUBMENU: | |
379 case ui::MenuModel::TYPE_COMMAND: { | |
380 SkBitmap icon; | |
381 // Create menu item with icon if icon exists. | |
382 if (model_->HasIcons() && model_->GetIconAt(index, &icon)) { | |
383 menu_item = gtk_image_menu_item_new_with_mnemonic(label.c_str()); | |
384 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); | |
385 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), | |
386 gtk_image_new_from_pixbuf(pixbuf)); | |
387 g_object_unref(pixbuf); | |
388 | |
389 // Show the image even if the "gtk-menu-images" setting is turned off. | |
390 gtk_image_menu_item_set_always_show_image( | |
391 GTK_IMAGE_MENU_ITEM(menu_item), TRUE); | |
392 } else { | |
393 menu_item = gtk_menu_item_new_with_mnemonic(label.c_str()); | |
394 } | |
395 break; | |
396 } | |
397 default: | |
398 NOTREACHED(); | |
399 break; | |
400 } | |
401 | |
402 // Label font. | |
403 const gfx::Font* font = model_->GetLabelFontAt(index); | |
404 if (font) { | |
405 // The label item is the first child of the menu item. | |
406 GtkWidget* label_widget = GTK_BIN(menu_item)->child; | |
407 DCHECK(label_widget && GTK_IS_LABEL(label_widget)); | |
408 PangoFontDescription* pfd = font->GetNativeFont(); | |
409 gtk_widget_modify_font(label_widget, pfd); | |
410 pango_font_description_free(pfd); | |
411 } | |
412 | |
413 if (type == ui::MenuModel::TYPE_SUBMENU) { | |
414 Menu2* submenu = new Menu2(model_->GetSubmenuModelAt(index)); | |
415 static_cast<NativeMenuGtk*>(submenu->wrapper_.get())->set_parent(this); | |
416 g_object_set_data(G_OBJECT(menu_item), "submenu", submenu); | |
417 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), | |
418 submenu->GetNativeMenu()); | |
419 g_signal_connect(submenu->GetNativeMenu(), "move-current", | |
420 G_CALLBACK(OnMenuMoveCurrentThunk), this); | |
421 } | |
422 | |
423 ui::Accelerator accelerator(ui::VKEY_UNKNOWN, false, false, false); | |
424 if (accel_group && model_->GetAcceleratorAt(index, &accelerator)) { | |
425 int gdk_modifiers = 0; | |
426 if (accelerator.IsShiftDown()) | |
427 gdk_modifiers |= GDK_SHIFT_MASK; | |
428 if (accelerator.IsCtrlDown()) | |
429 gdk_modifiers |= GDK_CONTROL_MASK; | |
430 if (accelerator.IsAltDown()) | |
431 gdk_modifiers |= GDK_MOD1_MASK; | |
432 gtk_widget_add_accelerator(menu_item, "activate", accel_group, | |
433 ui::GdkKeyCodeForWindowsKeyCode(accelerator.key_code(), false), | |
434 static_cast<GdkModifierType>(gdk_modifiers), GTK_ACCEL_VISIBLE); | |
435 } | |
436 | |
437 g_object_set_data(G_OBJECT(menu_item), kPositionString, | |
438 reinterpret_cast<void*>(index)); | |
439 g_signal_connect(menu_item, "activate", G_CALLBACK(CallActivate), this); | |
440 UpdateMenuItemState(menu_item, false); | |
441 gtk_widget_show(menu_item); | |
442 gtk_menu_append(menu_, menu_item); | |
443 | |
444 return menu_item; | |
445 } | |
446 | |
447 void NativeMenuGtk::ResetMenu() { | |
448 if (!menu_) { | |
449 menu_ = gtk_menu_new(); | |
450 g_object_set_data( | |
451 G_OBJECT(GTK_MENU(menu_)->toplevel), kNativeMenuGtkString, this); | |
452 destroy_handler_id_ = g_signal_connect( | |
453 menu_, "destroy", G_CALLBACK(NativeMenuGtk::MenuDestroyed), host_menu_); | |
454 } else { | |
455 gtk_container_foreach(GTK_CONTAINER(menu_), RemoveChildWidget, NULL); | |
456 } | |
457 } | |
458 | |
459 void NativeMenuGtk::UpdateMenuItemState(GtkWidget* menu_item, bool recurse) { | |
460 int index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), | |
461 kPositionString)); | |
462 | |
463 gtk_widget_set_sensitive(menu_item, model_->IsEnabledAt(index)); | |
464 if (GTK_IS_CHECK_MENU_ITEM(menu_item)) { | |
465 suppress_activate_signal_ = true; | |
466 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), | |
467 model_->IsItemCheckedAt(index)); | |
468 suppress_activate_signal_ = false; | |
469 } | |
470 | |
471 if (recurse && GTK_IS_MENU_ITEM(menu_item)) { | |
472 // Recurse into submenus. | |
473 if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item))) { | |
474 Menu2* submenu = | |
475 reinterpret_cast<Menu2*>(g_object_get_data(G_OBJECT(menu_item), | |
476 "submenu")); | |
477 if (submenu) | |
478 submenu->UpdateStates(); | |
479 } | |
480 } | |
481 } | |
482 | |
483 // static | |
484 void NativeMenuGtk::UpdateStateCallback(GtkWidget* menu_item, gpointer data) { | |
485 NativeMenuGtk* menu = reinterpret_cast<NativeMenuGtk*>(data); | |
486 menu->UpdateMenuItemState(menu_item, true); | |
487 } | |
488 | |
489 // static | |
490 void NativeMenuGtk::MenuPositionFunc(GtkMenu* menu, | |
491 int* x, | |
492 int* y, | |
493 gboolean* push_in, | |
494 void* data) { | |
495 Position* position = reinterpret_cast<Position*>(data); | |
496 | |
497 GtkRequisition menu_req; | |
498 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); | |
499 | |
500 *x = position->point.x(); | |
501 *y = position->point.y(); | |
502 views::Menu2::Alignment alignment = position->alignment; | |
503 if (base::i18n::IsRTL()) { | |
504 switch (alignment) { | |
505 case Menu2::ALIGN_TOPRIGHT: | |
506 alignment = Menu2::ALIGN_TOPLEFT; | |
507 break; | |
508 case Menu2::ALIGN_TOPLEFT: | |
509 alignment = Menu2::ALIGN_TOPRIGHT; | |
510 break; | |
511 default: | |
512 NOTREACHED(); | |
513 break; | |
514 } | |
515 } | |
516 if (alignment == Menu2::ALIGN_TOPRIGHT) | |
517 *x -= menu_req.width; | |
518 | |
519 // Make sure the popup fits on screen. | |
520 GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET(menu)); | |
521 *x = std::max(0, std::min(gdk_screen_get_width(screen) - menu_req.width, *x)); | |
522 *y = std::max(0, std::min(gdk_screen_get_height(screen) - menu_req.height, | |
523 *y)); | |
524 | |
525 *push_in = FALSE; | |
526 } | |
527 | |
528 void NativeMenuGtk::OnActivate(GtkMenuItem* menu_item) { | |
529 if (suppress_activate_signal_) | |
530 return; | |
531 int position = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), | |
532 kPositionString)); | |
533 // Ignore the signal if it's sent to an inactive checked radio item. | |
534 // | |
535 // Suppose there are three radio items A, B, C, and A is now being | |
536 // checked. If you click C, "activate" signal will be sent to A and C. | |
537 // Here, we ignore the signal sent to A. | |
538 if (GTK_IS_RADIO_MENU_ITEM(menu_item) && | |
539 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) { | |
540 return; | |
541 } | |
542 | |
543 // NOTE: we get activate messages for submenus when first shown. | |
544 if (model_->IsEnabledAt(position) && | |
545 MenuTypeCanExecute(model_->GetTypeAt(position))) { | |
546 NativeMenuGtk* ancestor = GetAncestor(); | |
547 ancestor->activated_menu_ = this; | |
548 activated_index_ = position; | |
549 ancestor->menu_action_ = MENU_ACTION_SELECTED; | |
550 } | |
551 } | |
552 | |
553 // static | |
554 void NativeMenuGtk::CallActivate(GtkMenuItem* menu_item, | |
555 NativeMenuGtk* native_menu) { | |
556 native_menu->OnActivate(menu_item); | |
557 } | |
558 | |
559 NativeMenuGtk* NativeMenuGtk::GetAncestor() { | |
560 NativeMenuGtk* ancestor = this; | |
561 while (ancestor->parent_) | |
562 ancestor = ancestor->parent_; | |
563 return ancestor; | |
564 } | |
565 | |
566 void NativeMenuGtk::ProcessActivate() { | |
567 if (activated_menu_) | |
568 activated_menu_->Activate(); | |
569 } | |
570 | |
571 void NativeMenuGtk::Activate() { | |
572 if (model_->IsEnabledAt(activated_index_) && | |
573 MenuTypeCanExecute(model_->GetTypeAt(activated_index_))) { | |
574 model_->ActivatedAt(activated_index_); | |
575 } | |
576 } | |
577 | |
578 void NativeMenuGtk::SendAccessibilityEvent() { | |
579 // Find the focused menu item, recursing into submenus as needed. | |
580 GtkWidget* menu = menu_; | |
581 GtkWidget* menu_item = GTK_MENU_SHELL(menu_)->active_menu_item; | |
582 if (!menu_item) | |
583 return; | |
584 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
585 while (submenu && GTK_MENU_SHELL(submenu)->active_menu_item) { | |
586 menu = submenu; | |
587 menu_item = GTK_MENU_SHELL(menu)->active_menu_item; | |
588 if (!menu_item) | |
589 return; | |
590 submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
591 } | |
592 | |
593 // Figure out the item index and total number of items. | |
594 GList* items = gtk_container_get_children(GTK_CONTAINER(menu)); | |
595 guint count = g_list_length(items); | |
596 int index = g_list_index(items, static_cast<gconstpointer>(menu_item)); | |
597 | |
598 // Get the menu item's label. | |
599 std::string name; | |
600 name = gtk_menu_item_get_label(GTK_MENU_ITEM(menu_item)); | |
601 | |
602 if (ViewsDelegate::views_delegate) { | |
603 ViewsDelegate::views_delegate->NotifyMenuItemFocused(string16(), | |
604 UTF8ToUTF16(name), | |
605 index, | |
606 count, | |
607 submenu != NULL); | |
608 } | |
609 } | |
610 | |
611 // static | |
612 void NativeMenuGtk::MenuDestroyed(GtkWidget* widget, Menu2* menu2) { | |
613 NativeMenuGtk* native_menu = | |
614 static_cast<NativeMenuGtk*>(menu2->wrapper_.get()); | |
615 // The native gtk widget has already been destroyed. | |
616 native_menu->menu_ = NULL; | |
617 delete menu2; | |
618 } | |
619 | |
620 //////////////////////////////////////////////////////////////////////////////// | |
621 // MenuWrapper, public: | |
622 | |
623 // static | |
624 MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) { | |
625 return new NativeMenuGtk(menu); | |
626 } | |
627 | |
628 } // namespace views | |
OLD | NEW |