Move all chromium's code under chromium_src.

This commit is contained in:
Cheng Zhao 2014-06-29 05:53:24 +00:00
parent 1f99a97544
commit 64bf1bcb9f
39 changed files with 594 additions and 53 deletions

View file

@ -0,0 +1,44 @@
// 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 "chrome/browser/ui/gtk/event_utils.h"
#include "base/logging.h"
#include "ui/base/window_open_disposition.h"
#include "ui/events/event_constants.h"
namespace event_utils {
int EventFlagsFromGdkState(guint state) {
int flags = ui::EF_NONE;
flags |= (state & GDK_LOCK_MASK) ? ui::EF_CAPS_LOCK_DOWN : ui::EF_NONE;
flags |= (state & GDK_CONTROL_MASK) ? ui::EF_CONTROL_DOWN : ui::EF_NONE;
flags |= (state & GDK_SHIFT_MASK) ? ui::EF_SHIFT_DOWN : ui::EF_NONE;
flags |= (state & GDK_MOD1_MASK) ? ui::EF_ALT_DOWN : ui::EF_NONE;
flags |= (state & GDK_BUTTON1_MASK) ? ui::EF_LEFT_MOUSE_BUTTON : ui::EF_NONE;
flags |= (state & GDK_BUTTON2_MASK) ? ui::EF_MIDDLE_MOUSE_BUTTON
: ui::EF_NONE;
flags |= (state & GDK_BUTTON3_MASK) ? ui::EF_RIGHT_MOUSE_BUTTON : ui::EF_NONE;
return flags;
}
// TODO(shinyak) This function will be removed after refactoring.
WindowOpenDisposition DispositionFromGdkState(guint state) {
int event_flags = EventFlagsFromGdkState(state);
return ui::DispositionFromEventFlags(event_flags);
}
WindowOpenDisposition DispositionForCurrentButtonPressEvent() {
GdkEvent* event = gtk_get_current_event();
if (!event) {
NOTREACHED();
return NEW_FOREGROUND_TAB;
}
guint state = event->button.state;
gdk_event_free(event);
return DispositionFromGdkState(state);
}
} // namespace event_utils

View file

@ -0,0 +1,28 @@
// 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 CHROME_BROWSER_UI_GTK_EVENT_UTILS_H_
#define CHROME_BROWSER_UI_GTK_EVENT_UTILS_H_
#include <gtk/gtk.h>
#include "ui/base/window_open_disposition.h"
namespace event_utils {
// Translates event flags into plaform independent event flags.
int EventFlagsFromGdkState(guint state);
// Translates GdkEvent state into what kind of disposition they represent.
// For example, a middle click would mean to open a background tab.
WindowOpenDisposition DispositionFromGdkState(guint state);
// Get the window open disposition from the state in gtk_get_current_event().
// This is designed to be called inside a "clicked" event handler. It is an
// error to call it when gtk_get_current_event() won't return a GdkEventButton*.
WindowOpenDisposition DispositionForCurrentButtonPressEvent();
} // namespace event_utils
#endif // CHROME_BROWSER_UI_GTK_EVENT_UTILS_H_

View file

@ -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 "chrome/browser/ui/gtk/gtk_custom_menu.h"
#include "chrome/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<GtkWidget*>(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<GdkEvent*>(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<GdkEvent*>(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));
}

View file

@ -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 <gtk/gtk.h>
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_

View file

@ -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 "chrome/browser/ui/gtk/gtk_custom_menu_item.h"
#include "base/i18n/rtl.h"
#include "chrome/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, &current_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,
&current_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);
}
}
}

View file

@ -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 <gtk/gtk.h>
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_

View file

@ -0,0 +1,94 @@
// 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 "chrome/browser/ui/gtk/gtk_util.h"
#include <cairo/cairo.h>
#include "base/logging.h"
namespace gtk_util {
namespace {
const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>";
// Returns the approximate number of characters that can horizontally fit in
// |pixel_width| pixels.
int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) {
DCHECK(gtk_widget_get_realized(widget))
<< " widget must be realized to compute font metrics correctly";
PangoContext* context = gtk_widget_create_pango_context(widget);
GtkStyle* style = gtk_widget_get_style(widget);
PangoFontMetrics* metrics = pango_context_get_metrics(context,
style->font_desc, pango_context_get_language(context));
// This technique (max of char and digit widths) matches the code in
// gtklabel.c.
int char_width = pixel_width * PANGO_SCALE /
std::max(pango_font_metrics_get_approximate_char_width(metrics),
pango_font_metrics_get_approximate_digit_width(metrics));
pango_font_metrics_unref(metrics);
g_object_unref(context);
return char_width;
}
void OnLabelRealize(GtkWidget* label, gpointer pixel_width) {
gtk_label_set_width_chars(
GTK_LABEL(label),
GetCharacterWidthForPixels(label, GPOINTER_TO_INT(pixel_width)));
}
} // namespace
GtkWidget* LeftAlignMisc(GtkWidget* misc) {
gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5);
return misc;
}
GtkWidget* CreateBoldLabel(const std::string& text) {
GtkWidget* label = gtk_label_new(NULL);
char* markup = g_markup_printf_escaped(kBoldLabelMarkup, text.c_str());
gtk_label_set_markup(GTK_LABEL(label), markup);
g_free(markup);
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))
parent = gtk_widget_get_parent(parent);
return !parent;
}
void SetLabelWidth(GtkWidget* label, int pixel_width) {
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
// Do the simple thing in LTR because the bug only affects right-aligned
// text. Also, when using the workaround, the label tries to maintain
// uniform line-length, which we don't really want.
if (gtk_widget_get_direction(label) == GTK_TEXT_DIR_LTR) {
gtk_widget_set_size_request(label, pixel_width, -1);
} else {
// The label has to be realized before we can adjust its width.
if (gtk_widget_get_realized(label)) {
OnLabelRealize(label, GINT_TO_POINTER(pixel_width));
} else {
g_signal_connect(label, "realize", G_CALLBACK(OnLabelRealize),
GINT_TO_POINTER(pixel_width));
}
}
}
} // namespace gtk_util

View file

@ -0,0 +1,36 @@
// 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 CHROME_BROWSER_UI_GTK_GTK_UTIL_H_
#define CHROME_BROWSER_UI_GTK_GTK_UTIL_H_
#include <gtk/gtk.h>
#include <string>
namespace gtk_util {
// Left-align the given GtkMisc and return the same pointer.
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);
// Sets the given label's size request to |pixel_width|. This will cause the
// label to wrap if it needs to. The reason for this function is that some
// versions of GTK mis-align labels that have a size request and line wrapping,
// and this function hides the complexity of the workaround.
void SetLabelWidth(GtkWidget* label, int pixel_width);
} // namespace gtk_util
#endif // CHROME_BROWSER_UI_GTK_GTK_UTIL_H_

