gtk: Add menu_gtk from Chromium.
authorCheng Zhao <zcbenz@gmail.com>
Fri, 14 Mar 2014 12:42:39 +0000 (20:42 +0800)
committerCheng Zhao <zcbenz@gmail.com>
Fri, 14 Mar 2014 12:42:39 +0000 (20:42 +0800)
atom.gyp
browser/ui/gtk/gtk_util.cc
browser/ui/gtk/gtk_util.h
browser/ui/gtk/menu_gtk.cc [new file with mode: 0644]
browser/ui/gtk/menu_gtk.h [new file with mode: 0644]

index 99f7a79..b680fbd 100644 (file)
--- a/atom.gyp
+++ b/atom.gyp
       'browser/ui/gtk/gtk_window_util.h',
       'browser/ui/gtk/event_utils.cc',
       'browser/ui/gtk/event_utils.h',
+      'browser/ui/gtk/menu_gtk.cc',
+      'browser/ui/gtk/menu_gtk.h',
       'browser/ui/message_box.h',
       'browser/ui/message_box_gtk.cc',
       'browser/ui/message_box_mac.mm',
index efb1b34..8f6f7c8 100644 (file)
@@ -59,6 +59,11 @@ GtkWidget* CreateBoldLabel(const std::string& text) {
   return LeftAlignMisc(label);
 }
 
