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 "chrome/browser/ui/gtk/menu_gtk.h"
6
7 #include <map>
8
9 #include "base/i18n/rtl.h"
10 #include "base/logging.h"
11 #include "base/message_loop.h"
12 #include "base/stl_util-inl.h"
13 #include "base/utf_string_conversions.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/ui/gtk/gtk_custom_menu.h"
16 #include "chrome/browser/ui/gtk/gtk_custom_menu_item.h"
17 #include "chrome/browser/ui/gtk/gtk_util.h"
18 #include "third_party/skia/include/core/SkBitmap.h"
19 #include "ui/base/models/accelerator_gtk.h"
20 #include "ui/base/models/button_menu_item_model.h"
21 #include "ui/base/models/menu_model.h"
22 #include "ui/gfx/gtk_util.h"
23 #include "webkit/glue/window_open_disposition.h"
24
25 bool MenuGtk::block_activation_ = false;
26
27 namespace {
28
29 // Sets the ID of a menu item.
SetMenuItemID(GtkWidget * menu_item,int menu_id)30 void SetMenuItemID(GtkWidget* menu_item, int menu_id) {
31 DCHECK_GE(menu_id, 0);
32
33 // Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
34 g_object_set_data(G_OBJECT(menu_item), "menu-id",
35 GINT_TO_POINTER(menu_id + 1));
36 }
37
38 // Gets the ID of a menu item.
39 // Returns true if the menu item has an ID.
GetMenuItemID(GtkWidget * menu_item,int * menu_id)40 bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) {
41 gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id");
42 if (id_ptr != NULL) {
43 *menu_id = GPOINTER_TO_INT(id_ptr) - 1;
44 return true;
45 }
46
47 return false;
48 }
49
ModelForMenuItem(GtkMenuItem * menu_item)50 ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) {
51 return reinterpret_cast<ui::MenuModel*>(
52 g_object_get_data(G_OBJECT(menu_item), "model"));
53 }
54
SetupButtonShowHandler(GtkWidget * button,ui::ButtonMenuItemModel * model,int index)55 void SetupButtonShowHandler(GtkWidget* button,
56 ui::ButtonMenuItemModel* model,
57 int index) {
58 g_object_set_data(G_OBJECT(button), "button-model",
59 model);
60 g_object_set_data(G_OBJECT(button), "button-model-id",
61 GINT_TO_POINTER(index));
62 }
63
OnSubmenuShowButtonImage(GtkWidget * widget,GtkButton * button)64 void OnSubmenuShowButtonImage(GtkWidget* widget, GtkButton* button) {
65 MenuGtk::Delegate* delegate = reinterpret_cast<MenuGtk::Delegate*>(
66 g_object_get_data(G_OBJECT(button), "menu-gtk-delegate"));
67 int icon_idr = GPOINTER_TO_INT(g_object_get_data(
68 G_OBJECT(button), "button-image-idr"));
69
70 GtkIconSet* icon_set = delegate->GetIconSetForId(icon_idr);
71 if (icon_set) {
72 gtk_button_set_image(
73 button, gtk_image_new_from_icon_set(icon_set,
74 GTK_ICON_SIZE_MENU));
75 }
76 }
77
SetupImageIcon(GtkWidget * button,GtkWidget * menu,int icon_idr,MenuGtk::Delegate * menu_gtk_delegate)78 void SetupImageIcon(GtkWidget* button,
79 GtkWidget* menu,
80 int icon_idr,
81 MenuGtk::Delegate* menu_gtk_delegate) {
82 g_object_set_data(G_OBJECT(button), "button-image-idr",
83 GINT_TO_POINTER(icon_idr));
84 g_object_set_data(G_OBJECT(button), "menu-gtk-delegate",
85 menu_gtk_delegate);
86
87 g_signal_connect(menu, "show", G_CALLBACK(OnSubmenuShowButtonImage), button);
88 }
89
90 // Popup menus may get squished if they open up too close to the bottom of the
91 // screen. This function takes the size of the screen, the size of the menu,
92 // an optional widget, the Y position of the mouse click, and adjusts the popup
93 // menu's Y position to make it fit if it's possible to do so.
94 // Returns the new Y position of the popup menu.
CalculateMenuYPosition(const GdkRectangle * screen_rect,const GtkRequisition * menu_req,const GtkWidget * widget,const int y)95 int CalculateMenuYPosition(const GdkRectangle* screen_rect,
96 const GtkRequisition* menu_req,
97 const GtkWidget* widget, const int y) {
98 CHECK(screen_rect);
99 CHECK(menu_req);
100 // If the menu would run off the bottom of the screen, and there is enough
101 // screen space upwards to accommodate the menu, then pop upwards. If there
102 // is a widget, then also move the anchor point to the top of the widget
103 // rather than the bottom.
104 const int screen_top = screen_rect->y;
105 const int screen_bottom = screen_rect->y + screen_rect->height;
106 const int menu_bottom = y + menu_req->height;
107 int alternate_y = y - menu_req->height;
108 if (widget)
109 alternate_y -= widget->allocation.height;
110 if (menu_bottom >= screen_bottom && alternate_y >= screen_top)
111 return alternate_y;
112 return y;
113 }
114
115 } // namespace
116
GetDefaultImageForCommandId(int command_id)117 GtkWidget* MenuGtk::Delegate::GetDefaultImageForCommandId(int command_id) {
118 const char* stock;
119 switch (command_id) {
120 case IDC_NEW_TAB:
121 case IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB:
122 case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB:
123 case IDC_CONTENT_CONTEXT_OPENAVNEWTAB:
124 stock = GTK_STOCK_NEW;
125 break;
126
127 case IDC_CLOSE_TAB:
128 stock = GTK_STOCK_CLOSE;
129 break;
130
131 case IDC_CONTENT_CONTEXT_SAVEIMAGEAS:
132 case IDC_CONTENT_CONTEXT_SAVEAVAS:
133 case IDC_CONTENT_CONTEXT_SAVELINKAS:
134 stock = GTK_STOCK_SAVE_AS;
135 break;
136
137 case IDC_SAVE_PAGE:
138 stock = GTK_STOCK_SAVE;
139 break;
140
141 case IDC_COPY:
142 case IDC_COPY_URL:
143 case IDC_CONTENT_CONTEXT_COPYIMAGELOCATION:
144 case IDC_CONTENT_CONTEXT_COPYLINKLOCATION:
145 case IDC_CONTENT_CONTEXT_COPYAVLOCATION:
146 case IDC_CONTENT_CONTEXT_COPYEMAILADDRESS:
147 case IDC_CONTENT_CONTEXT_COPY:
148 stock = GTK_STOCK_COPY;
149 break;
150
151 case IDC_CUT:
152 case IDC_CONTENT_CONTEXT_CUT:
153 stock = GTK_STOCK_CUT;
154 break;
155
156 case IDC_PASTE:
157 case IDC_CONTENT_CONTEXT_PASTE:
158 stock = GTK_STOCK_PASTE;
159 break;
160
161 case IDC_CONTENT_CONTEXT_DELETE:
162 case IDC_BOOKMARK_BAR_REMOVE:
163 stock = GTK_STOCK_DELETE;
164 break;
165
166 case IDC_CONTENT_CONTEXT_UNDO:
167 stock = GTK_STOCK_UNDO;
168 break;
169
170 case IDC_CONTENT_CONTEXT_REDO:
171 stock = GTK_STOCK_REDO;
172 break;
173
174 case IDC_SEARCH:
175 case IDC_FIND:
176 case IDC_CONTENT_CONTEXT_SEARCHWEBFOR:
177 stock = GTK_STOCK_FIND;
178 break;
179
180 case IDC_CONTENT_CONTEXT_SELECTALL:
181 stock = GTK_STOCK_SELECT_ALL;
182 break;
183
184 case IDC_CLEAR_BROWSING_DATA:
185 stock = GTK_STOCK_CLEAR;
186 break;
187
188 case IDC_BACK:
189 stock = GTK_STOCK_GO_BACK;
190 break;
191
192 case IDC_RELOAD:
193 stock = GTK_STOCK_REFRESH;
194 break;
195
196 case IDC_FORWARD:
197 stock = GTK_STOCK_GO_FORWARD;
198 break;
199
200 case IDC_PRINT:
201 stock = GTK_STOCK_PRINT;
202 break;
203
204 case IDC_CONTENT_CONTEXT_VIEWPAGEINFO:
205 stock = GTK_STOCK_INFO;
206 break;
207
208 case IDC_SPELLCHECK_MENU:
209 stock = GTK_STOCK_SPELL_CHECK;
210 break;
211
212 case IDC_RESTORE_TAB:
213 stock = GTK_STOCK_UNDELETE;
214 break;
215
216 case IDC_HOME:
217 stock = GTK_STOCK_HOME;
218 break;
219
220 case IDC_STOP:
221 stock = GTK_STOCK_STOP;
222 break;
223
224 case IDC_ABOUT:
225 stock = GTK_STOCK_ABOUT;
226 break;
227
228 case IDC_EXIT:
229 stock = GTK_STOCK_QUIT;
230 break;
231
232 case IDC_HELP_PAGE:
233 stock = GTK_STOCK_HELP;
234 break;
235
236 case IDC_OPTIONS:
237 stock = GTK_STOCK_PREFERENCES;
238 break;
239
240 case IDC_CONTENT_CONTEXT_GOTOURL:
241 stock = GTK_STOCK_JUMP_TO;
242 break;
243
244 case IDC_DEV_TOOLS_INSPECT:
245 case IDC_CONTENT_CONTEXT_INSPECTELEMENT:
246 stock = GTK_STOCK_PROPERTIES;
247 break;
248
249 case IDC_BOOKMARK_BAR_ADD_NEW_BOOKMARK:
250 stock = GTK_STOCK_ADD;
251 break;
252
253 case IDC_BOOKMARK_BAR_RENAME_FOLDER:
254 case IDC_BOOKMARK_BAR_EDIT:
255 stock = GTK_STOCK_EDIT;
256 break;
257
258 case IDC_BOOKMARK_BAR_NEW_FOLDER:
259 stock = GTK_STOCK_DIRECTORY;
260 break;
261
262 case IDC_BOOKMARK_BAR_OPEN_ALL:
263 stock = GTK_STOCK_OPEN;
264 break;
265
266 default:
267 stock = NULL;
268 }
269
270 return stock ? gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU) : NULL;
271 }
272
GetImageForCommandId(int command_id) const273 GtkWidget* MenuGtk::Delegate::GetImageForCommandId(int command_id) const {
274 return GetDefaultImageForCommandId(command_id);
275 }
276
MenuGtk(MenuGtk::Delegate * delegate,ui::MenuModel * model)277 MenuGtk::MenuGtk(MenuGtk::Delegate* delegate,
278 ui::MenuModel* model)
279 : delegate_(delegate),
280 model_(model),
281 dummy_accel_group_(gtk_accel_group_new()),
282 menu_(gtk_custom_menu_new()),
283 factory_(this) {
284 DCHECK(model);
285 g_object_ref_sink(menu_);
286 ConnectSignalHandlers();
287 BuildMenuFromModel();
288 }
289
~MenuGtk()290 MenuGtk::~MenuGtk() {
291 Cancel();
292
293 gtk_widget_destroy(menu_);
294 g_object_unref(menu_);
295
296 STLDeleteContainerPointers(submenus_we_own_.begin(), submenus_we_own_.end());
297 g_object_unref(dummy_accel_group_);
298 }
299
ConnectSignalHandlers()300 void MenuGtk::ConnectSignalHandlers() {
301 // We connect afterwards because OnMenuShow calls SetMenuItemInfo, which may
302 // take a long time or even start a nested message loop.
303 g_signal_connect(menu_, "show", G_CALLBACK(OnMenuShowThunk), this);
304 g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this);
305 }
306
AppendMenuItemWithLabel(int command_id,const std::string & label)307 GtkWidget* MenuGtk::AppendMenuItemWithLabel(int command_id,
308 const std::string& label) {
309 std::string converted_label = gfx::ConvertAcceleratorsFromWindowsStyle(label);
310 GtkWidget* menu_item = BuildMenuItemWithLabel(label, command_id);
311 return AppendMenuItem(command_id, menu_item);
312 }
313
AppendMenuItemWithIcon(int command_id,const std::string & label,const SkBitmap & icon)314 GtkWidget* MenuGtk::AppendMenuItemWithIcon(int command_id,
315 const std::string& label,
316 const SkBitmap& icon) {
317 std::string converted_label = gfx::ConvertAcceleratorsFromWindowsStyle(label);
318 GtkWidget* menu_item = BuildMenuItemWithImage(converted_label, icon);
319 return AppendMenuItem(command_id, menu_item);
320 }
321
AppendCheckMenuItemWithLabel(int command_id,const std::string & label)322 GtkWidget* MenuGtk::AppendCheckMenuItemWithLabel(int command_id,
323 const std::string& label) {
324 std::string converted_label = gfx::ConvertAcceleratorsFromWindowsStyle(label);
325 GtkWidget* menu_item =
326 gtk_check_menu_item_new_with_mnemonic(converted_label.c_str());
327 return AppendMenuItem(command_id, menu_item);
328 }
329
AppendSeparator()330 GtkWidget* MenuGtk::AppendSeparator() {
331 GtkWidget* menu_item = gtk_separator_menu_item_new();
332 gtk_widget_show(menu_item);
333 gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item);
334 return menu_item;
335 }
336
AppendMenuItem(int command_id,GtkWidget * menu_item)337 GtkWidget* MenuGtk::AppendMenuItem(int command_id, GtkWidget* menu_item) {
338 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
339 GTK_IS_IMAGE_MENU_ITEM(menu_item))
340 gtk_util::SetAlwaysShowImage(menu_item);
341
342 return AppendMenuItemToMenu(command_id, NULL, menu_item, menu_, true);
343 }
344
AppendMenuItemToMenu(int index,ui::MenuModel * model,GtkWidget * menu_item,GtkWidget * menu,bool connect_to_activate)345 GtkWidget* MenuGtk::AppendMenuItemToMenu(int index,
346 ui::MenuModel* model,
347 GtkWidget* menu_item,
348 GtkWidget* menu,
349 bool connect_to_activate) {
350 SetMenuItemID(menu_item, index);
351
352 // Native menu items do their own thing, so only selectively listen for the
353 // activate signal.
354 if (connect_to_activate) {
355 g_signal_connect(menu_item, "activate",
356 G_CALLBACK(OnMenuItemActivatedThunk), this);
357 }
358
359 // AppendMenuItemToMenu is used both internally when we control menu creation
360 // from a model (where the model can choose to hide certain menu items), and
361 // with immediate commands which don't provide the option.
362 if (model) {
363 if (model->IsVisibleAt(index))
364 gtk_widget_show(menu_item);
365 } else {
366 gtk_widget_show(menu_item);
367 }
368 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
369 return menu_item;
370 }
371
PopupForWidget(GtkWidget * widget,int button,guint32 event_time)372 void MenuGtk::PopupForWidget(GtkWidget* widget, int button,
373 guint32 event_time) {
374 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
375 WidgetMenuPositionFunc,
376 widget,
377 button, event_time);
378 }
379
PopupAsContext(const gfx::Point & point,guint32 event_time)380 void MenuGtk::PopupAsContext(const gfx::Point& point, guint32 event_time) {
381 // gtk_menu_popup doesn't like the "const" qualifier on point.
382 gfx::Point nonconst_point(point);
383 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
384 PointMenuPositionFunc, &nonconst_point,
385 3, event_time);
386 }
387
PopupAsContextForStatusIcon(guint32 event_time,guint32 button,GtkStatusIcon * icon)388 void MenuGtk::PopupAsContextForStatusIcon(guint32 event_time, guint32 button,
389 GtkStatusIcon* icon) {
390 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, gtk_status_icon_position_menu,
391 icon, button, event_time);
392 }
393
PopupAsFromKeyEvent(GtkWidget * widget)394 void MenuGtk::PopupAsFromKeyEvent(GtkWidget* widget) {
395 PopupForWidget(widget, 0, gtk_get_current_event_time());
396 gtk_menu_shell_select_first(GTK_MENU_SHELL(menu_), FALSE);
397 }
398
Cancel()399 void MenuGtk::Cancel() {
400 gtk_menu_popdown(GTK_MENU(menu_));
401 }
402
UpdateMenu()403 void MenuGtk::UpdateMenu() {
404 gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this);
405 }
406
BuildMenuItemWithImage(const std::string & label,GtkWidget * image)407 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
408 GtkWidget* image) {
409 GtkWidget* menu_item =
410 gtk_image_menu_item_new_with_mnemonic(label.c_str());
411 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
412 return menu_item;
413 }
414
BuildMenuItemWithImage(const std::string & label,const SkBitmap & icon)415 GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
416 const SkBitmap& icon) {
417 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
418 GtkWidget* menu_item = BuildMenuItemWithImage(label,
419 gtk_image_new_from_pixbuf(pixbuf));
420 g_object_unref(pixbuf);
421 return menu_item;
422 }
423
BuildMenuItemWithLabel(const std::string & label,int command_id)424 GtkWidget* MenuGtk::BuildMenuItemWithLabel(const std::string& label,
425 int command_id) {
426 GtkWidget* img =
427 delegate_ ? delegate_->GetImageForCommandId(command_id) :
428 MenuGtk::Delegate::GetDefaultImageForCommandId(command_id);
429 return img ? BuildMenuItemWithImage(label, img) :
430 gtk_menu_item_new_with_mnemonic(label.c_str());
431 }
432
BuildMenuFromModel()433 void MenuGtk::BuildMenuFromModel() {
434 BuildSubmenuFromModel(model_, menu_);
435 }
436
BuildSubmenuFromModel(ui::MenuModel * model,GtkWidget * menu)437 void MenuGtk::BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu) {
438 std::map<int, GtkWidget*> radio_groups;
439 GtkWidget* menu_item = NULL;
440 for (int i = 0; i < model->GetItemCount(); ++i) {
441 SkBitmap icon;
442 std::string label =
443 gfx::ConvertAcceleratorsFromWindowsStyle(
444 UTF16ToUTF8(model->GetLabelAt(i)));
445 bool connect_to_activate = true;
446
447 switch (model->GetTypeAt(i)) {
448 case ui::MenuModel::TYPE_SEPARATOR:
449 menu_item = gtk_separator_menu_item_new();
450 break;
451
452 case ui::MenuModel::TYPE_CHECK:
453 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
454 break;
455
456 case ui::MenuModel::TYPE_RADIO: {
457 std::map<int, GtkWidget*>::iterator iter =
458 radio_groups.find(model->GetGroupIdAt(i));
459
460 if (iter == radio_groups.end()) {
461 menu_item = gtk_radio_menu_item_new_with_mnemonic(
462 NULL, label.c_str());
463 radio_groups[model->GetGroupIdAt(i)] = menu_item;
464 } else {
465 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
466 GTK_RADIO_MENU_ITEM(iter->second), label.c_str());
467 }
468 break;
469 }
470 case ui::MenuModel::TYPE_BUTTON_ITEM: {
471 ui::ButtonMenuItemModel* button_menu_item_model =
472 model->GetButtonMenuItemAt(i);
473 menu_item = BuildButtonMenuItem(button_menu_item_model, menu);
474 connect_to_activate = false;
475 break;
476 }
477 case ui::MenuModel::TYPE_SUBMENU:
478 case ui::MenuModel::TYPE_COMMAND: {
479 int command_id = model->GetCommandIdAt(i);
480 if (model->GetIconAt(i, &icon))
481 menu_item = BuildMenuItemWithImage(label, icon);
482 else
483 menu_item = BuildMenuItemWithLabel(label, command_id);
484 if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
485 GTK_IS_IMAGE_MENU_ITEM(menu_item))
486 gtk_util::SetAlwaysShowImage(menu_item);
487 break;
488 }
489
490 default:
491 NOTREACHED();
492 }
493
494 if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
495 GtkWidget* submenu = gtk_menu_new();
496 BuildSubmenuFromModel(model->GetSubmenuModelAt(i), submenu);
497 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
498 }
499
500 ui::AcceleratorGtk accelerator;
501 if (model->GetAcceleratorAt(i, &accelerator)) {
502 gtk_widget_add_accelerator(menu_item,
503 "activate",
504 dummy_accel_group_,
505 accelerator.GetGdkKeyCode(),
506 accelerator.gdk_modifier_type(),
507 GTK_ACCEL_VISIBLE);
508 }
509
510 g_object_set_data(G_OBJECT(menu_item), "model", model);
511 AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate);
512
513 menu_item = NULL;
514 }
515 }
516
BuildButtonMenuItem(ui::ButtonMenuItemModel * model,GtkWidget * menu)517 GtkWidget* MenuGtk::BuildButtonMenuItem(ui::ButtonMenuItemModel* model,
518 GtkWidget* menu) {
519 GtkWidget* menu_item = gtk_custom_menu_item_new(
520 gfx::RemoveWindowsStyleAccelerators(UTF16ToUTF8(model->label())).c_str());
521
522 // Set up the callback to the model for when it is clicked.
523 g_object_set_data(G_OBJECT(menu_item), "button-model", model);
524 g_signal_connect(menu_item, "button-pushed",
525 G_CALLBACK(OnMenuButtonPressedThunk), this);
526 g_signal_connect(menu_item, "try-button-pushed",
527 G_CALLBACK(OnMenuTryButtonPressedThunk), this);
528
529 GtkSizeGroup* group = NULL;
530 for (int i = 0; i < model->GetItemCount(); ++i) {
531 GtkWidget* button = NULL;
532
533 switch (model->GetTypeAt(i)) {
534 case ui::ButtonMenuItemModel::TYPE_SPACE: {
535 gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item));
536 break;
537 }
538 case ui::ButtonMenuItemModel::TYPE_BUTTON: {
539 button = gtk_custom_menu_item_add_button(
540 GTK_CUSTOM_MENU_ITEM(menu_item),
541 model->GetCommandIdAt(i));
542
543 int icon_idr;
544 if (model->GetIconAt(i, &icon_idr)) {
545 SetupImageIcon(button, menu, icon_idr, delegate_);
546 } else {
547 gtk_button_set_label(
548 GTK_BUTTON(button),
549 gfx::RemoveWindowsStyleAccelerators(
550 UTF16ToUTF8(model->GetLabelAt(i))).c_str());
551 }
552
553 SetupButtonShowHandler(button, model, i);
554 break;
555 }
556 case ui::ButtonMenuItemModel::TYPE_BUTTON_LABEL: {
557 button = gtk_custom_menu_item_add_button_label(
558 GTK_CUSTOM_MENU_ITEM(menu_item),
559 model->GetCommandIdAt(i));
560 gtk_button_set_label(
561 GTK_BUTTON(button),
562 gfx::RemoveWindowsStyleAccelerators(
563 UTF16ToUTF8(model->GetLabelAt(i))).c_str());
564 SetupButtonShowHandler(button, model, i);
565 break;
566 }
567 }
568
569 if (button && model->PartOfGroup(i)) {
570 if (!group)
571 group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
572
573 gtk_size_group_add_widget(group, button);
574 }
575 }
576
577 if (group) {
578 g_object_unref(group);
579 }
580
581 return menu_item;
582 }
583
OnMenuItemActivated(GtkWidget * menuitem)584 void MenuGtk::OnMenuItemActivated(GtkWidget* menuitem) {
585 if (block_activation_)
586 return;
587
588 // We receive activation messages when highlighting a menu that has a
589 // submenu. Ignore them.
590 if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menuitem)))
591 return;
592
593 // The activate signal is sent to radio items as they get deselected;
594 // ignore it in this case.
595 if (GTK_IS_RADIO_MENU_ITEM(menuitem) &&
596 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
597 return;
598 }
599
600 int id;
601 if (!GetMenuItemID(menuitem, &id))
602 return;
603
604 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menuitem));
605
606 // The menu item can still be activated by hotkeys even if it is disabled.
607 if (model->IsEnabledAt(id))
608 ExecuteCommand(model, id);
609 }
610
OnMenuButtonPressed(GtkWidget * menu_item,int command_id)611 void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) {
612 ui::ButtonMenuItemModel* model =
613 reinterpret_cast<ui::ButtonMenuItemModel*>(
614 g_object_get_data(G_OBJECT(menu_item), "button-model"));
615 if (model && model->IsCommandIdEnabled(command_id)) {
616 if (delegate_)
617 delegate_->CommandWillBeExecuted();
618
619 model->ActivatedCommand(command_id);
620 }
621 }
622
OnMenuTryButtonPressed(GtkWidget * menu_item,int command_id)623 gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item,
624 int command_id) {
625 gboolean pressed = FALSE;
626 ui::ButtonMenuItemModel* model =
627 reinterpret_cast<ui::ButtonMenuItemModel*>(
628 g_object_get_data(G_OBJECT(menu_item), "button-model"));
629 if (model &&
630 model->IsCommandIdEnabled(command_id) &&
631 !model->DoesCommandIdDismissMenu(command_id)) {
632 if (delegate_)
633 delegate_->CommandWillBeExecuted();
634
635 model->ActivatedCommand(command_id);
636 pressed = TRUE;
637 }
638
639 return pressed;
640 }
641
642 // static
WidgetMenuPositionFunc(GtkMenu * menu,int * x,int * y,gboolean * push_in,void * void_widget)643 void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu,
644 int* x,
645 int* y,
646 gboolean* push_in,
647 void* void_widget) {
648 GtkWidget* widget = GTK_WIDGET(void_widget);
649 GtkRequisition menu_req;
650
651 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
652
653 gdk_window_get_origin(widget->window, x, y);
654 GdkScreen *screen = gtk_widget_get_screen(widget);
655 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
656
657 GdkRectangle screen_rect;
658 gdk_screen_get_monitor_geometry(screen, monitor,
659 &screen_rect);
660
661 if (GTK_WIDGET_NO_WINDOW(widget)) {
662 *x += widget->allocation.x;
663 *y += widget->allocation.y;
664 }
665 *y += widget->allocation.height;
666
667 bool start_align =
668 !!g_object_get_data(G_OBJECT(widget), "left-align-popup");
669 if (base::i18n::IsRTL())
670 start_align = !start_align;
671
672 if (!start_align)
673 *x += widget->allocation.width - menu_req.width;
674
675 *y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y);
676
677 *push_in = FALSE;
678 }
679
680 // static
PointMenuPositionFunc(GtkMenu * menu,int * x,int * y,gboolean * push_in,gpointer userdata)681 void MenuGtk::PointMenuPositionFunc(GtkMenu* menu,
682 int* x,
683 int* y,
684 gboolean* push_in,
685 gpointer userdata) {
686 *push_in = TRUE;
687
688 gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata);
689 *x = point->x();
690 *y = point->y();
691
692 GtkRequisition menu_req;
693 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
694 GdkScreen* screen;
695 gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL);
696 gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
697
698 GdkRectangle screen_rect;
699 gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);
700
701 *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y);
702 }
703
ExecuteCommand(ui::MenuModel * model,int id)704 void MenuGtk::ExecuteCommand(ui::MenuModel* model, int id) {
705 if (delegate_)
706 delegate_->CommandWillBeExecuted();
707
708 GdkEvent* event = gtk_get_current_event();
709 if (event && event->type == GDK_BUTTON_RELEASE) {
710 model->ActivatedAtWithDisposition(
711 id, event_utils::DispositionFromEventFlags(event->button.state));
712 } else {
713 model->ActivatedAt(id);
714 }
715
716 if (event)
717 gdk_event_free(event);
718 }
719
OnMenuShow(GtkWidget * widget)720 void MenuGtk::OnMenuShow(GtkWidget* widget) {
721 model_->MenuWillShow();
722 MessageLoop::current()->PostTask(FROM_HERE,
723 factory_.NewRunnableMethod(&MenuGtk::UpdateMenu));
724 }
725
OnMenuHidden(GtkWidget * widget)726 void MenuGtk::OnMenuHidden(GtkWidget* widget) {
727 if (delegate_)
728 delegate_->StoppedShowing();
729 model_->MenuClosed();
730 }
731
732 // static
SetButtonItemInfo(GtkWidget * button,gpointer userdata)733 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) {
734 ui::ButtonMenuItemModel* model =
735 reinterpret_cast<ui::ButtonMenuItemModel*>(
736 g_object_get_data(G_OBJECT(button), "button-model"));
737 int index = GPOINTER_TO_INT(g_object_get_data(
738 G_OBJECT(button), "button-model-id"));
739
740 if (model->IsItemDynamicAt(index)) {
741 std::string label =
742 gfx::ConvertAcceleratorsFromWindowsStyle(
743 UTF16ToUTF8(model->GetLabelAt(index)));
744 gtk_button_set_label(GTK_BUTTON(button), label.c_str());
745 }
746
747 gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index));
748 }
749
750 // static
SetMenuItemInfo(GtkWidget * widget,gpointer userdata)751 void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) {
752 if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) {
753 // We need to explicitly handle this case because otherwise we'll ask the
754 // menu delegate about something with an invalid id.
755 return;
756 }
757
758 int id;
759 if (!GetMenuItemID(widget, &id))
760 return;
761
762 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget));
763 if (!model) {
764 // If we're not providing the sub menu, then there's no model. For
765 // example, the IME submenu doesn't have a model.
766 return;
767 }
768
769 if (GTK_IS_CHECK_MENU_ITEM(widget)) {
770 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget);
771
772 // gtk_check_menu_item_set_active() will send the activate signal. Touching
773 // the underlying "active" property will also call the "activate" handler
774 // for this menu item. So we prevent the "activate" handler from
775 // being called while we set the checkbox.
776 // Why not use one of the glib signal-blocking functions? Because when we
777 // toggle a radio button, it will deactivate one of the other radio buttons,
778 // which we don't have a pointer to.
779 // Wny not make this a member variable? Because "menu" is a pointer to the
780 // root of the MenuGtk and we want to disable *all* MenuGtks, including
781 // submenus.
782 block_activation_ = true;
783 gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id));
784 block_activation_ = false;
785 }
786
787 if (GTK_IS_CUSTOM_MENU_ITEM(widget)) {
788 // Iterate across all the buttons to update their visible properties.
789 gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget),
790 SetButtonItemInfo,
791 userdata);
792 }
793
794 if (GTK_IS_MENU_ITEM(widget)) {
795 gtk_widget_set_sensitive(widget, model->IsEnabledAt(id));
796
797 if (model->IsVisibleAt(id)) {
798 // Update the menu item label if it is dynamic.
799 if (model->IsItemDynamicAt(id)) {
800 std::string label =
801 gfx::ConvertAcceleratorsFromWindowsStyle(
802 UTF16ToUTF8(model->GetLabelAt(id)));
803
804 #if GTK_CHECK_VERSION(2, 16, 0)
805 gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str());
806 #else
807 gtk_label_set_label(GTK_LABEL(GTK_BIN(widget)->child), label.c_str());
808 #endif
809 if (GTK_IS_IMAGE_MENU_ITEM(widget)) {
810 SkBitmap icon;
811 if (model->GetIconAt(id, &icon)) {
812 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
813 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget),
814 gtk_image_new_from_pixbuf(pixbuf));
815 g_object_unref(pixbuf);
816 } else {
817 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL);
818 }
819 }
820 }
821
822 gtk_widget_show(widget);
823 } else {
824 gtk_widget_hide(widget);
825 }
826
827 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget));
828 if (submenu) {
829 gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo,
830 userdata);
831 }
832 }
833 }
834