View file

@ -0,0 +1,257 @@
// 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 "chrome/browser/ui/gtk/gtk_window_util.h"
#include <dlfcn.h>
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
using content::RenderWidgetHost;
using content::WebContents;
namespace gtk_window_util {
const int kFrameBorderThickness = 4;
const int kResizeAreaCornerSize = 16;
// Keep track of the last click time and the last click position so we can
// filter out extra GDK_BUTTON_PRESS events when a double click happens.
static guint32 last_click_time;
static int last_click_x;
static int last_click_y;
// Ubuntu patches their version of GTK+ so that there is always a
// gripper in the bottom right corner of the window. We dynamically
// look up this symbol because it's a non-standard Ubuntu extension to
// GTK+. We always need to disable this feature since we can't
// communicate this to WebKit easily.
typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean);
gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym;
void DisableResizeGrip(GtkWindow* window) {
static bool resize_grip_looked_up = false;
if (!resize_grip_looked_up) {
resize_grip_looked_up = true;
gtk_window_set_has_resize_grip_sym =
reinterpret_cast<gtk_window_set_has_resize_grip_func>(
dlsym(NULL, "gtk_window_set_has_resize_grip"));
}
if (gtk_window_set_has_resize_grip_sym)
gtk_window_set_has_resize_grip_sym(window, FALSE);
}
GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) {
switch (edge) {
case GDK_WINDOW_EDGE_NORTH_WEST:
return GDK_TOP_LEFT_CORNER;
case GDK_WINDOW_EDGE_NORTH:
return GDK_TOP_SIDE;
case GDK_WINDOW_EDGE_NORTH_EAST:
return GDK_TOP_RIGHT_CORNER;
case GDK_WINDOW_EDGE_WEST:
return GDK_LEFT_SIDE;
case GDK_WINDOW_EDGE_EAST:
return GDK_RIGHT_SIDE;
case GDK_WINDOW_EDGE_SOUTH_WEST:
return GDK_BOTTOM_LEFT_CORNER;
case GDK_WINDOW_EDGE_SOUTH:
return GDK_BOTTOM_SIDE;
case GDK_WINDOW_EDGE_SOUTH_EAST:
return GDK_BOTTOM_RIGHT_CORNER;
default:
NOTREACHED();
}
return GDK_LAST_CURSOR;
}
bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds) {
// A screen can be composed of multiple monitors.
GdkScreen* screen = gtk_window_get_screen(window);
GdkRectangle monitor_size;
if (gtk_widget_get_realized(GTK_WIDGET(window))) {
// |window| has been realized.
gint monitor_num = gdk_screen_get_monitor_at_window(screen,
gtk_widget_get_window(GTK_WIDGET(window)));
gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_size);
return bounds.size() == gfx::Size(monitor_size.width, monitor_size.height);
}
// Make sure the window doesn't match any monitor size. We compare against
// all monitors because we don't know which monitor the window is going to
// open on before window realized.
gint num_monitors = gdk_screen_get_n_monitors(screen);
for (gint i = 0; i < num_monitors; ++i) {
GdkRectangle monitor_size;
gdk_screen_get_monitor_geometry(screen, i, &monitor_size);
if (bounds.size() == gfx::Size(monitor_size.width, monitor_size.height))
return true;
}
return false;
}
bool HandleTitleBarLeftMousePress(
GtkWindow* window,
const gfx::Rect& bounds,
GdkEventButton* event) {
// We want to start a move when the user single clicks, but not start a
// move when the user double clicks. However, a double click sends the
// following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE,
// GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE. If we
// start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS,
// 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_EQ(event->type, GDK_BUTTON_PRESS);
DCHECK_EQ(event->button, 1);
static GtkSettings* settings = gtk_settings_get_default();
gint double_click_time = 250;
gint double_click_distance = 5;
g_object_get(G_OBJECT(settings),
"gtk-double-click-time", &double_click_time,
"gtk-double-click-distance", &double_click_distance,
NULL);
guint32 click_time = event->time - last_click_time;
int click_move_x = abs(event->x - last_click_x);
int click_move_y = abs(event->y - last_click_y);
last_click_time = event->time;
last_click_x = static_cast<int>(event->x);
last_click_y = static_cast<int>(event->y);
if (click_time > static_cast<guint32>(double_click_time) ||
click_move_x > double_click_distance ||
click_move_y > double_click_distance) {
// Ignore drag requests if the window is the size of the screen.
// We do this to avoid triggering fullscreen mode in metacity
// (without the --no-force-fullscreen flag) and in compiz (with
// Legacy Fullscreen Mode enabled).
if (!BoundsMatchMonitorSize(window, bounds)) {
gtk_window_begin_move_drag(window, event->button,
static_cast<gint>(event->x_root),
static_cast<gint>(event->y_root),
event->time);
}
return TRUE;
}
return FALSE;
}
void UnMaximize(GtkWindow* window,
const gfx::Rect& bounds,
const gfx::Rect& restored_bounds) {
gtk_window_unmaximize(window);
// It can happen that you end up with a window whose restore size is the same
// as the size of the screen, so unmaximizing it merely remaximizes it due to
// the same WM feature that SetWindowSize() works around. We try to detect
// this and resize the window to work around the issue.
if (bounds.size() == restored_bounds.size())
gtk_window_resize(window, bounds.width(), bounds.height() - 1);
}
void SetWindowCustomClass(GtkWindow* window, const std::string& wmclass) {
gtk_window_set_wmclass(window,
wmclass.c_str(),
gdk_get_program_class());
// Set WM_WINDOW_ROLE for session management purposes.
// See http://tronche.com/gui/x/icccm/sec-5.html .
gtk_window_set_role(window, wmclass.c_str());
}
void SetWindowSize(GtkWindow* window, const gfx::Size& size) {
gfx::Size new_size = size;
gint current_width = 0;
gint current_height = 0;
gtk_window_get_size(window, &current_width, &current_height);
GdkRectangle size_with_decorations = {0};
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
if (gdk_window) {
gdk_window_get_frame_extents(gdk_window,
&size_with_decorations);
}
if (current_width == size_with_decorations.width &&
current_height == size_with_decorations.height) {
// Make sure the window doesn't match any monitor size. We compare against
// all monitors because we don't know which monitor the window is going to
// open on (the WM decides that).
GdkScreen* screen = gtk_window_get_screen(window);
gint num_monitors = gdk_screen_get_n_monitors(screen);
for (gint i = 0; i < num_monitors; ++i) {
GdkRectangle monitor_size;
gdk_screen_get_monitor_geometry(screen, i, &monitor_size);
if (gfx::Size(monitor_size.width, monitor_size.height) == size) {
gtk_window_resize(window, size.width(), size.height() - 1);
return;
}
}
} else {
// gtk_window_resize is the size of the window not including decorations,
// but we are given the |size| including window decorations.
if (size_with_decorations.width > current_width) {
new_size.set_width(size.width() - size_with_decorations.width +
current_width);
}
if (size_with_decorations.height > current_height) {
new_size.set_height(size.height() - size_with_decorations.height +
current_height);
}
}
gtk_window_resize(window, new_size.width(), new_size.height());
}
bool GetWindowEdge(const gfx::Size& window_size,
int top_edge_inset,
int x,
int y,
GdkWindowEdge* edge) {
gfx::Rect middle(window_size);
middle.Inset(kFrameBorderThickness,
kFrameBorderThickness - top_edge_inset,
kFrameBorderThickness,
kFrameBorderThickness);
if (middle.Contains(x, y))
return false;
gfx::Rect north(0, 0, window_size.width(),
kResizeAreaCornerSize - top_edge_inset);
gfx::Rect west(0, 0, kResizeAreaCornerSize, window_size.height());
gfx::Rect south(0, window_size.height() - kResizeAreaCornerSize,
window_size.width(), kResizeAreaCornerSize);
gfx::Rect east(window_size.width() - kResizeAreaCornerSize, 0,
kResizeAreaCornerSize, window_size.height());
if (north.Contains(x, y)) {
if (west.Contains(x, y))
*edge = GDK_WINDOW_EDGE_NORTH_WEST;
else if (east.Contains(x, y))
*edge = GDK_WINDOW_EDGE_NORTH_EAST;
else
*edge = GDK_WINDOW_EDGE_NORTH;
} else if (south.Contains(x, y)) {
if (west.Contains(x, y))
*edge = GDK_WINDOW_EDGE_SOUTH_WEST;
else if (east.Contains(x, y))
*edge = GDK_WINDOW_EDGE_SOUTH_EAST;
else
*edge = GDK_WINDOW_EDGE_SOUTH;
} else {
if (west.Contains(x, y))
*edge = GDK_WINDOW_EDGE_WEST;
else if (east.Contains(x, y))
*edge = GDK_WINDOW_EDGE_EAST;
else
return false; // The cursor must be outside the window.
}
return true;
}
} // namespace gtk_window_util

