172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file.
4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/menu_bar_helper.h"
6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <algorithm>
8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/logging.h"
1072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/gtk_util.h"
1172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/base/gtk/gtk_signal_registrar.h"
12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace {
14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Recursively find all the GtkMenus that are attached to menu item |child|
16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// and add them to |data|, which is a vector of GtkWidgets.
17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid PopulateSubmenus(GtkWidget* child, gpointer data) {
18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  std::vector<GtkWidget*>* submenus =
19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      static_cast<std::vector<GtkWidget*>*>(data);
20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(child));
21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (submenu) {
22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    submenus->push_back(submenu);
23c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_container_foreach(GTK_CONTAINER(submenu), PopulateSubmenus, submenus);
24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Is the cursor over |menu| or one of its parent menus?
28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool MotionIsOverMenu(GtkWidget* menu, GdkEventMotion* motion) {
29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (motion->x >= 0 && motion->y >= 0 &&
30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      motion->x < menu->allocation.width &&
31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      motion->y < menu->allocation.height) {
32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return true;
33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  while (menu) {
36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* menu_item = gtk_menu_get_attach_widget(GTK_MENU(menu));
37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (!menu_item)
38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return false;
39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* parent = gtk_widget_get_parent(menu_item);
40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (gtk_util::WidgetContainsCursor(parent))
42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return true;
43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    menu = parent;
44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return false;
47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}  // namespace
50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
51c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochMenuBarHelper::MenuBarHelper(Delegate* delegate)
52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    : button_showing_menu_(NULL),
53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      showing_menu_(NULL),
54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      delegate_(delegate) {
55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(delegate_);
56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
58c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochMenuBarHelper::~MenuBarHelper() {
59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::Add(GtkWidget* button) {
62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  buttons_.push_back(button);
63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::Remove(GtkWidget* button) {
66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  std::vector<GtkWidget*>::iterator iter =
67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      find(buttons_.begin(), buttons_.end(), button);
68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (iter == buttons_.end()) {
69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    NOTREACHED();
70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return;
71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  buttons_.erase(iter);
73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::Clear() {
76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  buttons_.clear();
77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::MenuStartedShowing(GtkWidget* button, GtkWidget* menu) {
80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(GTK_IS_MENU(menu));
81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  button_showing_menu_ = button;
82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  showing_menu_ = menu;
83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
8472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  signal_handlers_.reset(new ui::GtkSignalRegistrar());
85201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  signal_handlers_->Connect(menu, "destroy",
86201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch                            G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this);
87201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  signal_handlers_->Connect(menu, "hide",
88201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch                            G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this);
89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  signal_handlers_->Connect(menu, "motion-notify-event",
90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                            G_CALLBACK(OnMenuMotionNotifyThunk), this);
91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  signal_handlers_->Connect(menu, "move-current",
92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                            G_CALLBACK(OnMenuMoveCurrentThunk), this);
93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_container_foreach(GTK_CONTAINER(menu), PopulateSubmenus, &submenus_);
94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  for (size_t i = 0; i < submenus_.size(); ++i) {
96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    signal_handlers_->Connect(submenus_[i], "motion-notify-event",
97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                              G_CALLBACK(OnMenuMotionNotifyThunk), this);
98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgboolean MenuBarHelper::OnMenuMotionNotify(GtkWidget* menu,
102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                           GdkEventMotion* motion) {
103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Don't do anything if pointer is in the menu.
104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (MotionIsOverMenu(menu, motion))
105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return FALSE;
106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (buttons_.empty())
107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return FALSE;
108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gint x = 0;
110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gint y = 0;
111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* last_button = NULL;
112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  for (size_t i = 0; i < buttons_.size(); ++i) {
114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* button = buttons_[i];
115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // Figure out coordinates relative to this button. Avoid using
116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // gtk_widget_get_pointer() unnecessarily.
117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (i == 0) {
118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // We have to make this call because the menu is a popup window, so it
119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // doesn't share a toplevel with the buttons and we can't just use
120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // gtk_widget_translate_coordinates().
121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      gtk_widget_get_pointer(buttons_[0], &x, &y);
122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    } else {
123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      gint last_x = x;
124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      gint last_y = y;
125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (!gtk_widget_translate_coordinates(
126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          last_button, button, last_x, last_y, &x, &y)) {
1273345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick        // |button| may not be realized.
1283345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick        continue;
129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    last_button = button;
133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (x >= 0 && y >= 0 && x < button->allocation.width &&
135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        y < button->allocation.height) {
136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (button != button_showing_menu_)
137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        delegate_->PopupForButton(button);
138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return TRUE;
139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return FALSE;
143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
145201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdochvoid MenuBarHelper::OnMenuHiddenOrDestroyed(GtkWidget* menu) {
146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK_EQ(showing_menu_, menu);
147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  signal_handlers_.reset();
149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  showing_menu_ = NULL;
150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  button_showing_menu_ = NULL;
151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  submenus_.clear();
152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::OnMenuMoveCurrent(GtkWidget* menu,
155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                      GtkMenuDirectionType dir) {
156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // The menu directions are triggered by the arrow keys as follows
157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //
158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //   PARENT   left
159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //   CHILD    right
160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //   NEXT     down
161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //   PREV     up
162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //
163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // We only care about left and right. Note that for RTL, they are swapped.
164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  switch (dir) {
165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    case GTK_MENU_DIR_CHILD: {
166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      GtkWidget* active_item = GTK_MENU_SHELL(menu)->active_menu_item;
167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // The move is going to open a submenu; don't override default behavior.
168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (active_item && gtk_menu_item_get_submenu(GTK_MENU_ITEM(active_item)))
169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return;
170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // Fall through.
171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    case GTK_MENU_DIR_PARENT: {
173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      delegate_->PopupForButtonNextTo(button_showing_menu_, dir);
174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      break;
175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    default:
177c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return;
178c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
179c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // This signal doesn't have a return value; we have to manually stop its
181c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // propagation.
182c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  g_signal_stop_emission_by_name(menu, "move-current");
183c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
184