From 6bd56f2a5245287b55a3b634d92190b6d258dd69 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 13:59:41 +0000 Subject: [PATCH] gtk: Add utils imported from chrome. --- atom.gyp | 8 +- browser/native_window.h | 2 +- browser/ui/gtk/gtk_custom_menu.cc | 150 ++++++++ browser/ui/gtk/gtk_custom_menu.h | 51 +++ browser/ui/gtk/gtk_custom_menu_item.cc | 493 +++++++++++++++++++++++++ browser/ui/gtk/gtk_custom_menu_item.h | 139 +++++++ browser/ui/gtk/gtk_window_util.cc | 4 +- 7 files changed, 842 insertions(+), 5 deletions(-) create mode 100644 browser/ui/gtk/gtk_custom_menu.cc create mode 100644 browser/ui/gtk/gtk_custom_menu.h create mode 100644 browser/ui/gtk/gtk_custom_menu_item.cc create mode 100644 browser/ui/gtk/gtk_custom_menu_item.h diff --git a/atom.gyp b/atom.gyp index 6c795a258170..dd1506233f19 100644 --- a/atom.gyp +++ b/atom.gyp @@ -121,11 +121,15 @@ 'browser/ui/file_dialog.h', 'browser/ui/file_dialog_mac.mm', 'browser/ui/file_dialog_win.cc', + 'browser/ui/gtk/gtk_custom_menu.cc', + 'browser/ui/gtk/gtk_custom_menu.h', + 'browser/ui/gtk/gtk_custom_menu_item.cc', + 'browser/ui/gtk/gtk_custom_menu_item.h', + 'browser/ui/gtk/gtk_window_util.cc', + 'browser/ui/gtk/gtk_window_util.h', 'browser/ui/message_box.h', 'browser/ui/message_box_mac.mm', 'browser/ui/message_box_win.cc', - 'browser/ui/gtk/gtk_window_util.cc', - 'browser/ui/gtk/gtk_window_util.h', 'browser/ui/win/menu_2.cc', 'browser/ui/win/menu_2.h', 'browser/ui/win/native_menu_win.cc', diff --git a/browser/native_window.h b/browser/native_window.h index 0e802fd110d9..0b82b969c723 100644 --- a/browser/native_window.h +++ b/browser/native_window.h @@ -56,7 +56,7 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, class DialogScope { public: - DialogScope(NativeWindow* window) + explicit DialogScope(NativeWindow* window) : window_(window) { if (window_ != NULL) window_->set_has_dialog_attached(true); diff --git a/browser/ui/gtk/gtk_custom_menu.cc b/browser/ui/gtk/gtk_custom_menu.cc new file mode 100644 index 000000000000..4606cafdcfae --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu.cc @@ -0,0 +1,150 @@ +// 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/gtk_custom_menu.h" + +#include "browser/ui/gtk/gtk_custom_menu_item.h" + +G_DEFINE_TYPE(GtkCustomMenu, gtk_custom_menu, GTK_TYPE_MENU) + +// Stolen directly from gtkmenushell.c. I'd love to call the library version +// instead, but it's static and isn't exported. :( +static gint gtk_menu_shell_is_item(GtkMenuShell* menu_shell, + GtkWidget* child) { + GtkWidget *parent; + + g_return_val_if_fail(GTK_IS_MENU_SHELL(menu_shell), FALSE); + g_return_val_if_fail(child != NULL, FALSE); + + parent = gtk_widget_get_parent(child); + while (GTK_IS_MENU_SHELL(parent)) { + if (parent == reinterpret_cast(menu_shell)) + return TRUE; + parent = GTK_MENU_SHELL(parent)->parent_menu_shell; + } + + return FALSE; +} + +// Stolen directly from gtkmenushell.c. I'd love to call the library version +// instead, but it's static and isn't exported. :( +static GtkWidget* gtk_menu_shell_get_item(GtkMenuShell* menu_shell, + GdkEvent* event) { + GtkWidget* menu_item = gtk_get_event_widget(event); + + while (menu_item && !GTK_IS_MENU_ITEM(menu_item)) + menu_item = gtk_widget_get_parent(menu_item); + + if (menu_item && gtk_menu_shell_is_item(menu_shell, menu_item)) + return menu_item; + else + return NULL; +} + +// When processing a button event, abort processing if the cursor isn't in a +// clickable region. +static gboolean gtk_custom_menu_button_press(GtkWidget* widget, + GdkEventButton* event) { + GtkWidget* menu_item = gtk_menu_shell_get_item( + GTK_MENU_SHELL(widget), reinterpret_cast(event)); + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + if (!gtk_custom_menu_item_is_in_clickable_region( + GTK_CUSTOM_MENU_ITEM(menu_item))) { + return TRUE; + } + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + button_press_event(widget, event); +} + +// When processing a button event, abort processing if the cursor isn't in a +// clickable region. If it's in a button that doesn't dismiss the menu, fire +// that event and abort having the normal GtkMenu code run. +static gboolean gtk_custom_menu_button_release(GtkWidget* widget, + GdkEventButton* event) { + GtkWidget* menu_item = gtk_menu_shell_get_item( + GTK_MENU_SHELL(widget), reinterpret_cast(event)); + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + if (!gtk_custom_menu_item_is_in_clickable_region( + GTK_CUSTOM_MENU_ITEM(menu_item))) { + // Stop processing this event. This isn't a clickable region. + return TRUE; + } + + if (gtk_custom_menu_item_try_no_dismiss_command( + GTK_CUSTOM_MENU_ITEM(menu_item))) { + return TRUE; + } + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + button_release_event(widget, event); +} + +// Manually forward button press events to the menu item (and then do what we'd +// do normally). +static gboolean gtk_custom_menu_motion_notify(GtkWidget* widget, + GdkEventMotion* event) { + GtkWidget* menu_item = gtk_menu_shell_get_item( + GTK_MENU_SHELL(widget), (GdkEvent*)event); + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + gtk_custom_menu_item_receive_motion_event(GTK_CUSTOM_MENU_ITEM(menu_item), + event->x, event->y); + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + motion_notify_event(widget, event); +} + +static void gtk_custom_menu_move_current(GtkMenuShell* menu_shell, + GtkMenuDirectionType direction) { + // If the currently selected item is custom, we give it first chance to catch + // up/down events. + + // TODO(erg): We are breaking a GSEAL by directly accessing this. We'll need + // to fix this by the time gtk3 comes out. + GtkWidget* menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item; + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + switch (direction) { + case GTK_MENU_DIR_PREV: + case GTK_MENU_DIR_NEXT: + if (gtk_custom_menu_item_handle_move(GTK_CUSTOM_MENU_ITEM(menu_item), + direction)) + return; + break; + default: + break; + } + } + + GTK_MENU_SHELL_CLASS(gtk_custom_menu_parent_class)-> + move_current(menu_shell, direction); + + // In the case of hitting PREV and transitioning to a custom menu, we want to + // make sure we're selecting the final item in the list, not the first one. + menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item; + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + gtk_custom_menu_item_select_item_by_direction( + GTK_CUSTOM_MENU_ITEM(menu_item), direction); + } +} + +static void gtk_custom_menu_init(GtkCustomMenu* menu) { +} + +static void gtk_custom_menu_class_init(GtkCustomMenuClass* klass) { + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + GtkMenuShellClass* menu_shell_class = GTK_MENU_SHELL_CLASS(klass); + + widget_class->button_press_event = gtk_custom_menu_button_press; + widget_class->button_release_event = gtk_custom_menu_button_release; + widget_class->motion_notify_event = gtk_custom_menu_motion_notify; + + menu_shell_class->move_current = gtk_custom_menu_move_current; +} + +GtkWidget* gtk_custom_menu_new() { + return GTK_WIDGET(g_object_new(GTK_TYPE_CUSTOM_MENU, NULL)); +} diff --git a/browser/ui/gtk/gtk_custom_menu.h b/browser/ui/gtk/gtk_custom_menu.h new file mode 100644 index 000000000000..7aaa2b54f6f5 --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011 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 CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_ +#define CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_ + +// GtkCustomMenu is a GtkMenu subclass that can contain, and collaborates with, +// GtkCustomMenuItem instances. GtkCustomMenuItem is a GtkMenuItem that can +// have buttons and other normal widgets embeded in it. GtkCustomMenu exists +// only to override most of the button/motion/move callback functions so +// that the normal GtkMenu implementation doesn't handle events related to +// GtkCustomMenuItem items. +// +// For a more through overview of this system, see the comments in +// gtk_custom_menu_item.h. + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CUSTOM_MENU \ + (gtk_custom_menu_get_type()) +#define GTK_CUSTOM_MENU(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_CUSTOM_MENU, GtkCustomMenu)) +#define GTK_CUSTOM_MENU_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_CUSTOM_MENU, GtkCustomMenuClass)) +#define GTK_IS_CUSTOM_MENU(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_CUSTOM_MENU)) +#define GTK_IS_CUSTOM_MENU_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_CUSTOM_MENU)) +#define GTK_CUSTOM_MENU_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_CUSTOM_MENU, GtkCustomMenuClass)) + +typedef struct _GtkCustomMenu GtkCustomMenu; +typedef struct _GtkCustomMenuClass GtkCustomMenuClass; + +struct _GtkCustomMenu { + GtkMenu menu; +}; + +struct _GtkCustomMenuClass { + GtkMenuClass parent_class; +}; + +GType gtk_custom_menu_get_type(void) G_GNUC_CONST; +GtkWidget* gtk_custom_menu_new(); + +G_END_DECLS + +#endif // CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_ diff --git a/browser/ui/gtk/gtk_custom_menu_item.cc b/browser/ui/gtk/gtk_custom_menu_item.cc new file mode 100644 index 000000000000..617c1711995a --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu_item.cc @@ -0,0 +1,493 @@ +// 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/gtk_custom_menu_item.h" + +#include "base/i18n/rtl.h" +#include "browser/ui/gtk/gtk_custom_menu.h" +#include "ui/gfx/gtk_compat.h" + +// This method was autogenerated by the program glib-genmarshall, which +// generated it from the line "BOOL:INT". Two different attempts at getting gyp +// to autogenerate this didn't work. If we need more non-standard marshallers, +// this should be deleted, and an actual build step should be added. +void chrome_marshall_BOOLEAN__INT(GClosure* closure, + GValue* return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue* param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) { + typedef gboolean(*GMarshalFunc_BOOLEAN__INT)(gpointer data1, + gint arg_1, + gpointer data2); + register GMarshalFunc_BOOLEAN__INT callback; + register GCClosure *cc = (GCClosure*)closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail(return_value != NULL); + g_return_if_fail(n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA(closure)) { + data1 = closure->data; + // Note: This line (and the line setting data1 in the other if branch) + // were macros in the original autogenerated output. This is with the + // macro resolved for release mode. In debug mode, it uses an accessor + // that asserts saying that the object pointed to by param_values doesn't + // hold a pointer. This appears to be the cause of http://crbug.com/58945. + // + // This is more than a little odd because the gtype on this first param + // isn't set correctly by the time we get here, while I watched it + // explicitly set upstack. I verified that v_pointer is still set + // correctly. I'm not sure what's going on. :( + data2 = (param_values + 0)->data[0].v_pointer; + } else { + data1 = (param_values + 0)->data[0].v_pointer; + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__INT)(marshal_data ? marshal_data : + cc->callback); + + v_return = callback(data1, + g_value_get_int(param_values + 1), + data2); + + g_value_set_boolean(return_value, v_return); +} + +enum { + BUTTON_PUSHED, + TRY_BUTTON_PUSHED, + LAST_SIGNAL +}; + +static guint custom_menu_item_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE(GtkCustomMenuItem, gtk_custom_menu_item, GTK_TYPE_MENU_ITEM) + +static void set_selected(GtkCustomMenuItem* item, GtkWidget* selected) { + if (selected != item->currently_selected_button) { + if (item->currently_selected_button) { + gtk_widget_set_state(item->currently_selected_button, GTK_STATE_NORMAL); + gtk_widget_set_state( + gtk_bin_get_child(GTK_BIN(item->currently_selected_button)), + GTK_STATE_NORMAL); + } + + item->currently_selected_button = selected; + if (item->currently_selected_button) { + gtk_widget_set_state(item->currently_selected_button, GTK_STATE_SELECTED); + gtk_widget_set_state( + gtk_bin_get_child(GTK_BIN(item->currently_selected_button)), + GTK_STATE_PRELIGHT); + } + } +} + +// When GtkButtons set the label text, they rebuild the widget hierarchy each +// and every time. Therefore, we can't just fish out the label from the button +// and set some properties; we have to create this callback function that +// listens on the button's "notify" signal, which is emitted right after the +// label has been (re)created. (Label values can change dynamically.) +static void on_button_label_set(GObject* object) { + GtkButton* button = GTK_BUTTON(object); + GtkWidget* child = gtk_bin_get_child(GTK_BIN(button)); + gtk_widget_set_sensitive(child, FALSE); + gtk_misc_set_padding(GTK_MISC(child), 2, 0); +} + +static void gtk_custom_menu_item_finalize(GObject *object); +static gint gtk_custom_menu_item_expose(GtkWidget* widget, + GdkEventExpose* event); +static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget, + GdkEventExpose* event, + GtkCustomMenuItem* menu_item); +static void gtk_custom_menu_item_select(GtkItem *item); +static void gtk_custom_menu_item_deselect(GtkItem *item); +static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item); + +static void gtk_custom_menu_item_init(GtkCustomMenuItem* item) { + item->all_widgets = NULL; + item->button_widgets = NULL; + item->currently_selected_button = NULL; + item->previously_selected_button = NULL; + + GtkWidget* menu_hbox = gtk_hbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(item), menu_hbox); + + item->label = gtk_label_new(NULL); + gtk_misc_set_alignment(GTK_MISC(item->label), 0.0, 0.5); + gtk_box_pack_start(GTK_BOX(menu_hbox), item->label, TRUE, TRUE, 0); + + item->hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(menu_hbox), item->hbox, FALSE, FALSE, 0); + + g_signal_connect(item->hbox, "expose-event", + G_CALLBACK(gtk_custom_menu_item_hbox_expose), + item); + + gtk_widget_show_all(menu_hbox); +} + +static void gtk_custom_menu_item_class_init(GtkCustomMenuItemClass* klass) { + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + GtkItemClass* item_class = GTK_ITEM_CLASS(klass); + GtkMenuItemClass* menu_item_class = GTK_MENU_ITEM_CLASS(klass); + + gobject_class->finalize = gtk_custom_menu_item_finalize; + + widget_class->expose_event = gtk_custom_menu_item_expose; + + item_class->select = gtk_custom_menu_item_select; + item_class->deselect = gtk_custom_menu_item_deselect; + + menu_item_class->activate = gtk_custom_menu_item_activate; + + custom_menu_item_signals[BUTTON_PUSHED] = + g_signal_new("button-pushed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + custom_menu_item_signals[TRY_BUTTON_PUSHED] = + g_signal_new("try-button-pushed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + chrome_marshall_BOOLEAN__INT, + G_TYPE_BOOLEAN, 1, G_TYPE_INT); +} + +static void gtk_custom_menu_item_finalize(GObject *object) { + GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM(object); + g_list_free(item->all_widgets); + g_list_free(item->button_widgets); + + G_OBJECT_CLASS(gtk_custom_menu_item_parent_class)->finalize(object); +} + +static gint gtk_custom_menu_item_expose(GtkWidget* widget, + GdkEventExpose* event) { + if (gtk_widget_get_visible(widget) && + gtk_widget_get_mapped(widget) && + gtk_bin_get_child(GTK_BIN(widget))) { + // We skip the drawing in the GtkMenuItem class it draws the highlighted + // background and we don't want that. + gtk_container_propagate_expose(GTK_CONTAINER(widget), + gtk_bin_get_child(GTK_BIN(widget)), + event); + } + + return FALSE; +} + +static void gtk_custom_menu_item_expose_button(GtkWidget* hbox, + GdkEventExpose* event, + GList* button_item) { + // We search backwards to find the leftmost and rightmost buttons. The + // current button may be that button. + GtkWidget* current_button = GTK_WIDGET(button_item->data); + GtkWidget* first_button = current_button; + for (GList* i = button_item; i && GTK_IS_BUTTON(i->data); + i = g_list_previous(i)) { + first_button = GTK_WIDGET(i->data); + } + + GtkWidget* last_button = current_button; + for (GList* i = button_item; i && GTK_IS_BUTTON(i->data); + i = g_list_next(i)) { + last_button = GTK_WIDGET(i->data); + } + + if (base::i18n::IsRTL()) + std::swap(first_button, last_button); + + GtkAllocation first_allocation; + gtk_widget_get_allocation(first_button, &first_allocation); + GtkAllocation current_allocation; + gtk_widget_get_allocation(current_button, ¤t_allocation); + GtkAllocation last_allocation; + gtk_widget_get_allocation(last_button, &last_allocation); + + int x = first_allocation.x; + int y = first_allocation.y; + int width = last_allocation.width + last_allocation.x - first_allocation.x; + int height = last_allocation.height; + + gtk_paint_box(gtk_widget_get_style(hbox), + gtk_widget_get_window(hbox), + gtk_widget_get_state(current_button), + GTK_SHADOW_OUT, + ¤t_allocation, hbox, "button", + x, y, width, height); + + // Propagate to the button's children. + gtk_container_propagate_expose( + GTK_CONTAINER(current_button), + gtk_bin_get_child(GTK_BIN(current_button)), + event); +} + +static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget, + GdkEventExpose* event, + GtkCustomMenuItem* menu_item) { + // First render all the buttons that aren't the currently selected item. + for (GList* current_item = menu_item->all_widgets; + current_item != NULL; current_item = g_list_next(current_item)) { + if (GTK_IS_BUTTON(current_item->data)) { + if (GTK_WIDGET(current_item->data) != + menu_item->currently_selected_button) { + gtk_custom_menu_item_expose_button(widget, event, current_item); + } + } + } + + // As a separate pass, draw the buton separators above. We need to draw the + // separators in a separate pass because we are drawing on top of the + // buttons. Otherwise, the vlines are overwritten by the next button. + for (GList* current_item = menu_item->all_widgets; + current_item != NULL; current_item = g_list_next(current_item)) { + if (GTK_IS_BUTTON(current_item->data)) { + // Check to see if this is the last button in a run. + GList* next_item = g_list_next(current_item); + if (next_item && GTK_IS_BUTTON(next_item->data)) { + GtkWidget* current_button = GTK_WIDGET(current_item->data); + GtkAllocation button_allocation; + gtk_widget_get_allocation(current_button, &button_allocation); + GtkAllocation child_alloc; + gtk_widget_get_allocation(gtk_bin_get_child(GTK_BIN(current_button)), + &child_alloc); + GtkStyle* style = gtk_widget_get_style(widget); + int half_offset = style->xthickness / 2; + gtk_paint_vline(style, + gtk_widget_get_window(widget), + gtk_widget_get_state(current_button), + &event->area, widget, "button", + child_alloc.y, + child_alloc.y + child_alloc.height, + button_allocation.x + + button_allocation.width - half_offset); + } + } + } + + // Finally, draw the selected item on top of the separators so there are no + // artifacts inside the button area. + GList* selected = g_list_find(menu_item->all_widgets, + menu_item->currently_selected_button); + if (selected) { + gtk_custom_menu_item_expose_button(widget, event, selected); + } + + return TRUE; +} + +static void gtk_custom_menu_item_select(GtkItem* item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item); + + // When we are selected, the only thing we do is clear information from + // previous selections. Actual selection of a button is done either in the + // "mouse-motion-event" or is manually set from GtkCustomMenu's overridden + // "move-current" handler. + custom_item->previously_selected_button = NULL; + + gtk_widget_queue_draw(GTK_WIDGET(item)); +} + +static void gtk_custom_menu_item_deselect(GtkItem* item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item); + + // When we are deselected, we store the item that was currently selected so + // that it can be acted on. Menu items are first deselected before they are + // activated. + custom_item->previously_selected_button = + custom_item->currently_selected_button; + if (custom_item->currently_selected_button) + set_selected(custom_item, NULL); + + gtk_widget_queue_draw(GTK_WIDGET(item)); +} + +static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item); + + // We look at |previously_selected_button| because by the time we've been + // activated, we've already gone through our deselect handler. + if (custom_item->previously_selected_button) { + gpointer id_ptr = g_object_get_data( + G_OBJECT(custom_item->previously_selected_button), "command-id"); + if (id_ptr != NULL) { + int command_id = GPOINTER_TO_INT(id_ptr); + g_signal_emit(custom_item, custom_menu_item_signals[BUTTON_PUSHED], 0, + command_id); + set_selected(custom_item, NULL); + } + } +} + +GtkWidget* gtk_custom_menu_item_new(const char* title) { + GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM( + g_object_new(GTK_TYPE_CUSTOM_MENU_ITEM, NULL)); + gtk_label_set_text(GTK_LABEL(item->label), title); + return GTK_WIDGET(item); +} + +GtkWidget* gtk_custom_menu_item_add_button(GtkCustomMenuItem* menu_item, + int command_id) { + GtkWidget* button = gtk_button_new(); + g_object_set_data(G_OBJECT(button), "command-id", + GINT_TO_POINTER(command_id)); + gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0); + gtk_widget_show(button); + + menu_item->all_widgets = g_list_append(menu_item->all_widgets, button); + menu_item->button_widgets = g_list_append(menu_item->button_widgets, button); + + return button; +} + +GtkWidget* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem* menu_item, + int command_id) { + GtkWidget* button = gtk_button_new_with_label(""); + g_object_set_data(G_OBJECT(button), "command-id", + GINT_TO_POINTER(command_id)); + gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0); + g_signal_connect(button, "notify::label", + G_CALLBACK(on_button_label_set), NULL); + gtk_widget_show(button); + + menu_item->all_widgets = g_list_append(menu_item->all_widgets, button); + + return button; +} + +void gtk_custom_menu_item_add_space(GtkCustomMenuItem* menu_item) { + GtkWidget* fixed = gtk_fixed_new(); + gtk_widget_set_size_request(fixed, 5, -1); + + gtk_box_pack_start(GTK_BOX(menu_item->hbox), fixed, FALSE, FALSE, 0); + gtk_widget_show(fixed); + + menu_item->all_widgets = g_list_append(menu_item->all_widgets, fixed); +} + +void gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem* menu_item, + gdouble x, gdouble y) { + GtkWidget* new_selected_widget = NULL; + GList* current = menu_item->button_widgets; + for (; current != NULL; current = current->next) { + GtkWidget* current_widget = GTK_WIDGET(current->data); + GtkAllocation alloc; + gtk_widget_get_allocation(current_widget, &alloc); + int offset_x, offset_y; + gtk_widget_translate_coordinates(current_widget, GTK_WIDGET(menu_item), + 0, 0, &offset_x, &offset_y); + if (x >= offset_x && x < (offset_x + alloc.width) && + y >= offset_y && y < (offset_y + alloc.height)) { + new_selected_widget = current_widget; + break; + } + } + + set_selected(menu_item, new_selected_widget); +} + +gboolean gtk_custom_menu_item_handle_move(GtkCustomMenuItem* menu_item, + GtkMenuDirectionType direction) { + GtkWidget* current = menu_item->currently_selected_button; + if (menu_item->button_widgets && current) { + switch (direction) { + case GTK_MENU_DIR_PREV: { + if (g_list_first(menu_item->button_widgets)->data == current) + return FALSE; + + set_selected(menu_item, GTK_WIDGET(g_list_previous(g_list_find( + menu_item->button_widgets, current))->data)); + break; + } + case GTK_MENU_DIR_NEXT: { + if (g_list_last(menu_item->button_widgets)->data == current) + return FALSE; + + set_selected(menu_item, GTK_WIDGET(g_list_next(g_list_find( + menu_item->button_widgets, current))->data)); + break; + } + default: + break; + } + } + + return TRUE; +} + +void gtk_custom_menu_item_select_item_by_direction( + GtkCustomMenuItem* menu_item, GtkMenuDirectionType direction) { + menu_item->previously_selected_button = NULL; + + // If we're just told to be selected by the menu system, select the first + // item. + if (menu_item->button_widgets) { + switch (direction) { + case GTK_MENU_DIR_PREV: { + GtkWidget* last_button = + GTK_WIDGET(g_list_last(menu_item->button_widgets)->data); + if (last_button) + set_selected(menu_item, last_button); + break; + } + case GTK_MENU_DIR_NEXT: { + GtkWidget* first_button = + GTK_WIDGET(g_list_first(menu_item->button_widgets)->data); + if (first_button) + set_selected(menu_item, first_button); + break; + } + default: + break; + } + } + + gtk_widget_queue_draw(GTK_WIDGET(menu_item)); +} + +gboolean gtk_custom_menu_item_is_in_clickable_region( + GtkCustomMenuItem* menu_item) { + return menu_item->currently_selected_button != NULL; +} + +gboolean gtk_custom_menu_item_try_no_dismiss_command( + GtkCustomMenuItem* menu_item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item); + gboolean activated = TRUE; + + // We work with |currently_selected_button| instead of + // |previously_selected_button| since we haven't been "deselect"ed yet. + gpointer id_ptr = g_object_get_data( + G_OBJECT(custom_item->currently_selected_button), "command-id"); + if (id_ptr != NULL) { + int command_id = GPOINTER_TO_INT(id_ptr); + g_signal_emit(custom_item, custom_menu_item_signals[TRY_BUTTON_PUSHED], 0, + command_id, &activated); + } + + return activated; +} + +void gtk_custom_menu_item_foreach_button(GtkCustomMenuItem* menu_item, + GtkCallback callback, + gpointer callback_data) { + // Even though we're filtering |all_widgets| on GTK_IS_BUTTON(), this isn't + // equivalent to |button_widgets| because we also want the button-labels. + for (GList* i = menu_item->all_widgets; i && GTK_IS_BUTTON(i->data); + i = g_list_next(i)) { + if (GTK_IS_BUTTON(i->data)) { + callback(GTK_WIDGET(i->data), callback_data); + } + } +} diff --git a/browser/ui/gtk/gtk_custom_menu_item.h b/browser/ui/gtk/gtk_custom_menu_item.h new file mode 100644 index 000000000000..46e5cf721e9b --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu_item.h @@ -0,0 +1,139 @@ +// Copyright (c) 2011 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 CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_ +#define CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_ + +// GtkCustomMenuItem is a GtkMenuItem subclass that has buttons in it and acts +// to support this. GtkCustomMenuItems only render properly when put in a +// GtkCustomMenu; there's a lot of collaboration between these two classes +// necessary to work around how gtk normally does menus. +// +// We can't rely on the normal event infrastructure. While a menu is up, the +// GtkMenu has a grab on all events. Instead of trying to pump events through +// the normal channels, we have the GtkCustomMenu selectively forward mouse +// motion through a back channel. The GtkCustomMenu only listens for button +// press information so it can block the effects of the click if the cursor +// isn't in a button in the menu item. +// +// A GtkCustomMenuItem doesn't try to take these signals and forward them to +// the buttons it owns. The GtkCustomMenu class keeps track of which button is +// selected (due to key events and mouse movement) and otherwise acts like a +// normal GtkItem. The buttons are only for sizing and rendering; they don't +// respond to events. Instead, when the GtkCustomMenuItem is activated by the +// GtkMenu, it uses which button was selected as a signal of what to do. +// +// Users should connect to the "button-pushed" signal to be notified when a +// button was pushed. We don't go through the normal "activate" signal because +// we need to communicate additional information, namely which button was +// activated. + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CUSTOM_MENU_ITEM \ + (gtk_custom_menu_item_get_type()) +#define GTK_CUSTOM_MENU_ITEM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_CUSTOM_MENU_ITEM, \ + GtkCustomMenuItem)) +#define GTK_CUSTOM_MENU_ITEM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_CUSTOM_MENU_ITEM, \ + GtkCustomMenuItemClass)) +#define GTK_IS_CUSTOM_MENU_ITEM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_CUSTOM_MENU_ITEM)) +#define GTK_IS_CUSTOM_MENU_ITEM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_CUSTOM_MENU_ITEM)) +#define GTK_CUSTOM_MENU_ITEM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_CUSTOM_MENU_ITEM, \ + GtkCustomMenuItemClass)) + +typedef struct _GtkCustomMenuItem GtkCustomMenuItem; +typedef struct _GtkCustomMenuItemClass GtkCustomMenuItemClass; + +struct _GtkCustomMenuItem { + GtkMenuItem menu_item; + + // Container for button widgets. + GtkWidget* hbox; + + // Label on left side of menu item. + GtkWidget* label; + + // List of all widgets we added. Used to find the leftmost and rightmost + // continuous buttons. + GList* all_widgets; + + // Possible button widgets. Used for keyboard navigation. + GList* button_widgets; + + // The widget that currently has highlight. + GtkWidget* currently_selected_button; + + // The widget that was selected *before* |currently_selected_button|. Why do + // we hang on to this? Because the menu system sends us a deselect signal + // right before activating us. We need to listen to deselect since that's + // what we receive when the mouse cursor leaves us entirely. + GtkWidget* previously_selected_button; +}; + +struct _GtkCustomMenuItemClass { + GtkMenuItemClass parent_class; +}; + +GType gtk_custom_menu_item_get_type(void) G_GNUC_CONST; +GtkWidget* gtk_custom_menu_item_new(const char* title); + +// Adds a button to our list of items in the |hbox|. +GtkWidget* gtk_custom_menu_item_add_button(GtkCustomMenuItem* menu_item, + int command_id); + +// Adds a button to our list of items in the |hbox|, but that isn't part of +// |button_widgets| to prevent it from being activatable. +GtkWidget* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem* menu_item, + int command_id); + +// Adds a vertical space in the |hbox|. +void gtk_custom_menu_item_add_space(GtkCustomMenuItem* menu_item); + +// Receives a motion event from the GtkCustomMenu that contains us. We can't +// just subscribe to motion-event or the individual widget enter/leave events +// because the top level GtkMenu has an event grab. +void gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem* menu_item, + gdouble x, gdouble y); + +// Notification that the menu got a cursor key event. Used to move up/down +// within the menu buttons. Returns TRUE to stop the default signal handler +// from running. +gboolean gtk_custom_menu_item_handle_move(GtkCustomMenuItem* menu_item, + GtkMenuDirectionType direction); + +// Because we only get a generic "selected" signal when we've changed, we need +// to have a way for the GtkCustomMenu to tell us that we were just +// selected. +void gtk_custom_menu_item_select_item_by_direction( + GtkCustomMenuItem* menu_item, GtkMenuDirectionType direction); + +// Whether we are currently hovering over a clickable region on the menu +// item. Used by GtkCustomMenu to determine whether it should discard click +// events. +gboolean gtk_custom_menu_item_is_in_clickable_region( + GtkCustomMenuItem* menu_item); + +// If the button is released while the |currently_selected_button| isn't +// supposed to dismiss the menu, this signals to our listeners that we want to +// run this command if it doesn't dismiss the menu. Returns TRUE if we acted +// on this button click (and should prevent the normal GtkMenu machinery from +// firing an "activate" signal). +gboolean gtk_custom_menu_item_try_no_dismiss_command( + GtkCustomMenuItem* menu_item); + +// Calls |callback| with every button and button-label in the container. +void gtk_custom_menu_item_foreach_button(GtkCustomMenuItem* menu_item, + GtkCallback callback, + gpointer callback_data); + +G_END_DECLS + +#endif // CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_ diff --git a/browser/ui/gtk/gtk_window_util.cc b/browser/ui/gtk/gtk_window_util.cc index 7cd84b9506ce..c06e3302b373 100644 --- a/browser/ui/gtk/gtk_window_util.cc +++ b/browser/ui/gtk/gtk_window_util.cc @@ -143,8 +143,8 @@ bool HandleTitleBarLeftMousePress( // the call to gtk_window_maximize fails. To work around this, we // keep track of the last click and if it's going to be a double click, // we don't call gtk_window_begin_move_drag. - DCHECK(event->type == GDK_BUTTON_PRESS); - DCHECK(event->button == 1); + DCHECK_EQ(event->type, GDK_BUTTON_PRESS); + DCHECK_EQ(event->button, 1); static GtkSettings* settings = gtk_settings_get_default(); gint double_click_time = 250;