View file

@ -0,0 +1,69 @@
// 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 CHROME_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_
#define CHROME_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include "ui/gfx/rect.h"
namespace content {
class WebContents;
}
namespace gtk_window_util {
// The frame border is only visible in restored mode and is hardcoded to 4 px
// on each side regardless of the system window border size.
extern const int kFrameBorderThickness;
// In the window corners, the resize areas don't actually expand bigger, but
// the 16 px at the end of each edge triggers diagonal resizing.
extern const int kResizeAreaCornerSize;
// Ubuntu patches their version of GTK+ to that there is always a
// gripper in the bottom right corner of the window. We always need to
// disable this feature since we can't communicate this to WebKit easily.
void DisableResizeGrip(GtkWindow* window);
// Returns the resize cursor corresponding to the window |edge|.
GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge);
// Returns |true| if the window bounds match the monitor size.
bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds);
bool HandleTitleBarLeftMousePress(GtkWindow* window,
const gfx::Rect& bounds,
GdkEventButton* event);
// Request the underlying window to unmaximize. Also tries to work around
// a window manager "feature" that can prevent this in some edge cases.
void UnMaximize(GtkWindow* window,
const gfx::Rect& bounds,
const gfx::Rect& restored_bounds);
// Set a custom WM_CLASS for a window.
void SetWindowCustomClass(GtkWindow* window, const std::string& wmclass);
// A helper method for setting the GtkWindow size that should be used in place
// of calling gtk_window_resize directly. This is done to avoid a WM "feature"
// where setting the window size to the monitor size causes the WM to set the
// EWMH for full screen mode.
void SetWindowSize(GtkWindow* window, const gfx::Size& size);
// If the point (|x|, |y|) is within the resize border area of the window,
// returns true and sets |edge| to the appropriate GdkWindowEdge value.
// Otherwise, returns false.
// |top_edge_inset| specifies how much smaller (in px) than the default edge
// size the top edge should be, used by browser windows to make it easier to
// move the window since a lot of title bar space is taken by the tabs.
bool GetWindowEdge(const gfx::Size& window_size,
int top_edge_inset,
int x,
int y,
GdkWindowEdge* edge);
} // namespace gtk_window_util
#endif // CHROME_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_

View file

@ -0,0 +1,883 @@
// 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 "chrome/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 "chrome/browser/ui/gtk/event_utils.h"
#include "chrome/browser/ui/gtk/gtk_custom_menu.h"
#include "chrome/browser/ui/gtk/gtk_custom_menu_item.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.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,
bool is_menubar)
: delegate_(delegate),
model_(model),
is_menubar_(is_menubar),
dummy_accel_group_(gtk_accel_group_new()),
menu_(is_menubar ? gtk_menu_bar_new() : 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() {
if (!is_menubar_)
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(
libgtk2ui::GdkPixbufFromSkBitmap(*icon.ToSkBitmap())));
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) {
if (is_menubar_)
return;
// 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(
libgtk2ui::GdkPixbufFromSkBitmap(*icon.ToSkBitmap())));
} 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);
}
}
}

View file

@ -0,0 +1,224 @@
// 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 CHROME_BROWSER_UI_GTK_MENU_GTK_H_
#define CHROME_BROWSER_UI_GTK_MENU_GTK_H_
#include <gtk/gtk.h>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
#include "chrome/browser/ui/libgtk2ui/gtk2_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,
bool is_menubar = false);
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_; }
ui::MenuModel* model() const { return model_;}
// 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_;
// Whether this is a menu bar.
bool is_menubar_;
// 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_;
libgtk2ui::Gtk2SignalRegistrar signal_;
base::WeakPtrFactory<MenuGtk> weak_factory_;
};
#endif // CHROME_BROWSER_UI_GTK_MENU_GTK_H_

View file

