menu_gtk.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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
25bool MenuGtk::block_activation_ = false;
26
27namespace {
28
29// Sets the ID of a menu item.
30void 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.
40bool 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
50ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) {
51  return reinterpret_cast<ui::MenuModel*>(
52      g_object_get_data(G_OBJECT(menu_item), "model"));
53}
54
55void 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
64void 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
78void 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.
95int 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
117GtkWidget* 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
273GtkWidget* MenuGtk::Delegate::GetImageForCommandId(int command_id) const {
274  return GetDefaultImageForCommandId(command_id);
275}
276
277MenuGtk::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
290MenuGtk::~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
300void 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
307GtkWidget* 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
314GtkWidget* 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
322GtkWidget* 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
330GtkWidget* 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
337GtkWidget* 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
345GtkWidget* 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
372void 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
380void 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
388void 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
394void 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
399void MenuGtk::Cancel() {
400  gtk_menu_popdown(GTK_MENU(menu_));
401}
402
403void MenuGtk::UpdateMenu() {
404  gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this);
405}
406
407GtkWidget* 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
415GtkWidget* 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
424GtkWidget* 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
433void MenuGtk::BuildMenuFromModel() {
434  BuildSubmenuFromModel(model_, menu_);
435}
436
437void 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
517GtkWidget* 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
584void 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
611void 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
623gboolean 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
643void 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
681void 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
704void 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
720void MenuGtk::OnMenuShow(GtkWidget* widget) {
721  model_->MenuWillShow();
722  MessageLoop::current()->PostTask(FROM_HERE,
723      factory_.NewRunnableMethod(&MenuGtk::UpdateMenu));
724}
725
726void MenuGtk::OnMenuHidden(GtkWidget* widget) {
727  if (delegate_)
728    delegate_->StoppedShowing();
729  model_->MenuClosed();
730}
731
732// static
733void 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
751void 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