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/gtk_custom_menu.h"
6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/gtk_custom_menu_item.h"
8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
9c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochG_DEFINE_TYPE(GtkCustomMenu, gtk_custom_menu, GTK_TYPE_MENU)
10c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
11c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Stolen directly from gtkmenushell.c. I'd love to call the library version
12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// instead, but it's static and isn't exported. :(
13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic gint gtk_menu_shell_is_item(GtkMenuShell* menu_shell,
14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                   GtkWidget* child) {
15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget *parent;
16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  g_return_val_if_fail(GTK_IS_MENU_SHELL(menu_shell), FALSE);
18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  g_return_val_if_fail(child != NULL, FALSE);
19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  parent = child->parent;
21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  while (GTK_IS_MENU_SHELL(parent)) {
22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (parent == reinterpret_cast<GtkWidget*>(menu_shell))
23c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return TRUE;
24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    parent = GTK_MENU_SHELL(parent)->parent_menu_shell;
25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return FALSE;
28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Stolen directly from gtkmenushell.c. I'd love to call the library version
31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// instead, but it's static and isn't exported. :(
32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic GtkWidget* gtk_menu_shell_get_item(GtkMenuShell* menu_shell,
33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                          GdkEvent* event) {
34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* menu_item = gtk_get_event_widget(event);
35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  while (menu_item && !GTK_IS_MENU_ITEM(menu_item))
37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    menu_item = menu_item->parent;
38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (menu_item && gtk_menu_shell_is_item(menu_shell, menu_item))
40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return menu_item;
41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  else
42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return NULL;
43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// When processing a button event, abort processing if the cursor isn't in a
46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// clickable region.
47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic gboolean gtk_custom_menu_button_press(GtkWidget* widget,
48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                             GdkEventButton* event) {
49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* menu_item = gtk_menu_shell_get_item(
50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      GTK_MENU_SHELL(widget), reinterpret_cast<GdkEvent*>(event));
51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (!gtk_custom_menu_item_is_in_clickable_region(
53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            GTK_CUSTOM_MENU_ITEM(menu_item))) {
54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return TRUE;
55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
58c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      button_press_event(widget, event);
60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// When processing a button event, abort processing if the cursor isn't in a
633345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// clickable region. If it's in a button that doesn't dismiss the menu, fire
643345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// that event and abort having the normal GtkMenu code run.
65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic gboolean gtk_custom_menu_button_release(GtkWidget* widget,
66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                               GdkEventButton* event) {
67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* menu_item = gtk_menu_shell_get_item(
68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      GTK_MENU_SHELL(widget), reinterpret_cast<GdkEvent*>(event));
69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (!gtk_custom_menu_item_is_in_clickable_region(
71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            GTK_CUSTOM_MENU_ITEM(menu_item))) {
72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // Stop processing this event. This isn't a clickable region.
73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return TRUE;
74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
753345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
763345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    if (gtk_custom_menu_item_try_no_dismiss_command(
773345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick            GTK_CUSTOM_MENU_ITEM(menu_item))) {
783345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      return TRUE;
793345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    }
80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      button_release_event(widget, event);
84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Manually forward button press events to the menu item (and then do what we'd
87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// do normally).
88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic gboolean gtk_custom_menu_motion_notify(GtkWidget* widget,
89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                              GdkEventMotion* event) {
90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* menu_item = gtk_menu_shell_get_item(
91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      GTK_MENU_SHELL(widget), (GdkEvent*)event);
92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_custom_menu_item_receive_motion_event(GTK_CUSTOM_MENU_ITEM(menu_item),
94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                              event->x, event->y);
95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      motion_notify_event(widget, event);
99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic void gtk_custom_menu_move_current(GtkMenuShell* menu_shell,
102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                         GtkMenuDirectionType direction) {
103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // If the currently selected item is custom, we give it first chance to catch
104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // up/down events.
105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // TODO(erg): We are breaking a GSEAL by directly accessing this. We'll need
107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // to fix this by the time gtk3 comes out.
108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item;
109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    switch (direction) {
111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      case GTK_MENU_DIR_PREV:
112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      case GTK_MENU_DIR_NEXT:
113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (gtk_custom_menu_item_handle_move(GTK_CUSTOM_MENU_ITEM(menu_item),
114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                             direction))
115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          return;
116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        break;
117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      default:
118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        break;
119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GTK_MENU_SHELL_CLASS(gtk_custom_menu_parent_class)->
123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      move_current(menu_shell, direction);
124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // In the case of hitting PREV and transitioning to a custom menu, we want to
126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // make sure we're selecting the final item in the list, not the first one.
127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item;
128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_custom_menu_item_select_item_by_direction(
130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        GTK_CUSTOM_MENU_ITEM(menu_item), direction);
131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic void gtk_custom_menu_init(GtkCustomMenu* menu) {
135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic void gtk_custom_menu_class_init(GtkCustomMenuClass* klass) {
138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkMenuShellClass* menu_shell_class = GTK_MENU_SHELL_CLASS(klass);
140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  widget_class->button_press_event = gtk_custom_menu_button_press;
142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  widget_class->button_release_event = gtk_custom_menu_button_release;
143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  widget_class->motion_notify_event = gtk_custom_menu_motion_notify;
144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  menu_shell_class->move_current = gtk_custom_menu_move_current;
146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
148c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochGtkWidget* gtk_custom_menu_new() {
149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return GTK_WIDGET(g_object_new(GTK_TYPE_CUSTOM_MENU, NULL));
150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
151