@ -0,0 +1,89 @@
// 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 "chrome/browser/ui/libgtk2ui/g_object_destructor_filo.h"
#include <glib-object.h>
#include "base/logging.h"
#include "base/memory/singleton.h"
namespace libgtk2ui {
GObjectDestructorFILO::GObjectDestructorFILO() {
}
GObjectDestructorFILO::~GObjectDestructorFILO() {
// Probably CHECK(handler_map_.empty()) would look natural here. But
// some tests (some views_unittests) violate this assertion.
}
// static
GObjectDestructorFILO* GObjectDestructorFILO::GetInstance() {
return Singleton<GObjectDestructorFILO>::get();
}
void GObjectDestructorFILO::Connect(
GObject* object, DestructorHook callback, void* context) {
const Hook hook(object, callback, context);
HandlerMap::iterator iter = handler_map_.find(object);
if (iter == handler_map_.end()) {
g_object_weak_ref(object, WeakNotifyThunk, this);
handler_map_[object].push_front(hook);
} else {
iter->second.push_front(hook);
}
}
void GObjectDestructorFILO::Disconnect(
GObject* object, DestructorHook callback, void* context) {
HandlerMap::iterator iter = handler_map_.find(object);
if (iter == handler_map_.end()) {
LOG(DFATAL) << "Unable to disconnect destructor hook for object " << object
<< ": hook not found (" << callback << ", " << context << ").";
return;
}
HandlerList& dtors = iter->second;
if (dtors.empty()) {
LOG(DFATAL) << "Destructor list is empty for specified object " << object
<< " Maybe it is being executed?";
return;
}
if (!dtors.front().equal(object, callback, context)) {
// Reenable this warning once this bug is fixed:
// http://code.google.com/p/chromium/issues/detail?id=85603
DVLOG(1) << "Destructors should be unregistered the reverse order they "
<< "were registered. But for object " << object << " "
<< "deleted hook is "<< context << ", the last queued hook is "
<< dtors.front().context;
}
for (HandlerList::iterator i = dtors.begin(); i != dtors.end(); ++i) {
if (i->equal(object, callback, context)) {
dtors.erase(i);
break;
}
}
if (dtors.empty()) {
g_object_weak_unref(object, WeakNotifyThunk, this);
handler_map_.erase(iter);
}
}
void GObjectDestructorFILO::WeakNotify(GObject* where_the_object_was) {
HandlerMap::iterator iter = handler_map_.find(where_the_object_was);
DCHECK(iter != handler_map_.end());
DCHECK(!iter->second.empty());
// Save destructor list for given object into local copy to avoid reentrancy
// problem: if callee wants to modify the caller list.
HandlerList dtors;
iter->second.swap(dtors);
handler_map_.erase(iter);
// Execute hooks in local list in FILO order.
for (HandlerList::iterator i = dtors.begin(); i != dtors.end(); ++i)
i->callback(i->context, where_the_object_was);
}
} // namespace libgtk2ui

View file

@ -0,0 +1,90 @@
// 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 CHROME_BROWSER_UI_LIBGTK2UI_G_OBJECT_DESTRUCTOR_FILO_H_
#define CHROME_BROWSER_UI_LIBGTK2UI_G_OBJECT_DESTRUCTOR_FILO_H_
#include <glib.h>
#include <list>
#include <map>
#include "base/basictypes.h"
template <typename T> struct DefaultSingletonTraits;
typedef struct _GObject GObject;
namespace libgtk2ui {
// This class hooks calls to g_object_weak_ref()/unref() and executes them in
// FILO order. This is important if there are several hooks to the single object
// (set up at different levels of class hierarchy) and the lowest hook (set up
// first) is deleting self - it must be called last (among hooks for the given
// object). Unfortunately Glib does not provide this guarantee.
//
// Use it as follows:
//
// static void OnDestroyedThunk(gpointer data, GObject *where_the_object_was) {
// reinterpret_cast<MyClass*>(data)->OnDestroyed(where_the_object_was);
// }
// void MyClass::OnDestroyed(GObject *where_the_object_was) {
// destroyed_ = true;
// delete this;
// }
// MyClass::Init() {
// ...
// ui::GObjectDestructorFILO::GetInstance()->Connect(
// G_OBJECT(my_widget), &OnDestroyedThunk, this);
// }
// MyClass::~MyClass() {
// if (!destroyed_) {
// ui::GObjectDestructorFILO::GetInstance()->Disconnect(
// G_OBJECT(my_widget), &OnDestroyedThunk, this);
// }
// }
//
// TODO(glotov): Probably worth adding ScopedGObjectDtor<T>.
//
// This class is a singleton. Not thread safe. Must be called within UI thread.
class GObjectDestructorFILO {
public:
typedef void (*DestructorHook)(void* context, GObject* where_the_object_was);
static GObjectDestructorFILO* GetInstance();
void Connect(GObject* object, DestructorHook callback, void* context);
void Disconnect(GObject* object, DestructorHook callback, void* context);
private:
struct Hook {
Hook(GObject* o, DestructorHook cb, void* ctx)
: object(o), callback(cb), context(ctx) {
}
bool equal(GObject* o, DestructorHook cb, void* ctx) const {
return object == o && callback == cb && context == ctx;
}
GObject* object;
DestructorHook callback;
void* context;
};
typedef std::list<Hook> HandlerList;
typedef std::map<GObject*, HandlerList> HandlerMap;
GObjectDestructorFILO();
~GObjectDestructorFILO();
friend struct DefaultSingletonTraits<GObjectDestructorFILO>;
void WeakNotify(GObject* where_the_object_was);
static void WeakNotifyThunk(gpointer data, GObject* where_the_object_was) {
reinterpret_cast<GObjectDestructorFILO*>(data)->WeakNotify(
where_the_object_was);
}
HandlerMap handler_map_;
DISALLOW_COPY_AND_ASSIGN(GObjectDestructorFILO);
};
} // namespace libgtk2ui
#endif // CHROME_BROWSER_UI_LIBGTK2UI_G_OBJECT_DESTRUCTOR_FILO_H_

View file