+void SetAlwaysShowImage(GtkWidget* image_menu_item) {
+  gtk_image_menu_item_set_always_show_image(
+      GTK_IMAGE_MENU_ITEM(image_menu_item), TRUE);
+}
+
 bool IsWidgetAncestryVisible(GtkWidget* widget) {
   GtkWidget* parent = widget;
   while (parent && gtk_widget_get_visible(parent))
index 38961fc..cc16b6f 100644 (file)
@@ -16,6 +16,11 @@ GtkWidget* LeftAlignMisc(GtkWidget* misc);
 // Create a left-aligned label with the given text in bold.
 GtkWidget* CreateBoldLabel(const std::string& text);
 
+// Show the image for the given menu item, even if the user's default is to not
+// show images. Only to be used for favicons or other menus where the image is
+// crucial to its functionality.
+void SetAlwaysShowImage(GtkWidget* image_menu_item);
+
 // Checks whether a widget is actually visible, i.e. whether it and all its
 // ancestors up to its toplevel are visible.
 bool IsWidgetAncestryVisible(GtkWidget* widget);
diff --git a/browser/ui/gtk/menu_gtk.cc b/browser/ui/gtk/menu_gtk.cc
new file mode 100644 (file)
index 0000000..d4c8b7f
--- /dev/null
@@ -0,0 +1,873 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "browser/ui/gtk/menu_gtk.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "browser/ui/gtk/event_utils.h"
+#include "browser/ui/gtk/gtk_custom_menu.h"
+#include "browser/ui/gtk/gtk_custom_menu_item.h"
+#include "browser/ui/gtk/gtk_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
+#include "ui/base/accelerators/platform_accelerator_gtk.h"
+#include "ui/base/models/button_menu_item_model.h"
+#include "ui/base/models/menu_model.h"
+#include "ui/base/window_open_disposition.h"
+#include "ui/gfx/gtk_util.h"
+#include "ui/gfx/image/image.h"
+
+bool MenuGtk::block_activation_ = false;
+
+namespace {
+
+// Sets the ID of a menu item.
+void SetMenuItemID(GtkWidget* menu_item, int menu_id) {
+  DCHECK_GE(menu_id, 0);
+
+  // Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
+  g_object_set_data(G_OBJECT(menu_item), "menu-id",
+                    GINT_TO_POINTER(menu_id + 1));
+}
+
+// Gets the ID of a menu item.
+// Returns true if the menu item has an ID.
+bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) {
+  gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id");
+  if (id_ptr != NULL) {
+    *menu_id = GPOINTER_TO_INT(id_ptr) - 1;
+    return true;
+  }
+
+  return false;
+}
+
+ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) {
+  return reinterpret_cast<ui::MenuModel*>(
+      g_object_get_data(G_OBJECT(menu_item), "model"));
+}
+
+void SetUpButtonShowHandler(GtkWidget* button,
+                            ui::ButtonMenuItemModel* model,
+                            int index) {
+  g_object_set_data(G_OBJECT(button), "button-model",
+                    model);
+  g_object_set_data(G_OBJECT(button), "button-model-id",
+                    GINT_TO_POINTER(index));
+}
+
+void OnSubmenuShowButtonImage(GtkWidget* widget, GtkButton* button) {
+  MenuGtk::Delegate* delegate = reinterpret_cast<MenuGtk::Delegate*>(
+      g_object_get_data(G_OBJECT(button), "menu-gtk-delegate"));
+  int icon_idr = GPOINTER_TO_INT(g_object_get_data(
+      G_OBJECT(button), "button-image-idr"));
+
+  GtkIconSet* icon_set = delegate->GetIconSetForId(icon_idr);
+  if (icon_set) {
+    gtk_button_set_image(
+        button, gtk_image_new_from_icon_set(icon_set,
+                                            GTK_ICON_SIZE_MENU));
+  }
+}
+
+void SetupImageIcon(GtkWidget* button,
+                    GtkWidget* menu,
+                    int icon_idr,
+                    MenuGtk::Delegate* menu_gtk_delegate) {
+  g_object_set_data(G_OBJECT(button), "button-image-idr",
+                    GINT_TO_POINTER(icon_idr));
+  g_object_set_data(G_OBJECT(button), "menu-gtk-delegate",
+                    menu_gtk_delegate);
+
+  g_signal_connect(menu, "show", G_CALLBACK(OnSubmenuShowButtonImage), button);
+}
+
+// Popup menus may get squished if they open up too close to the bottom of the
+// screen. This function takes the size of the screen, the size of the menu,
+// an optional widget, the Y position of the mouse click, and adjusts the popup
+// menu's Y position to make it fit if it's possible to do so.
+// Returns the new Y position of the popup menu.
+int CalculateMenuYPosition(const GdkRectangle* screen_rect,
+                           const GtkRequisition* menu_req,
+                           GtkWidget* widget, const int y) {
+  CHECK(screen_rect);
+  CHECK(menu_req);
+  // If the menu would run off the bottom of the screen, and there is enough
+  // screen space upwards to accommodate the menu, then pop upwards. If there
+  // is a widget, then also move the anchor point to the top of the widget
+  // rather than the bottom.
+  const int screen_top = screen_rect->y;
+  const int screen_bottom = screen_rect->y + screen_rect->height;
+  const int menu_bottom = y + menu_req->height;
+  int alternate_y = y - menu_req->height;
+  if (widget) {
+    GtkAllocation allocation;
+    gtk_widget_get_allocation(widget, &allocation);
+    alternate_y -= allocation.height;
+  }
+  if (menu_bottom >= screen_bottom && alternate_y >= screen_top)
+    return alternate_y;
+  return y;
+}
+
+}  // namespace
+
+bool MenuGtk::Delegate::AlwaysShowIconForCmd(int command_id) const {
+  return false;
+}
+
+GtkIconSet* MenuGtk::Delegate::GetIconSetForId(int idr) { return NULL; }
+
+GtkWidget* MenuGtk::Delegate::GetDefaultImageForLabel(
+    const std::string& label) {
+  const char* stock = NULL;
+  if (label == "New")
+    stock = GTK_STOCK_NEW;
+  else if (label == "Close")
+    stock = GTK_STOCK_CLOSE;
+  else if (label == "Save As")
+    stock = GTK_STOCK_SAVE_AS;
+  else if (label == "Save")
+    stock = GTK_STOCK_SAVE;
+  else if (label == "Copy")
+    stock = GTK_STOCK_COPY;
+  else if (label == "Cut")
+    stock = GTK_STOCK_CUT;
+  else if (label == "Paste")
+    stock = GTK_STOCK_PASTE;
+  else if (label == "Delete")
+    stock = GTK_STOCK_DELETE;
+  else if (label == "Undo")
+    stock = GTK_STOCK_UNDO;
+  else if (label == "Redo")
+    stock = GTK_STOCK_REDO;
+  else if (label == "Search" || label == "Find")
+    stock = GTK_STOCK_FIND;
+  else if (label == "Select All")
+    stock = GTK_STOCK_SELECT_ALL;
+  else if (label == "Clear")
+    stock = GTK_STOCK_SELECT_ALL;
+  else if (label == "Back")
+    stock = GTK_STOCK_GO_BACK;
+  else if (label == "Forward")
+    stock = GTK_STOCK_GO_FORWARD;
+  else if (label == "Reload" || label == "Refresh")
+    stock = GTK_STOCK_REFRESH;
+  else if (label == "Print")
+    stock = GTK_STOCK_PRINT;
+  else if (label == "About")
+    stock = GTK_STOCK_ABOUT;
+  else if (label == "Quit")
+    stock = GTK_STOCK_QUIT;
+  else if (label == "Help")
+    stock = GTK_STOCK_HELP;
+
+  return stock ? gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU) : NULL;
+}
+
+GtkWidget* MenuGtk::Delegate::GetImageForCommandId(int command_id) const {
+  return NULL;
+}
+
+MenuGtk::MenuGtk(MenuGtk::Delegate* delegate,
+                 ui::MenuModel* model)
+    : delegate_(delegate),
+      model_(model),
+      dummy_accel_group_(gtk_accel_group_new()),
+      menu_(gtk_custom_menu_new()),
+      weak_factory_(this) {
+  DCHECK(model);
+  g_object_ref_sink(menu_);
+  ConnectSignalHandlers();
+  BuildMenuFromModel();
+}
+
+MenuGtk::~MenuGtk() {
+  Cancel();
+
+  gtk_widget_destroy(menu_);
+  g_object_unref(menu_);
+
+  g_object_unref(dummy_accel_group_);
+}
+
+void MenuGtk::ConnectSignalHandlers() {
+  // We connect afterwards because OnMenuShow calls SetMenuItemInfo, which may
+  // take a long time or even start a nested message loop.
+  g_signal_connect(menu_, "show", G_CALLBACK(OnMenuShowThunk), this);
+  g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this);
+  GtkWidget* toplevel_window = gtk_widget_get_toplevel(menu_);
+  signal_.Connect(toplevel_window, "focus-out-event",
+                  G_CALLBACK(OnMenuFocusOutThunk), this);
+}
+
+GtkWidget* MenuGtk::AppendMenuItemWithLabel(int command_id,
+                                            const std::string& label) {
+  std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label);
+  GtkWidget* menu_item = BuildMenuItemWithLabel(converted_label, command_id);
+  return AppendMenuItem(command_id, menu_item);
+}
+
+GtkWidget* MenuGtk::AppendMenuItemWithIcon(int command_id,
+                                           const std::string& label,
+                                           const gfx::Image& icon) {
+  std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label);
+  GtkWidget* menu_item = BuildMenuItemWithImage(converted_label, icon);
+  return AppendMenuItem(command_id, menu_item);
+}
+
+GtkWidget* MenuGtk::AppendCheckMenuItemWithLabel(int command_id,
+                                                 const std::string& label) {
+  std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label);
+  GtkWidget* menu_item =
+      gtk_check_menu_item_new_with_mnemonic(converted_label.c_str());
+  return AppendMenuItem(command_id, menu_item);
+}
+
+GtkWidget* MenuGtk::AppendSeparator() {
+  GtkWidget* menu_item = gtk_separator_menu_item_new();
+  gtk_widget_show(menu_item);
+  gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item);
+  return menu_item;
+}
+
+GtkWidget* MenuGtk::InsertSeparator(int position) {
+  GtkWidget* menu_item = gtk_separator_menu_item_new();
+  gtk_widget_show(menu_item);
+  gtk_menu_shell_insert(GTK_MENU_SHELL(menu_), menu_item, position);
+  return menu_item;
+}
+
+GtkWidget* MenuGtk::AppendMenuItem(int command_id, GtkWidget* menu_item) {
+  if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
+      GTK_IS_IMAGE_MENU_ITEM(menu_item))
+    gtk_util::SetAlwaysShowImage(menu_item);
+
+  return AppendMenuItemToMenu(command_id, NULL, menu_item, menu_, true);
+}
+
+GtkWidget* MenuGtk::InsertMenuItem(int command_id, GtkWidget* menu_item,
+                                   int position) {
+  if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
+      GTK_IS_IMAGE_MENU_ITEM(menu_item))
+    gtk_util::SetAlwaysShowImage(menu_item);
+
+  return InsertMenuItemToMenu(command_id, NULL, menu_item, menu_, position,
+      true);
+}
+
+GtkWidget* MenuGtk::AppendMenuItemToMenu(int index,
+                                         ui::MenuModel* model,
+                                         GtkWidget* menu_item,
+                                         GtkWidget* menu,
+                                         bool connect_to_activate) {
+  int children_count = g_list_length(GTK_MENU_SHELL(menu)->children);
+  return InsertMenuItemToMenu(index, model, menu_item, menu,
+      children_count, connect_to_activate);
+}
+
+GtkWidget* MenuGtk::InsertMenuItemToMenu(int index,
+                                         ui::MenuModel* model,
+                                         GtkWidget* menu_item,
+                                         GtkWidget* menu,
+                                         int position,
+                                         bool connect_to_activate) {
+  SetMenuItemID(menu_item, index);
+
+  // Native menu items do their own thing, so only selectively listen for the
+  // activate signal.
+  if (connect_to_activate) {
+    g_signal_connect(menu_item, "activate",
+                     G_CALLBACK(OnMenuItemActivatedThunk), this);
+  }
+
+  // AppendMenuItemToMenu is used both internally when we control menu creation
+  // from a model (where the model can choose to hide certain menu items), and
+  // with immediate commands which don't provide the option.
+  if (model) {
+    if (model->IsVisibleAt(index))
+      gtk_widget_show(menu_item);
+  } else {
+    gtk_widget_show(menu_item);
+  }
+  gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menu_item, position);
+  return menu_item;
+}
+
+void MenuGtk::PopupForWidget(GtkWidget* widget, int button,
+                             guint32 event_time) {
+  gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
+                 WidgetMenuPositionFunc,
+                 widget,
+                 button, event_time);
+}
+
+void MenuGtk::PopupAsContext(const gfx::Point& point, guint32 event_time) {
+  // gtk_menu_popup doesn't like the "const" qualifier on point.
+  gfx::Point nonconst_point(point);
+  gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
+                 PointMenuPositionFunc, &nonconst_point,
+                 3, event_time);
+}
+
+void MenuGtk::PopupAsContextForStatusIcon(guint32 event_time, guint32 button,
+                                          GtkStatusIcon* icon) {
+  gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, gtk_status_icon_position_menu,
+                 icon, button, event_time);
+}
+
+void MenuGtk::PopupAsFromKeyEvent(GtkWidget* widget) {
+  PopupForWidget(widget, 0, gtk_get_current_event_time());
+  gtk_menu_shell_select_first(GTK_MENU_SHELL(menu_), FALSE);
+}
+
+void MenuGtk::Cancel() {
+  gtk_menu_popdown(GTK_MENU(menu_));
+}
+
+void MenuGtk::UpdateMenu() {
+  gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this);
+}
+
+GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
+                                           GtkWidget* image) {
+  GtkWidget* menu_item =
+      gtk_image_menu_item_new_with_mnemonic(label.c_str());
+  gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
+  return menu_item;
+}
+
+GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
+                                           const gfx::Image& icon) {
+  GtkWidget* menu_item = BuildMenuItemWithImage(label,
+      gtk_image_new_from_pixbuf(icon.ToGdkPixbuf()));
+  return menu_item;
+}
+
+GtkWidget* MenuGtk::BuildMenuItemWithLabel(const std::string& label,
+                                           int command_id) {
+  GtkWidget* img =
+      delegate_ ? delegate_->GetImageForCommandId(command_id) :
+                  MenuGtk::Delegate::GetDefaultImageForLabel(label);
+  return img ? BuildMenuItemWithImage(label, img) :
+               gtk_menu_item_new_with_mnemonic(label.c_str());
+}
+
+void MenuGtk::BuildMenuFromModel() {
+  BuildSubmenuFromModel(model_, menu_);
+}
+
+void MenuGtk::BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu) {
+  std::map<int, GtkWidget*> radio_groups;
+  GtkWidget* menu_item = NULL;
+  for (int i = 0; i < model->GetItemCount(); ++i) {
+    gfx::Image icon;
+    std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
+        base::UTF16ToUTF8(model->GetLabelAt(i)));
+    bool connect_to_activate = true;
+
+    switch (model->GetTypeAt(i)) {
+      case ui::MenuModel::TYPE_SEPARATOR:
+        menu_item = gtk_separator_menu_item_new();
+        break;
+
+      case ui::MenuModel::TYPE_CHECK:
+        menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
+        break;
+
+      case ui::MenuModel::TYPE_RADIO: {
+        std::map<int, GtkWidget*>::iterator iter =
+            radio_groups.find(model->GetGroupIdAt(i));
+
+        if (iter == radio_groups.end()) {
+          menu_item = gtk_radio_menu_item_new_with_mnemonic(
+              NULL, label.c_str());
+          radio_groups[model->GetGroupIdAt(i)] = menu_item;
+        } else {
+          menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
+              GTK_RADIO_MENU_ITEM(iter->second), label.c_str());
+        }
+        break;
+      }
+      case ui::MenuModel::TYPE_BUTTON_ITEM: {
+        ui::ButtonMenuItemModel* button_menu_item_model =
+            model->GetButtonMenuItemAt(i);
+        menu_item = BuildButtonMenuItem(button_menu_item_model, menu);
+        connect_to_activate = false;
+        break;
+      }
+      case ui::MenuModel::TYPE_SUBMENU:
+      case ui::MenuModel::TYPE_COMMAND: {
+        int command_id = model->GetCommandIdAt(i);
+        if (model->GetIconAt(i, &icon))
+          menu_item = BuildMenuItemWithImage(label, icon);
+        else
+          menu_item = BuildMenuItemWithLabel(label, command_id);
+        if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
+            GTK_IS_IMAGE_MENU_ITEM(menu_item)) {
+          gtk_util::SetAlwaysShowImage(menu_item);
+        }
+        break;
+      }
+
+      default:
+        NOTREACHED();
+    }
+
+    if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
+      GtkWidget* submenu = gtk_menu_new();
+      g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item);
+      ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i);
+      g_object_set_data(G_OBJECT(menu_item), "submenu-model", submenu_model);
+      gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
+      // We will populate the submenu on demand when shown.
+      g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this);
+      g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this);
+      connect_to_activate = false;
+    }
+
+    ui::Accelerator accelerator;
+    if (model->GetAcceleratorAt(i, &accelerator)) {
+      gtk_widget_add_accelerator(menu_item,
+                                 "activate",
+                                 dummy_accel_group_,
+                                 ui::GetGdkKeyCodeForAccelerator(accelerator),
+                                 ui::GetGdkModifierForAccelerator(accelerator),
+                                 GTK_ACCEL_VISIBLE);
+    }
+
+    g_object_set_data(G_OBJECT(menu_item), "model", model);
+    AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate);
+
+    menu_item = NULL;
+  }
+}
+
+GtkWidget* MenuGtk::BuildButtonMenuItem(ui::ButtonMenuItemModel* model,
+                                        GtkWidget* menu) {
+  GtkWidget* menu_item = gtk_custom_menu_item_new(
+      ui::RemoveWindowsStyleAccelerators(
+          base::UTF16ToUTF8(model->label())).c_str());
+
+  // Set up the callback to the model for when it is clicked.
+  g_object_set_data(G_OBJECT(menu_item), "button-model", model);
+  g_signal_connect(menu_item, "button-pushed",
+                   G_CALLBACK(OnMenuButtonPressedThunk), this);
+  g_signal_connect(menu_item, "try-button-pushed",
+                   G_CALLBACK(OnMenuTryButtonPressedThunk), this);
+
+  GtkSizeGroup* group = NULL;
+  for (int i = 0; i < model->GetItemCount(); ++i) {
+    GtkWidget* button = NULL;
+
+    switch (model->GetTypeAt(i)) {
+      case ui::ButtonMenuItemModel::TYPE_SPACE: {
+        gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item));
+        break;
+      }
+      case ui::ButtonMenuItemModel::TYPE_BUTTON: {
+        button = gtk_custom_menu_item_add_button(
+            GTK_CUSTOM_MENU_ITEM(menu_item),
+            model->GetCommandIdAt(i));
+
+        int icon_idr;
+        if (model->GetIconAt(i, &icon_idr)) {
+          SetupImageIcon(button, menu, icon_idr, delegate_);
+        } else {
+          gtk_button_set_label(
+              GTK_BUTTON(button),
+              ui::RemoveWindowsStyleAccelerators(
+                  base::UTF16ToUTF8(model->GetLabelAt(i))).c_str());
+        }
+
+        SetUpButtonShowHandler(button, model, i);
+        break;
+      }
+      case ui::ButtonMenuItemModel::TYPE_BUTTON_LABEL: {
+        button = gtk_custom_menu_item_add_button_label(
+            GTK_CUSTOM_MENU_ITEM(menu_item),
+            model->GetCommandIdAt(i));
+        gtk_button_set_label(
+            GTK_BUTTON(button),
+            ui::RemoveWindowsStyleAccelerators(
+                base::UTF16ToUTF8(model->GetLabelAt(i))).c_str());
+        SetUpButtonShowHandler(button, model, i);
+        break;
+      }
+    }
+
+    if (button && model->PartOfGroup(i)) {
+      if (!group)
+        group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+      gtk_size_group_add_widget(group, button);
+    }
+  }
+
+  if (group)
+    g_object_unref(group);
+
+  return menu_item;
+}
+
+void MenuGtk::OnMenuItemActivated(GtkWidget* menu_item) {
+  if (block_activation_)
+    return;
+
+  ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
+
+  if (!model) {
+    // There won't be a model for "native" submenus like the "Input Methods"
+    // context menu. We don't need to handle activation messages for submenus
+    // anyway, so we can just return here.
+    DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
+    return;
+  }
+
+  // The activate signal is sent to radio items as they get deselected;
+  // ignore it in this case.
+  if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
+      !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
+    return;
+  }
+
+  int id;
+  if (!GetMenuItemID(menu_item, &id))
+    return;
+
+  // The menu item can still be activated by hotkeys even if it is disabled.
+  if (model->IsEnabledAt(id))
+    ExecuteCommand(model, id);
+}
+
+void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) {
+  ui::ButtonMenuItemModel* model =
+      reinterpret_cast<ui::ButtonMenuItemModel*>(
+          g_object_get_data(G_OBJECT(menu_item), "button-model"));
+  if (model && model->IsCommandIdEnabled(command_id)) {
+    if (delegate_)
+      delegate_->CommandWillBeExecuted();
+
+    model->ActivatedCommand(command_id);
+  }
+}
+
+gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item,
+                                         int command_id) {
+  gboolean pressed = FALSE;
+  ui::ButtonMenuItemModel* model =
+      reinterpret_cast<ui::ButtonMenuItemModel*>(
+          g_object_get_data(G_OBJECT(menu_item), "button-model"));
+  if (model &&
+      model->IsCommandIdEnabled(command_id) &&
+      !model->DoesCommandIdDismissMenu(command_id)) {
+    if (delegate_)
+      delegate_->CommandWillBeExecuted();
+
+    model->ActivatedCommand(command_id);
+    pressed = TRUE;
+  }
+
+  return pressed;
+}
+
+// static
+void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu,
+                                     int* x,
+                                     int* y,
+                                     gboolean* push_in,
+                                     void* void_widget) {
+  GtkWidget* widget = GTK_WIDGET(void_widget);
+  GtkRequisition menu_req;
+
+  gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
+
+  gdk_window_get_origin(gtk_widget_get_window(widget), x, y);
+  GdkScreen *screen = gtk_widget_get_screen(widget);
+  gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
+
+  GdkRectangle screen_rect;
+  gdk_screen_get_monitor_geometry(screen, monitor,
+                                  &screen_rect);
+
+  GtkAllocation allocation;
+  gtk_widget_get_allocation(widget, &allocation);
+
+  if (!gtk_widget_get_has_window(widget)) {
+    *x += allocation.x;
+    *y += allocation.y;
+  }
+  *y += allocation.height;
+
+  bool start_align =
+    !!g_object_get_data(G_OBJECT(widget), "left-align-popup");
+  if (base::i18n::IsRTL())
+    start_align = !start_align;
+
+  if (!start_align)
+    *x += allocation.width - menu_req.width;
+
+  *y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y);
+
+  *push_in = FALSE;
+}
+
+// static
+void MenuGtk::PointMenuPositionFunc(GtkMenu* menu,
+                                    int* x,
+                                    int* y,
+                                    gboolean* push_in,
+                                    gpointer userdata) {
+  *push_in = TRUE;
+
+  gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata);
+  *x = point->x();
+  *y = point->y();
+
+  GtkRequisition menu_req;
+  gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
+  GdkScreen* screen;
+  gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL);
+  gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
+
+  GdkRectangle screen_rect;
+  gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);
+
+  *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y);
+}
+
+void MenuGtk::ExecuteCommand(ui::MenuModel* model, int id) {
+  if (delegate_)
+    delegate_->CommandWillBeExecuted();
+
+  GdkEvent* event = gtk_get_current_event();
+  int event_flags = 0;
+
+  if (event && event->type == GDK_BUTTON_RELEASE)
+    event_flags = event_utils::EventFlagsFromGdkState(event->button.state);
+  model->ActivatedAt(id, event_flags);
+
+  if (event)
+    gdk_event_free(event);
+}
+
+void MenuGtk::OnMenuShow(GtkWidget* widget) {
+  model_->MenuWillShow();
+  base::MessageLoop::current()->PostTask(
+      FROM_HERE, base::Bind(&MenuGtk::UpdateMenu, weak_factory_.GetWeakPtr()));
+}
+
+void MenuGtk::OnMenuHidden(GtkWidget* widget) {
+  if (delegate_)
+    delegate_->StoppedShowing();
+  model_->MenuClosed();
+}
+
+gboolean MenuGtk::OnMenuFocusOut(GtkWidget* widget, GdkEventFocus* event) {
+  gtk_widget_hide(menu_);
+  return TRUE;
+}
+
+void MenuGtk::OnSubMenuShow(GtkWidget* submenu) {
+  GtkWidget* menu_item = static_cast<GtkWidget*>(
+      g_object_get_data(G_OBJECT(submenu), "menu-item"));
+  // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974.
+  CHECK(menu_item);
+  // Notify the submenu model that the menu will be shown.
+  ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>(
+      g_object_get_data(G_OBJECT(menu_item), "submenu-model"));
+  // We're extra cautious here, and bail out if the submenu model is NULL. In
+  // some cases we clear it out from a parent menu; we shouldn't ever show the
+  // menu after that, but we play it safe since we're dealing with wacky
+  // injected libraries that toy with our menus. (See comments below.)
+  if (!submenu_model)
+    return;
+
+  // If the submenu is already built, then return right away. This means we
+  // recently showed this submenu, and have not yet processed the fact that it
+  // was hidden before being shown again.
+  if (g_object_get_data(G_OBJECT(submenu), "submenu-built"))
+    return;
+  g_object_set_data(G_OBJECT(submenu), "submenu-built", GINT_TO_POINTER(1));
+
+  submenu_model->MenuWillShow();
+
+  // Actually build the submenu and attach it to the parent menu item.
+  BuildSubmenuFromModel(submenu_model, submenu);
+  gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
+
+  // Update all the menu item info in the newly-generated menu.
+  gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo, this);
+}
+
+void MenuGtk::OnSubMenuHidden(GtkWidget* submenu) {
+  // Increase the reference count of the old submenu, and schedule it to be
+  // deleted later. We get this hide notification before we've processed menu
+  // activations, so if we were to delete the submenu now, we might lose the
+  // activation. This also lets us reuse the menu if it is shown again before
+  // it gets deleted; in that case, OnSubMenuHiddenCallback() just decrements
+  // the reference count again. Note that the delay is just an optimization; we
+  // could use PostTask() and this would still work correctly.
+  g_object_ref(G_OBJECT(submenu));
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu),
+      base::TimeDelta::FromSeconds(2));
+}
+
+namespace {
+
+// Remove all descendant submenu-model data pointers.
+void RemoveSubMenuModels(GtkWidget* menu_item, void* unused) {
+  if (!GTK_IS_MENU_ITEM(menu_item))
+    return;
+  g_object_steal_data(G_OBJECT(menu_item), "submenu-model");
+  GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
+  if (submenu)
+    gtk_container_foreach(GTK_CONTAINER(submenu), RemoveSubMenuModels, NULL);
+}
+
+}  // namespace
+
+// static
+void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) {
+  if (!gtk_widget_get_visible(submenu)) {
+    // Remove all the children of this menu, clearing out their submenu-model
+    // pointers in case they have pending calls to OnSubMenuHiddenCallback().
+    // (Normally that won't happen: we'd have hidden them first, and so they'd
+    // have already been deleted. But in some cases [e.g. on Ubuntu 12.04],
+    // GTK menu operations may be hooked to allow external applications to
+    // mirror the menu structure, and the hooks may show and hide menus in
+    // order to trigger exactly the kind of dynamic menu building we're doing.
+    // The result is that we see show and hide events in strange orders.)
+    GList* children = gtk_container_get_children(GTK_CONTAINER(submenu));
+    for (GList* child = children; child; child = g_list_next(child)) {
+      RemoveSubMenuModels(GTK_WIDGET(child->data), NULL);
+      gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(child->data));
+    }
+    g_list_free(children);
+
+    // Clear out the bit that says the menu is built.
+    // We'll rebuild it next time it is shown.
+    g_object_steal_data(G_OBJECT(submenu), "submenu-built");
+
+    // Notify the submenu model that the menu has been hidden. This may cause
+    // it to delete descendant submenu models, which is why we cleared those
+    // pointers out above.
+    GtkWidget* menu_item = static_cast<GtkWidget*>(
+        g_object_get_data(G_OBJECT(submenu), "menu-item"));
+    // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110.
+    CHECK(menu_item);
+    ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>(
+        g_object_get_data(G_OBJECT(menu_item), "submenu-model"));
+    if (submenu_model)
+      submenu_model->MenuClosed();
+  }
+
+  // Remove the reference we grabbed in OnSubMenuHidden() above.
+  g_object_unref(G_OBJECT(submenu));
+}
+
+// static
+void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) {
+  ui::ButtonMenuItemModel* model =
+      reinterpret_cast<ui::ButtonMenuItemModel*>(
+          g_object_get_data(G_OBJECT(button), "button-model"));
+  int index = GPOINTER_TO_INT(g_object_get_data(
+      G_OBJECT(button), "button-model-id"));
+
+  if (model->IsItemDynamicAt(index)) {
+    std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
+        base::UTF16ToUTF8(model->GetLabelAt(index)));
+    gtk_button_set_label(GTK_BUTTON(button), label.c_str());
+  }
+
+  gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index));
+}
+
+// static
+void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) {
+  if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) {
+    // We need to explicitly handle this case because otherwise we'll ask the
+    // menu delegate about something with an invalid id.
+    return;
+  }
+
+  int id;
+  if (!GetMenuItemID(widget, &id))
+    return;
+
+  ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget));
+  if (!model) {
+    // If we're not providing the sub menu, then there's no model.  For
+    // example, the IME submenu doesn't have a model.
+    return;
+  }
+
+  if (GTK_IS_CHECK_MENU_ITEM(widget)) {
+    GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget);
+
+    // gtk_check_menu_item_set_active() will send the activate signal. Touching
+    // the underlying "active" property will also call the "activate" handler
+    // for this menu item. So we prevent the "activate" handler from
+    // being called while we set the checkbox.
+    // Why not use one of the glib signal-blocking functions?  Because when we
+    // toggle a radio button, it will deactivate one of the other radio buttons,
+    // which we don't have a pointer to.
+    // Wny not make this a member variable?  Because "menu" is a pointer to the
+    // root of the MenuGtk and we want to disable *all* MenuGtks, including
+    // submenus.
+    block_activation_ = true;
+    gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id));
+    block_activation_ = false;
+  }
+
+  if (GTK_IS_CUSTOM_MENU_ITEM(widget)) {
+    // Iterate across all the buttons to update their visible properties.
+    gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget),
+                                        SetButtonItemInfo,
+                                        userdata);
+  }
+
+  if (GTK_IS_MENU_ITEM(widget)) {
+    gtk_widget_set_sensitive(widget, model->IsEnabledAt(id));
+
+    if (model->IsVisibleAt(id)) {
+      // Update the menu item label if it is dynamic.
+      if (model->IsItemDynamicAt(id)) {
+        std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
+            base::UTF16ToUTF8(model->GetLabelAt(id)));
+
+        gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str());
+        if (GTK_IS_IMAGE_MENU_ITEM(widget)) {
+          gfx::Image icon;
+          if (model->GetIconAt(id, &icon)) {
+            gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget),
+                                          gtk_image_new_from_pixbuf(
+                                              icon.ToGdkPixbuf()));
+          } else {
+            gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL);
+          }
+        }
+      }
+
+      gtk_widget_show(widget);
+    } else {
+      gtk_widget_hide(widget);
+    }
+
+    GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget));
+    if (submenu) {
+      gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo,
+                            userdata);
+    }
+  }
+}
diff --git a/browser/ui/gtk/menu_gtk.h b/browser/ui/gtk/menu_gtk.h
new file mode 100644 (file)
index 0000000..34efd85
--- /dev/null
@@ -0,0 +1,218 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_BROWSER_UI_GTK_MENU_GTK_H_
+#define ATOM_BROWSER_UI_GTK_MENU_GTK_H_
+
+#include <gtk/gtk.h>
+
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/gtk/gtk_signal_registrar.h"
+#include "ui/gfx/point.h"
+
+namespace gfx {
+class Image;
+}
+
+namespace ui {
+class ButtonMenuItemModel;
+class MenuModel;
+}
+
+class MenuGtk {
+ public:
+  // Delegate class that lets another class control the status of the menu.
+  class Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Called before a command is executed. This exists for the case where a
+    // model is handling the actual execution of commands, but the delegate
+    // still needs to know that some command got executed. This is called before
+    // and not after the command is executed because its execution may delete
+    // the menu and/or the delegate.
+    virtual void CommandWillBeExecuted() {}
+
+    // Called when the menu stops showing. This will be called before
+    // ExecuteCommand if the user clicks an item, but will also be called when
+    // the user clicks away from the menu.
+    virtual void StoppedShowing() {}
+
+    // Return true if we should override the "gtk-menu-images" system setting
+    // when showing image menu items for this menu.
+    virtual bool AlwaysShowIconForCmd(int command_id) const;
+
+    // Returns a tinted image used in button in a menu.
+    virtual GtkIconSet* GetIconSetForId(int idr);
+
+    // Returns an icon for the menu item, if available.
+    virtual GtkWidget* GetImageForCommandId(int command_id) const;
+
+    static GtkWidget* GetDefaultImageForLabel(const std::string& label);
+  };
+
+  MenuGtk(MenuGtk::Delegate* delegate, ui::MenuModel* model);
+  virtual ~MenuGtk();
+
+  // Initialize GTK signal handlers.
+  void ConnectSignalHandlers();
+
+  // These methods are used to build the menu dynamically. The return value
+  // is the new menu item.
+  GtkWidget* AppendMenuItemWithLabel(int command_id, const std::string& label);
+  GtkWidget* AppendMenuItemWithIcon(int command_id, const std::string& label,
+                                    const gfx::Image& icon);
+  GtkWidget* AppendCheckMenuItemWithLabel(int command_id,
+                                          const std::string& label);
+  GtkWidget* AppendSeparator();
+  GtkWidget* InsertSeparator(int position);
+  GtkWidget* AppendMenuItem(int command_id, GtkWidget* menu_item);
+  GtkWidget* InsertMenuItem(int command_id, GtkWidget* menu_item, int position);
+  GtkWidget* AppendMenuItemToMenu(int index,
+                                  ui::MenuModel* model,
+                                  GtkWidget* menu_item,
+                                  GtkWidget* menu,
+                                  bool connect_to_activate);
+  GtkWidget* InsertMenuItemToMenu(int index,
+                                  ui::MenuModel* model,
+                                  GtkWidget* menu_item,
+                                  GtkWidget* menu,
+                                  int position,
+                                  bool connect_to_activate);
+
+  // Displays the menu near a widget, as if the widget were a menu bar.
+  // Example: the wrench menu button.
+  // |button| is the mouse button that brought up the menu.
+  // |event_time| is the time from the GdkEvent.
+  void PopupForWidget(GtkWidget* widget, int button, guint32 event_time);
+
+  // Displays the menu as a context menu, i.e. at the cursor location.
+  // It is implicit that it was brought up using the right mouse button.
+  // |point| is the point where to put the menu.
+  // |event_time| is the time of the event that triggered the menu's display.
+  void PopupAsContext(const gfx::Point& point, guint32 event_time);
+
+  // Displays the menu as a context menu for the passed status icon.
+  void PopupAsContextForStatusIcon(guint32 event_time, guint32 button,
+                                   GtkStatusIcon* icon);
+
+  // Displays the menu following a keyboard event (such as selecting |widget|
+  // and pressing "enter").
+  void PopupAsFromKeyEvent(GtkWidget* widget);
+
+  // Closes the menu.
+  void Cancel();
+
+  // Repositions the menu to be right under the button.  Alignment is set as
+  // object data on |void_widget| with the tag "left_align".  If "left_align"
+  // is true, it aligns the left side of the menu with the left side of the
+  // button. Otherwise it aligns the right side of the menu with the right side
+  // of the button. Public since some menus have odd requirements that don't
+  // belong in a public class.
+  static void WidgetMenuPositionFunc(GtkMenu* menu,
+                                     int* x,
+                                     int* y,
+                                     gboolean* push_in,
+                                     void* void_widget);
+
+  // Positions the menu to appear at the gfx::Point represented by |userdata|.
+  static void PointMenuPositionFunc(GtkMenu* menu,
+                                    int* x,
+                                    int* y,
+                                    gboolean* push_in,
+                                    gpointer userdata);
+
+  GtkWidget* widget() const { return menu_; }
+
+  // Updates all the enabled/checked states and the dynamic labels.
+  void UpdateMenu();
+
+ private:
+  // Builds a GtkImageMenuItem.
+  GtkWidget* BuildMenuItemWithImage(const std::string& label,
+                                    const gfx::Image& icon);
+
+  GtkWidget* BuildMenuItemWithImage(const std::string& label,
+                                    GtkWidget* image);
+
+  GtkWidget* BuildMenuItemWithLabel(const std::string& label,
+                                    int command_id);
+
+  // A function that creates a GtkMenu from |model_|.
+  void BuildMenuFromModel();
+  // Implementation of the above; called recursively.
+  void BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu);
+  // Builds a menu item with buttons in it from the data in the model.
+  GtkWidget* BuildButtonMenuItem(ui::ButtonMenuItemModel* model,
+                                 GtkWidget* menu);
+
+  void ExecuteCommand(ui::MenuModel* model, int id);
+
+  // Callback for when a menu item is clicked.
+  CHROMEGTK_CALLBACK_0(MenuGtk, void, OnMenuItemActivated);
+
+  // Called when one of the buttons is pressed.
+  CHROMEGTK_CALLBACK_1(MenuGtk, void, OnMenuButtonPressed, int);
+
+  // Called to maybe activate a button if that button isn't supposed to dismiss
+  // the menu.
+  CHROMEGTK_CALLBACK_1(MenuGtk, gboolean, OnMenuTryButtonPressed, int);
+
+  // Updates all the menu items' state.
+  CHROMEGTK_CALLBACK_0(MenuGtk, void, OnMenuShow);
+
+  // Sets the activating widget back to a normal appearance.
+  CHROMEGTK_CALLBACK_0(MenuGtk, void, OnMenuHidden);
+
+  // Focus out event handler for the menu.
+  CHROMEGTK_CALLBACK_1(MenuGtk, gboolean, OnMenuFocusOut, GdkEventFocus*);
+
+  // Handles building dynamic submenus on demand when they are shown.
+  CHROMEGTK_CALLBACK_0(MenuGtk, void, OnSubMenuShow);
+
+  // Handles trearing down dynamic submenus when they have been closed.
+  CHROMEGTK_CALLBACK_0(MenuGtk, void, OnSubMenuHidden);
+
+  // Scheduled by OnSubMenuHidden() to avoid deleting submenus when hidden
+  // before pending activations within them are delivered.
+  static void OnSubMenuHiddenCallback(GtkWidget* submenu);
+
+  // Sets the enable/disabled state and dynamic labels on our menu items.
+  static void SetButtonItemInfo(GtkWidget* button, gpointer userdata);
+
+  // Sets the check mark, enabled/disabled state and dynamic labels on our menu
+  // items.
+  static void SetMenuItemInfo(GtkWidget* widget, void* raw_menu);
+
+  // Queries this object about the menu state.
+  MenuGtk::Delegate* delegate_;
+
+  // If non-NULL, the MenuModel that we use to populate and control the GTK
+  // menu (overriding the delegate as a controller).
+  ui::MenuModel* model_;
+
+  // For some menu items, we want to show the accelerator, but not actually
+  // explicitly handle it. To this end we connect those menu items' accelerators
+  // to this group, but don't attach this group to any top level window.
+  GtkAccelGroup* dummy_accel_group_;
+
+  // gtk_menu_popup() does not appear to take ownership of popup menus, so
+  // MenuGtk explicitly manages the lifetime of the menu.
+  GtkWidget* menu_;
+
+  // True when we should ignore "activate" signals.  Used to prevent
+  // menu items from getting activated when we are setting up the
+  // menu.
+  static bool block_activation_;
+
+  ui::GtkSignalRegistrar signal_;
+
+  base::WeakPtrFactory<MenuGtk> weak_factory_;
+};
+
+#endif  // ATOM_BROWSER_UI_GTK_MENU_GTK_H_