@ -0,0 +1,68 @@
// 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 CHROME_BROWSER_UI_LIBGTK2UI_GTK2_SIGNAL_H_
#define CHROME_BROWSER_UI_LIBGTK2UI_GTK2_SIGNAL_H_
#include "ui/base/glib/glib_signal.h"
typedef struct _GtkWidget GtkWidget;
// These macros handle the common case where the sender object will be a
// GtkWidget*.
#define CHROMEGTK_CALLBACK_0(CLASS, RETURN, METHOD) \
CHROMEG_CALLBACK_0(CLASS, RETURN, METHOD, GtkWidget*);
#define CHROMEGTK_CALLBACK_1(CLASS, RETURN, METHOD, ARG1) \
CHROMEG_CALLBACK_1(CLASS, RETURN, METHOD, GtkWidget*, ARG1);
#define CHROMEGTK_CALLBACK_2(CLASS, RETURN, METHOD, ARG1, ARG2) \
CHROMEG_CALLBACK_2(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2);
#define CHROMEGTK_CALLBACK_3(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3) \
CHROMEG_CALLBACK_3(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3);
#define CHROMEGTK_CALLBACK_4(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, ARG4) \
CHROMEG_CALLBACK_4(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3, \
ARG4);
#define CHROMEGTK_CALLBACK_5(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, ARG4, \
ARG5) \
CHROMEG_CALLBACK_5(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3, \
ARG4, ARG5);
#define CHROMEGTK_CALLBACK_6(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, ARG4, \
ARG5, ARG6) \
CHROMEG_CALLBACK_6(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3, \
ARG4, ARG5, ARG6);
#define CHROMEGTK_VIRTUAL_CALLBACK_0(CLASS, RETURN, METHOD) \
CHROMEG_VIRTUAL_CALLBACK_0(CLASS, RETURN, METHOD, GtkWidget*);
#define CHROMEGTK_VIRTUAL_CALLBACK_1(CLASS, RETURN, METHOD, ARG1) \
CHROMEG_VIRTUAL_CALLBACK_1(CLASS, RETURN, METHOD, GtkWidget*, ARG1);
#define CHROMEGTK_VIRTUAL_CALLBACK_2(CLASS, RETURN, METHOD, ARG1, ARG2) \
CHROMEG_VIRTUAL_CALLBACK_2(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2);
#define CHROMEGTK_VIRTUAL_CALLBACK_3(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3) \
CHROMEG_VIRTUAL_CALLBACK_3(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, \
ARG3);
#define CHROMEGTK_VIRTUAL_CALLBACK_4(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, \
ARG4) \
CHROMEG_VIRTUAL_CALLBACK_4(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, \
ARG3, ARG4);
#define CHROMEGTK_VIRTUAL_CALLBACK_5(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, \
ARG4, ARG5) \
CHROMEG_VIRTUAL_CALLBACK_5(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, \
ARG3, ARG4, ARG5);
#define CHROMEGTK_VIRTUAL_CALLBACK_6(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, \
ARG4, ARG5, ARG6) \
CHROMEG_VIRTUAL_CALLBACK_6(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, \
ARG3, ARG4, ARG5, ARG6);
#endif // CHROME_BROWSER_UI_LIBGTK2UI_GTK2_SIGNAL_H_

View file

@ -0,0 +1,98 @@
// 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.
#include "chrome/browser/ui/libgtk2ui/gtk2_signal_registrar.h"
#include <glib-object.h>
#include "base/logging.h"
#include "chrome/browser/ui/libgtk2ui/g_object_destructor_filo.h"
namespace libgtk2ui {
Gtk2SignalRegistrar::Gtk2SignalRegistrar() {
}
Gtk2SignalRegistrar::~Gtk2SignalRegistrar() {
for (HandlerMap::iterator list_iter = handler_lists_.begin();
list_iter != handler_lists_.end(); ++list_iter) {
GObject* object = list_iter->first;
GObjectDestructorFILO::GetInstance()->Disconnect(
object, WeakNotifyThunk, this);
HandlerList& handlers = list_iter->second;
for (HandlerList::iterator ids_iter = handlers.begin();
ids_iter != handlers.end(); ++ids_iter) {
g_signal_handler_disconnect(object, *ids_iter);
}
}
}
glong Gtk2SignalRegistrar::Connect(gpointer instance,
const gchar* detailed_signal,
GCallback signal_handler,
gpointer data) {
return ConnectInternal(instance, detailed_signal, signal_handler, data,
false);
}
glong Gtk2SignalRegistrar::ConnectAfter(gpointer instance,
const gchar* detailed_signal,
GCallback signal_handler,
gpointer data) {
return ConnectInternal(instance, detailed_signal, signal_handler, data, true);
}
glong Gtk2SignalRegistrar::ConnectInternal(gpointer instance,
const gchar* detailed_signal,
GCallback signal_handler,
gpointer data,
bool after) {
GObject* object = G_OBJECT(instance);
HandlerMap::iterator iter = handler_lists_.find(object);
if (iter == handler_lists_.end()) {
GObjectDestructorFILO::GetInstance()->Connect(
object, WeakNotifyThunk, this);
handler_lists_[object] = HandlerList();
iter = handler_lists_.find(object);
}
glong handler_id = after ?
g_signal_connect_after(instance, detailed_signal, signal_handler, data) :
g_signal_connect(instance, detailed_signal, signal_handler, data);
iter->second.push_back(handler_id);
return handler_id;
}
void Gtk2SignalRegistrar::WeakNotify(GObject* where_the_object_was) {
HandlerMap::iterator iter = handler_lists_.find(where_the_object_was);
if (iter == handler_lists_.end()) {
NOTREACHED();
return;
}
// The signal handlers will be disconnected automatically. Just erase the
// handler id list.
handler_lists_.erase(iter);
}
void Gtk2SignalRegistrar::DisconnectAll(gpointer instance) {
GObject* object = G_OBJECT(instance);
HandlerMap::iterator iter = handler_lists_.find(object);
if (iter == handler_lists_.end())
return;
GObjectDestructorFILO::GetInstance()->Disconnect(
object, WeakNotifyThunk, this);
HandlerList& handlers = iter->second;
for (HandlerList::iterator ids_iter = handlers.begin();
ids_iter != handlers.end(); ++ids_iter) {
g_signal_handler_disconnect(object, *ids_iter);
}
handler_lists_.erase(iter);
}
} // namespace libgtk2ui

View file

@ -0,0 +1,74 @@
// 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_LIBGTK2UI_GTK2_SIGNAL_REGISTRAR_H_
#define CHROME_BROWSER_UI_LIBGTK2UI_GTK2_SIGNAL_REGISTRAR_H_
#include <glib.h>
#include <map>
#include <vector>
#include "base/basictypes.h"
typedef void (*GCallback) (void);
typedef struct _GObject GObject;
typedef struct _GtkWidget GtkWidget;
namespace libgtk2ui {
// A class that ensures that callbacks don't run on stale owner objects. Similar
// in spirit to NotificationRegistrar. Use as follows:
//
// class ChromeObject {
// public:
// ChromeObject() {
// ...
//
// signals_.Connect(widget, "event", CallbackThunk, this);
// }
//
// ...
//
// private:
// Gtk2SignalRegistrar signals_;
// };
//
// When |signals_| goes down, it will disconnect the handlers connected via
// Connect.
class Gtk2SignalRegistrar {
public:
Gtk2SignalRegistrar();
~Gtk2SignalRegistrar();
// Connect before the default handler. Returns the handler id.
glong Connect(gpointer instance, const gchar* detailed_signal,
GCallback signal_handler, gpointer data);
// Connect after the default handler. Returns the handler id.
glong ConnectAfter(gpointer instance, const gchar* detailed_signal,
GCallback signal_handler, gpointer data);
// Disconnects all signal handlers connected to |instance|.
void DisconnectAll(gpointer instance);
private:
typedef std::vector<glong> HandlerList;
typedef std::map<GObject*, HandlerList> HandlerMap;
static void WeakNotifyThunk(gpointer data, GObject* where_the_object_was) {
reinterpret_cast<Gtk2SignalRegistrar*>(data)->WeakNotify(
where_the_object_was);
}
void WeakNotify(GObject* where_the_object_was);
glong ConnectInternal(gpointer instance, const gchar* detailed_signal,
GCallback signal_handler, gpointer data, bool after);
HandlerMap handler_lists_;
DISALLOW_COPY_AND_ASSIGN(Gtk2SignalRegistrar);
};
} // namespace libgtk2ui
#endif // CHROME_BROWSER_UI_LIBGTK2UI_GTK2_SIGNAL_REGISTRAR_H_

View file

@ -0,0 +1,135 @@
// 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 "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h"
#include <gdk/gdk.h>
#include "base/basictypes.h"
#include "base/logging.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkUnPreMultiply.h"
namespace libgtk2ui {
// GDK_COLOR_RGB multiplies by 257 (= 0x10001) to distribute the bits evenly
// See: http://www.mindcontrol.org/~hplus/graphics/expand-bits.html
// To get back, we can just right shift by eight
// (or, formulated differently, i == (i*257)/256 for all i < 256).
SkColor GdkColorToSkColor(GdkColor color) {
return SkColorSetRGB(color.red >> 8, color.green >> 8, color.blue >> 8);
}
GdkColor SkColorToGdkColor(SkColor color) {
GdkColor gdk_color = {
0,
static_cast<guint16>(SkColorGetR(color) * kSkiaToGDKMultiplier),
static_cast<guint16>(SkColorGetG(color) * kSkiaToGDKMultiplier),
static_cast<guint16>(SkColorGetB(color) * kSkiaToGDKMultiplier)
};
return gdk_color;
}
const SkBitmap GdkPixbufToImageSkia(GdkPixbuf* pixbuf) {
// TODO(erg): What do we do in the case where the pixbuf fails these dchecks?
// I would prefer to use our gtk based canvas, but that would require
// recompiling half of our skia extensions with gtk support, which we can't
// do in this build.
DCHECK_EQ(GDK_COLORSPACE_RGB, gdk_pixbuf_get_colorspace(pixbuf));
int n_channels = gdk_pixbuf_get_n_channels(pixbuf);
int w = gdk_pixbuf_get_width(pixbuf);
int h = gdk_pixbuf_get_height(pixbuf);
SkBitmap ret;
ret.setConfig(SkBitmap::kARGB_8888_Config, w, h);
ret.allocPixels();
ret.eraseColor(0);
uint32_t* skia_data = static_cast<uint32_t*>(ret.getAddr(0, 0));
if (n_channels == 4) {
int total_length = w * h;
guchar* gdk_pixels = gdk_pixbuf_get_pixels(pixbuf);
// Now here's the trick: we need to convert the gdk data (which is RGBA and
// isn't premultiplied) to skia (which can be anything and premultiplied).
for (int i = 0; i < total_length; ++i, gdk_pixels += 4) {
const unsigned char& red = gdk_pixels[0];
const unsigned char& green = gdk_pixels[1];
const unsigned char& blue = gdk_pixels[2];
const unsigned char& alpha = gdk_pixels[3];
skia_data[i] = SkPreMultiplyARGB(alpha, red, green, blue);
}
} else if (n_channels == 3) {
// Because GDK makes rowstrides word aligned, we need to do something a bit
// more complex when a pixel isn't perfectly a word of memory.
int rowstride = gdk_pixbuf_get_rowstride(pixbuf);
guchar* gdk_pixels = gdk_pixbuf_get_pixels(pixbuf);
for (int y = 0; y < h; ++y) {
int row = y * rowstride;
for (int x = 0; x < w; ++x) {
guchar* pixel = gdk_pixels + row + (x * 3);
const unsigned char& red = pixel[0];
const unsigned char& green = pixel[1];
const unsigned char& blue = pixel[2];
skia_data[y * w + x] = SkPreMultiplyARGB(255, red, green, blue);
}
}
} else {
NOTREACHED();
}
return ret;
}
GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap& bitmap) {
if (bitmap.isNull())
return NULL;
SkAutoLockPixels lock_pixels(bitmap);
int width = bitmap.width();
int height = bitmap.height();
GdkPixbuf* pixbuf =
gdk_pixbuf_new(GDK_COLORSPACE_RGB, // The only colorspace gtk supports.
TRUE, // There is an alpha channel.
8,
width,
height);
// SkBitmaps are premultiplied, we need to unpremultiply them.
const int kBytesPerPixel = 4;
uint8* divided = gdk_pixbuf_get_pixels(pixbuf);
for (int y = 0, i = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32 pixel = bitmap.getAddr32(0, y)[x];
int alpha = SkColorGetA(pixel);
if (alpha != 0 && alpha != 255) {
SkColor unmultiplied = SkUnPreMultiply::PMColorToColor(pixel);
divided[i + 0] = SkColorGetR(unmultiplied);
divided[i + 1] = SkColorGetG(unmultiplied);
divided[i + 2] = SkColorGetB(unmultiplied);
divided[i + 3] = alpha;
} else {
divided[i + 0] = SkColorGetR(pixel);
divided[i + 1] = SkColorGetG(pixel);
divided[i + 2] = SkColorGetB(pixel);
divided[i + 3] = alpha;
}
i += kBytesPerPixel;
}
}
return pixbuf;
}
} // namespace libgtk2ui

View file

@ -0,0 +1,42 @@
// 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 CHROME_BROWSER_UI_LIBGTK2UI_SKIA_UTILS_GTK2_H_
#define CHROME_BROWSER_UI_LIBGTK2UI_SKIA_UTILS_GTK2_H_
#include "third_party/skia/include/core/SkColor.h"
typedef struct _GdkColor GdkColor;
typedef struct _GdkPixbuf GdkPixbuf;
class SkBitmap;
// Define a macro for creating GdkColors from RGB values. This is a macro to
// allow static construction of literals, etc. Use this like:
// GdkColor white = GDK_COLOR_RGB(0xff, 0xff, 0xff);
#define GDK_COLOR_RGB(r, g, b) {0, r * ::libgtk2ui::kSkiaToGDKMultiplier, \
g * ::libgtk2ui::kSkiaToGDKMultiplier, \
b * ::libgtk2ui::kSkiaToGDKMultiplier}
namespace libgtk2ui {
// Multiply uint8 color components by this.
const int kSkiaToGDKMultiplier = 257;
// Converts GdkColors to the ARGB layout Skia expects.
SkColor GdkColorToSkColor(GdkColor color);
// Converts ARGB to GdkColor.
GdkColor SkColorToGdkColor(SkColor color);
const SkBitmap GdkPixbufToImageSkia(GdkPixbuf* pixbuf);
// Convert and copy a SkBitmap to a GdkPixbuf. NOTE: this uses BGRAToRGBA, so
// it is an expensive operation. The returned GdkPixbuf will have a refcount of
// 1, and the caller is responsible for unrefing it when done.
GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap& bitmap);
} // namespace libgtk2ui
#endif // CHROME_BROWSER_UI_LIBGTK2UI_SKIA_UTILS_GTK2_H_

View file

@ -0,0 +1,236 @@
// Copyright 2014 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 "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
namespace {
////////////////////////////////////////////////////////////////////////////////
// Status Tray API
// The folowing describes the interface to the undocumented Windows Exporer APIs
// for manipulating with the status tray area. This code should be used with
// care as it can change with versions (even minor versions) of Windows.
// ITrayNotify is an interface describing the API for manipulating the state of
// the Windows notification area, as well as for registering for change
// notifications.
class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE
RegisterCallback(INotificationCB* callback) = 0;
virtual HRESULT STDMETHODCALLTYPE
SetPreference(const NOTIFYITEM* notify_item) = 0;
virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL enabled) = 0;
};
// ITrayNotifyWin8 is the interface that replaces ITrayNotify for newer versions
// of Windows.
class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWin8
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE
RegisterCallback(INotificationCB* callback, unsigned long*) = 0;
virtual HRESULT STDMETHODCALLTYPE UnregisterCallback(unsigned long*) = 0;
virtual HRESULT STDMETHODCALLTYPE SetPreference(NOTIFYITEM const*) = 0;
virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL) = 0;
virtual HRESULT STDMETHODCALLTYPE DoAction(BOOL) = 0;
};
const CLSID CLSID_TrayNotify = {
0x25DEAD04,
0x1EAC,
0x4911,
{0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};
} // namespace
StatusTrayStateChangerWin::StatusTrayStateChangerWin(UINT icon_id, HWND window)
: interface_version_(INTERFACE_VERSION_UNKNOWN),
icon_id_(icon_id),
window_(window) {
wchar_t module_name[MAX_PATH];
::GetModuleFileName(NULL, module_name, MAX_PATH);
file_name_ = module_name;
}
void StatusTrayStateChangerWin::EnsureTrayIconVisible() {
DCHECK(CalledOnValidThread());
if (!CreateTrayNotify()) {
VLOG(1) << "Unable to create COM object for ITrayNotify.";
return;
}
scoped_ptr<NOTIFYITEM> notify_item = RegisterCallback();
// If the user has already hidden us explicitly, try to honor their choice by
// not changing anything.
if (notify_item->preference == PREFERENCE_SHOW_NEVER)
return;
// If we are already on the taskbar, return since nothing needs to be done.
if (notify_item->preference == PREFERENCE_SHOW_ALWAYS)
return;
notify_item->preference = PREFERENCE_SHOW_ALWAYS;
SendNotifyItemUpdate(notify_item.Pass());
}
STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::AddRef() {
DCHECK(CalledOnValidThread());
return base::win::IUnknownImpl::AddRef();
}
STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::Release() {
DCHECK(CalledOnValidThread());
return base::win::IUnknownImpl::Release();
}
STDMETHODIMP StatusTrayStateChangerWin::QueryInterface(REFIID riid,
PVOID* ptr_void) {
DCHECK(CalledOnValidThread());
if (riid == __uuidof(INotificationCB)) {
*ptr_void = static_cast<INotificationCB*>(this);
AddRef();
return S_OK;
}
return base::win::IUnknownImpl::QueryInterface(riid, ptr_void);
}
STDMETHODIMP StatusTrayStateChangerWin::Notify(ULONG event,
NOTIFYITEM* notify_item) {
DCHECK(CalledOnValidThread());
DCHECK(notify_item);
if (notify_item->hwnd != window_ || notify_item->id != icon_id_ ||
base::string16(notify_item->exe_name) != file_name_) {
return S_OK;
}
notify_item_.reset(new NOTIFYITEM(*notify_item));
return S_OK;
}
StatusTrayStateChangerWin::~StatusTrayStateChangerWin() {
DCHECK(CalledOnValidThread());
}
bool StatusTrayStateChangerWin::CreateTrayNotify() {
DCHECK(CalledOnValidThread());
tray_notify_.Release(); // Release so this method can be called more than
// once.
HRESULT hr = tray_notify_.CreateInstance(CLSID_TrayNotify);
if (FAILED(hr))
return false;
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
hr = tray_notify_win8.QueryFrom(tray_notify_);
if (SUCCEEDED(hr)) {
interface_version_ = INTERFACE_VERSION_WIN8;
return true;
}
base::win::ScopedComPtr<ITrayNotify> tray_notify_legacy;
hr = tray_notify_legacy.QueryFrom(tray_notify_);
if (SUCCEEDED(hr)) {
interface_version_ = INTERFACE_VERSION_LEGACY;
return true;
}
return false;
}
scoped_ptr<NOTIFYITEM> StatusTrayStateChangerWin::RegisterCallback() {
// |notify_item_| is used to store the result of the callback from
// Explorer.exe, which happens synchronously during
// RegisterCallbackWin8 or RegisterCallbackLegacy.
DCHECK(notify_item_.get() == NULL);
// TODO(dewittj): Add UMA logging here to report if either of our strategies
// has a tendency to fail on particular versions of Windows.
switch (interface_version_) {
case INTERFACE_VERSION_WIN8:
if (!RegisterCallbackWin8())
VLOG(1) << "Unable to successfully run RegisterCallbackWin8.";
break;
case INTERFACE_VERSION_LEGACY:
if (!RegisterCallbackLegacy())
VLOG(1) << "Unable to successfully run RegisterCallbackLegacy.";
break;
default:
NOTREACHED();
}
// Adding an intermediate scoped pointer here so that |notify_item_| is reset
// to NULL.
scoped_ptr<NOTIFYITEM> rv(notify_item_.release());
return rv.Pass();
}
bool StatusTrayStateChangerWin::RegisterCallbackWin8() {
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
HRESULT hr = tray_notify_win8.QueryFrom(tray_notify_);
if (FAILED(hr))
return false;
// The following two lines cause Windows Explorer to call us back with all the
// existing tray icons and their preference. It would also presumably notify
// us if changes were made in realtime while we registered as a callback, but
// we just want to modify our own entry so we immediately unregister.
unsigned long callback_id = 0;
hr = tray_notify_win8->RegisterCallback(this, &callback_id);
tray_notify_win8->UnregisterCallback(&callback_id);
if (FAILED(hr)) {
return false;
}
return true;
}
bool StatusTrayStateChangerWin::RegisterCallbackLegacy() {
base::win::ScopedComPtr<ITrayNotify> tray_notify;
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
if (FAILED(hr)) {
return false;
}
// The following two lines cause Windows Explorer to call us back with all the
// existing tray icons and their preference. It would also presumably notify
// us if changes were made in realtime while we registered as a callback. In
// this version of the API, there can be only one registered callback so it is
// better to unregister as soon as possible.
// TODO(dewittj): Try to notice if the notification area icon customization
// window is open and postpone this call until the user closes it;
// registering the callback while the window is open can cause stale data to
// be displayed to the user.
hr = tray_notify->RegisterCallback(this);
tray_notify->RegisterCallback(NULL);
if (FAILED(hr)) {
return false;
}
return true;
}
void StatusTrayStateChangerWin::SendNotifyItemUpdate(
scoped_ptr<NOTIFYITEM> notify_item) {
if (interface_version_ == INTERFACE_VERSION_LEGACY) {
base::win::ScopedComPtr<ITrayNotify> tray_notify;
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
if (SUCCEEDED(hr))
tray_notify->SetPreference(notify_item.get());
} else if (interface_version_ == INTERFACE_VERSION_WIN8) {
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify;
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
if (SUCCEEDED(hr))
tray_notify->SetPreference(notify_item.get());
}
}

View file

@ -0,0 +1,133 @@
// Copyright 2014 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_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
#define CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
#include "base/threading/non_thread_safe.h"
#include "base/win/iunknown_impl.h"
#include "base/win/scoped_comptr.h"
// The known values for NOTIFYITEM's dwPreference member.
enum NOTIFYITEM_PREFERENCE {
// In Windows UI: "Only show notifications."
PREFERENCE_SHOW_WHEN_ACTIVE = 0,
// In Windows UI: "Hide icon and notifications."
PREFERENCE_SHOW_NEVER = 1,
// In Windows UI: "Show icon and notifications."
PREFERENCE_SHOW_ALWAYS = 2
};
// NOTIFYITEM describes an entry in Explorer's registry of status icons.
// Explorer keeps entries around for a process even after it exits.
struct NOTIFYITEM {
PWSTR exe_name; // The file name of the creating executable.
PWSTR tip; // The last hover-text value associated with this status
// item.
HICON icon; // The icon associated with this status item.
HWND hwnd; // The HWND associated with the status item.
DWORD preference; // Determines the behavior of the icon with respect to
// the taskbar. Values taken from NOTIFYITEM_PREFERENCE.
UINT id; // The ID specified by the application. (hWnd, uID) is
// unique.
GUID guid; // The GUID specified by the application, alternative to
// uID.
};
// INotificationCB is an interface that applications can implement in order to
// receive notifications about the state of the notification area manager.
class __declspec(uuid("D782CCBA-AFB0-43F1-94DB-FDA3779EACCB")) INotificationCB
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE
Notify(ULONG event, NOTIFYITEM* notify_item) = 0;
};
// A class that is capable of reading and writing the state of the notification
// area in the Windows taskbar. It is used to promote a tray icon from the
// overflow area to the taskbar, and refuses to do anything if the user has
// explicitly marked an icon to be always hidden.
class StatusTrayStateChangerWin : public INotificationCB,
public base::win::IUnknownImpl,
public base::NonThreadSafe {
public:
StatusTrayStateChangerWin(UINT icon_id, HWND window);
// Call this method to move the icon matching |icon_id| and |window| to the
// taskbar from the overflow area. This will not make any changes if the
// icon has been set to |PREFERENCE_SHOW_NEVER|, in order to comply with
// the explicit wishes/configuration of the user.
void EnsureTrayIconVisible();
// IUnknown.
virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE;
virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE;
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, PVOID*) OVERRIDE;
// INotificationCB.
// Notify is called in response to RegisterCallback for each current
// entry in Explorer's list of notification area icons, and ever time
// one of them changes, until UnregisterCallback is called or |this|
// is destroyed.
virtual HRESULT STDMETHODCALLTYPE Notify(ULONG, NOTIFYITEM*);
protected:
virtual ~StatusTrayStateChangerWin();
private:
friend class StatusTrayStateChangerWinTest;
enum InterfaceVersion {
INTERFACE_VERSION_LEGACY = 0,
INTERFACE_VERSION_WIN8,
INTERFACE_VERSION_UNKNOWN
};
// Creates an instance of TrayNotify, and ensures that it supports either
// ITrayNotify or ITrayNotifyWin8. Returns true on success.
bool CreateTrayNotify();
// Returns the NOTIFYITEM that corresponds to this executable and the
// HWND/ID pair that were used to create the StatusTrayStateChangerWin.
// Internally it calls the appropriate RegisterCallback{Win8,Legacy}.
scoped_ptr<NOTIFYITEM> RegisterCallback();
// Calls RegisterCallback with the appropriate interface required by
// different versions of Windows. This will result in |notify_item_| being
// updated when a matching item is passed into
// StatusTrayStateChangerWin::Notify.
bool RegisterCallbackWin8();
bool RegisterCallbackLegacy();
// Sends an update to Explorer with the passed NOTIFYITEM.
void SendNotifyItemUpdate(scoped_ptr<NOTIFYITEM> notify_item);
// Storing IUnknown since we will need to use different interfaces
// for different versions of Windows.
base::win::ScopedComPtr<IUnknown> tray_notify_;
InterfaceVersion interface_version_;
// The ID assigned to the notification area icon that we want to manipulate.
const UINT icon_id_;
// The HWND associated with the notification area icon that we want to
// manipulate. This is an unretained pointer, do not dereference.
const HWND window_;
// Executable name of the current program. Along with |icon_id_| and
// |window_|, this uniquely identifies a notification area entry to Explorer.
base::string16 file_name_;
// Temporary storage for the matched NOTIFYITEM. This is necessary because
// Notify doesn't return anything. The call flow looks like this:
// TrayNotify->RegisterCallback()
// ... other COM stack frames ..
// StatusTrayStateChangerWin->Notify(NOTIFYITEM);
// so we can't just return the notifyitem we're looking for.
scoped_ptr<NOTIFYITEM> notify_item_;
DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerWin);
};
#endif // CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_