diff options
author | erg@chromium.org <erg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-25 21:34:04 +0000 |
---|---|---|
committer | erg@chromium.org <erg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-25 21:34:04 +0000 |
commit | 9c8f1501112092eba0e1c411c2c196487eb61b00 (patch) | |
tree | 38a5b7452b7bb9d9a5334eb3740f4cdc1fe8da51 /chrome/browser | |
parent | 66761b95332549f825999e482c17c94675275f49 (diff) | |
download | chromium_src-9c8f1501112092eba0e1c411c2c196487eb61b00.zip chromium_src-9c8f1501112092eba0e1c411c2c196487eb61b00.tar.gz chromium_src-9c8f1501112092eba0e1c411c2c196487eb61b00.tar.bz2 |
Reapply r50859 with chromeos fixes.
GTK: First draft of the unified cut/copy/paste and +/-/Fullscreen menu items.
Adds special menu item types that allow shoving buttons into them, along with
tracking which button is selected. We now are halfway to the mocks that the
chrome-ui-leads sent out.
Review URL: http://codereview.chromium.org/2800015
BUG=45757
TEST=none
Review URL: http://codereview.chromium.org/2879002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50896 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
20 files changed, 866 insertions, 22 deletions
diff --git a/chrome/browser/back_forward_menu_model.cc b/chrome/browser/back_forward_menu_model.cc index e1bc742..5a164e7 100644 --- a/chrome/browser/back_forward_menu_model.cc +++ b/chrome/browser/back_forward_menu_model.cc @@ -115,6 +115,11 @@ bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const { return true; } +menus::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt( + int index) const { + return NULL; +} + bool BackForwardMenuModel::IsEnabledAt(int index) const { return index < GetItemCount() && !IsSeparator(index); } diff --git a/chrome/browser/back_forward_menu_model.h b/chrome/browser/back_forward_menu_model.h index 802c6a6..2315a41 100644 --- a/chrome/browser/back_forward_menu_model.h +++ b/chrome/browser/back_forward_menu_model.h @@ -53,6 +53,7 @@ class BackForwardMenuModel : public menus::MenuModel { virtual bool IsItemCheckedAt(int index) const; virtual int GetGroupIdAt(int index) const; virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const; virtual bool IsEnabledAt(int index) const; virtual MenuModel* GetSubmenuModelAt(int index) const; virtual void HighlightChangedTo(int index); diff --git a/chrome/browser/chromeos/status/clock_menu_button.h b/chrome/browser/chromeos/status/clock_menu_button.h index 07287f6..5457bec 100644 --- a/chrome/browser/chromeos/status/clock_menu_button.h +++ b/chrome/browser/chromeos/status/clock_menu_button.h @@ -41,6 +41,9 @@ class ClockMenuButton : public views::MenuButton, virtual bool IsItemCheckedAt(int index) const { return false; } virtual int GetGroupIdAt(int index) const { return 0; } virtual bool GetIconAt(int index, SkBitmap* icon) const { return false; } + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const { + return NULL; + } virtual bool IsEnabledAt(int index) const; virtual menus::MenuModel* GetSubmenuModelAt(int index) const { return NULL; } virtual void HighlightChangedTo(int index) {} diff --git a/chrome/browser/chromeos/status/feedback_menu_button.h b/chrome/browser/chromeos/status/feedback_menu_button.h index bf7bb49..1b6b1df 100644 --- a/chrome/browser/chromeos/status/feedback_menu_button.h +++ b/chrome/browser/chromeos/status/feedback_menu_button.h @@ -47,6 +47,9 @@ class FeedbackMenuButton : public StatusAreaButton, virtual bool IsItemCheckedAt(int index) const { return false; } virtual int GetGroupIdAt(int index) const { return 0; } virtual bool GetIconAt(int index, SkBitmap* icon) const { return false; } + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const { + return NULL; + } virtual bool IsEnabledAt(int index) const { return false; } virtual menus::MenuModel* GetSubmenuModelAt(int index) const { return NULL; } virtual void HighlightChangedTo(int index) {} diff --git a/chrome/browser/chromeos/status/language_menu_button.cc b/chrome/browser/chromeos/status/language_menu_button.cc index a4b7f49..c13ea7b 100644 --- a/chrome/browser/chromeos/status/language_menu_button.cc +++ b/chrome/browser/chromeos/status/language_menu_button.cc @@ -239,6 +239,11 @@ bool LanguageMenuButton::GetIconAt(int index, SkBitmap* icon) const { return false; } +menus::ButtonMenuItemModel* LanguageMenuButton::GetButtonMenuItemAt( + int index) const { + return NULL; +} + bool LanguageMenuButton::IsEnabledAt(int index) const { // Just return true so all input method names and input method propertie names // could be clicked. diff --git a/chrome/browser/chromeos/status/language_menu_button.h b/chrome/browser/chromeos/status/language_menu_button.h index a029b9d..b0483ce 100644 --- a/chrome/browser/chromeos/status/language_menu_button.h +++ b/chrome/browser/chromeos/status/language_menu_button.h @@ -43,6 +43,7 @@ class LanguageMenuButton : public views::MenuButton, virtual bool IsItemCheckedAt(int index) const; virtual int GetGroupIdAt(int index) const; virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const; virtual bool IsEnabledAt(int index) const; virtual menus::MenuModel* GetSubmenuModelAt(int index) const; virtual void HighlightChangedTo(int index); diff --git a/chrome/browser/chromeos/status/network_menu_button.h b/chrome/browser/chromeos/status/network_menu_button.h index aab1c05..c38c35e 100644 --- a/chrome/browser/chromeos/status/network_menu_button.h +++ b/chrome/browser/chromeos/status/network_menu_button.h @@ -71,6 +71,9 @@ class NetworkMenuButton : public StatusAreaButton, virtual bool IsItemCheckedAt(int index) const; virtual int GetGroupIdAt(int index) const { return 0; } virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const { + return NULL; + } virtual bool IsEnabledAt(int index) const; virtual menus::MenuModel* GetSubmenuModelAt(int index) const { return NULL; } virtual void HighlightChangedTo(int index) {} diff --git a/chrome/browser/chromeos/status/power_menu_button.h b/chrome/browser/chromeos/status/power_menu_button.h index 15601bc..413b744 100644 --- a/chrome/browser/chromeos/status/power_menu_button.h +++ b/chrome/browser/chromeos/status/power_menu_button.h @@ -37,6 +37,9 @@ class PowerMenuButton : public StatusAreaButton, virtual bool IsItemCheckedAt(int index) const { return false; } virtual int GetGroupIdAt(int index) const { return 0; } virtual bool GetIconAt(int index, SkBitmap* icon) const { return false; } + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const { + return NULL; + } virtual bool IsEnabledAt(int index) const { return false; } virtual menus::MenuModel* GetSubmenuModelAt(int index) const { return NULL; } virtual void HighlightChangedTo(int index) {} diff --git a/chrome/browser/gtk/gtk_custom_menu.cc b/chrome/browser/gtk/gtk_custom_menu.cc new file mode 100644 index 0000000..d666091 --- /dev/null +++ b/chrome/browser/gtk/gtk_custom_menu.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2010 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/gtk/gtk_custom_menu.h" + +#include "chrome/browser/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 = child->parent; + 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 = menu_item->parent; + + 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. +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; + } + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + button_release_event(widget, event); +} + +// Manually forward button press events to the menu item (and then do what we'd +// do normally). +static gboolean gtk_custom_menu_motion_notify(GtkWidget* widget, + GdkEventMotion* event) { + GtkWidget* menu_item = gtk_menu_shell_get_item( + GTK_MENU_SHELL(widget), (GdkEvent*)event); + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + gtk_custom_menu_item_receive_motion_event(GTK_CUSTOM_MENU_ITEM(menu_item), + event->x, event->y); + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + motion_notify_event(widget, event); +} + +static void gtk_custom_menu_move_current(GtkMenuShell* menu_shell, + GtkMenuDirectionType direction) { + // If the currently selected item is custom, we give it first chance to catch + // up/down events. + + // TODO(erg): We are breaking a GSEAL by directly accessing this. We'll need + // to fix this by the time gtk3 comes out. + GtkWidget* menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item; + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + switch (direction) { + case GTK_MENU_DIR_PREV: + case GTK_MENU_DIR_NEXT: + if (gtk_custom_menu_item_handle_move(GTK_CUSTOM_MENU_ITEM(menu_item), + direction)) + return; + break; + default: + break; + } + } + + GTK_MENU_SHELL_CLASS(gtk_custom_menu_parent_class)-> + move_current(menu_shell, direction); + + // In the case of hitting PREV and transitioning to a custom menu, we want to + // make sure we're selecting the final item in the list, not the first one. + menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item; + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + gtk_custom_menu_item_select_item_by_direction( + GTK_CUSTOM_MENU_ITEM(menu_item), direction); + } +} + +static void gtk_custom_menu_init(GtkCustomMenu* menu) { +} + +static void gtk_custom_menu_class_init(GtkCustomMenuClass* klass) { + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + GtkMenuShellClass* menu_shell_class = GTK_MENU_SHELL_CLASS(klass); + + widget_class->button_press_event = gtk_custom_menu_button_press; + widget_class->button_release_event = gtk_custom_menu_button_release; + widget_class->motion_notify_event = gtk_custom_menu_motion_notify; + + menu_shell_class->move_current = gtk_custom_menu_move_current; +} + +GtkWidget* gtk_custom_menu_new() { + return GTK_WIDGET(g_object_new(GTK_TYPE_CUSTOM_MENU, NULL)); +} diff --git a/chrome/browser/gtk/gtk_custom_menu.h b/chrome/browser/gtk/gtk_custom_menu.h new file mode 100644 index 0000000..a2fc757 --- /dev/null +++ b/chrome/browser/gtk/gtk_custom_menu.h @@ -0,0 +1,53 @@ +// Copyright (c) 2010 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_GTK_GTK_CUSTOM_MENU_H_ +#define CHROME_BROWSER_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 <gdk/gdk.h> +#include <gtk/gtk.h> +#include <gtk/gtkmenuitem.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_GTK_GTK_CUSTOM_MENU_H_ diff --git a/chrome/browser/gtk/gtk_custom_menu_item.cc b/chrome/browser/gtk/gtk_custom_menu_item.cc new file mode 100644 index 0000000..f925c48 --- /dev/null +++ b/chrome/browser/gtk/gtk_custom_menu_item.cc @@ -0,0 +1,282 @@ +// Copyright (c) 2010 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/gtk/gtk_custom_menu_item.h" + +#include "base/logging.h" +#include "chrome/browser/gtk/gtk_custom_menu.h" + +enum { + 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); + + item->currently_selected_button = selected; + if (item->currently_selected_button) + gtk_widget_set_state(item->currently_selected_button, GTK_STATE_SELECTED); + } +} + +static void gtk_custom_menu_item_finalize(GObject *object); +static gint gtk_custom_menu_item_expose(GtkWidget* widget, + GdkEventExpose* event); +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_style_set(GtkCustomMenuItem* item, + GtkStyle* old_style) { + // Because several popular themes have no idea about styling buttons in menus + // (it's sort of a weird concept) and look like crap, we manually apply the + // menu item's prelight information to the button. + GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(item)); + + for (GList* i = item->button_widgets; i; i = g_list_next(i)) { + // Set the button prelight colors. + GtkWidget* button = GTK_WIDGET(i->data); + gtk_widget_modify_fg(button, GTK_STATE_PRELIGHT, + &style->fg[GTK_STATE_PRELIGHT]); + gtk_widget_modify_bg(button, GTK_STATE_PRELIGHT, + &style->bg[GTK_STATE_PRELIGHT]); + gtk_widget_modify_text(button, GTK_STATE_PRELIGHT, + &style->text[GTK_STATE_PRELIGHT]); + gtk_widget_modify_base(button, GTK_STATE_PRELIGHT, + &style->base[GTK_STATE_PRELIGHT]); + } +} + +static void gtk_custom_menu_item_init(GtkCustomMenuItem* item) { + 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, "style-set", + G_CALLBACK(gtk_custom_menu_item_style_set), NULL); + + 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_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + gtk_marshal_NONE__INT, + G_TYPE_NONE, 1, GTK_TYPE_INT); +} + +static void gtk_custom_menu_item_finalize(GObject *object) { + GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM(object); + 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_VISIBLE(widget) && + GTK_WIDGET_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_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)); + + char* markup = g_markup_printf_escaped("<b>%s</b>", title); + gtk_label_set_markup(GTK_LABEL(item->label), markup); + g_free(markup); + + 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->button_widgets = g_list_append(menu_item->button_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); +} + +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 = current_widget->allocation; + 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; +} diff --git a/chrome/browser/gtk/gtk_custom_menu_item.h b/chrome/browser/gtk/gtk_custom_menu_item.h new file mode 100644 index 0000000..a7c152b --- /dev/null +++ b/chrome/browser/gtk/gtk_custom_menu_item.h @@ -0,0 +1,118 @@ +// Copyright (c) 2010 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_GTK_GTK_CUSTOM_MENU_ITEM_H_ +#define CHROME_BROWSER_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 <gdk/gdk.h> +#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; + + // Possible button widgets + 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 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); + +G_END_DECLS + +#endif // CHROME_BROWSER_GTK_GTK_CUSTOM_MENU_ITEM_H_ diff --git a/chrome/browser/gtk/gtk_util.cc b/chrome/browser/gtk/gtk_util.cc index 1bca63ed..62f1af0 100644 --- a/chrome/browser/gtk/gtk_util.cc +++ b/chrome/browser/gtk/gtk_util.cc @@ -449,7 +449,12 @@ GtkWidget* CenterWidgetInHBox(GtkWidget* hbox, GtkWidget* widget, return centering_vbox; } -std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { +namespace { + +// Common implementation of ConvertAcceleratorsFromWindowsStyle() and +// RemoveWindowsStyleAccelerators(). +std::string ConvertAmperstandsTo(const std::string& label, + const std::string& target) { std::string ret; ret.reserve(label.length() * 2); for (size_t i = 0; i < label.length(); ++i) { @@ -461,7 +466,7 @@ std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { ret.push_back(label[i]); ++i; } else { - ret.push_back('_'); + ret.append(target); } } else { ret.push_back(label[i]); @@ -471,6 +476,16 @@ std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { return ret; } +} // namespace + +std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { + return ConvertAmperstandsTo(label, "_"); +} + +std::string RemoveWindowsStyleAccelerators(const std::string& label) { + return ConvertAmperstandsTo(label, ""); +} + bool IsScreenComposited() { GdkScreen* screen = gdk_screen_get_default(); return gdk_screen_is_composited(screen) == TRUE; diff --git a/chrome/browser/gtk/gtk_util.h b/chrome/browser/gtk/gtk_util.h index 7852493..3954729 100644 --- a/chrome/browser/gtk/gtk_util.h +++ b/chrome/browser/gtk/gtk_util.h @@ -141,6 +141,9 @@ GtkWidget* CenterWidgetInHBox(GtkWidget* hbox, GtkWidget* widget, // accelerators. Windows uses & with && as an escape for &.) std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label); +// Removes the "&" accelerators from a Windows label. +std::string RemoveWindowsStyleAccelerators(const std::string& label); + // Returns true if the screen is composited, false otherwise. bool IsScreenComposited(); diff --git a/chrome/browser/gtk/menu_gtk.cc b/chrome/browser/gtk/menu_gtk.cc index 6c24fd8..104b39f 100644 --- a/chrome/browser/gtk/menu_gtk.cc +++ b/chrome/browser/gtk/menu_gtk.cc @@ -8,17 +8,22 @@ #include "app/l10n_util.h" #include "app/menus/accelerator_gtk.h" +#include "app/menus/button_menu_item_model.h" #include "app/menus/menu_model.h" +#include "app/resource_bundle.h" #include "base/i18n/rtl.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/stl_util-inl.h" #include "base/utf_string_conversions.h" +#include "chrome/browser/gtk/gtk_custom_menu.h" +#include "chrome/browser/gtk/gtk_custom_menu_item.h" #include "chrome/browser/gtk/gtk_util.h" #include "gfx/gtk_util.h" #include "third_party/skia/include/core/SkBitmap.h" using gtk_util::ConvertAcceleratorsFromWindowsStyle; +using gtk_util::RemoveWindowsStyleAccelerators; bool MenuGtk::block_activation_ = false; @@ -99,7 +104,7 @@ MenuGtk::MenuGtk(MenuGtk::Delegate* delegate, : delegate_(delegate), model_(model), dummy_accel_group_(gtk_accel_group_new()), - menu_(gtk_menu_new()), + menu_(gtk_custom_menu_new()), factory_(this) { DCHECK(model); g_object_ref_sink(menu_); @@ -156,15 +161,20 @@ GtkWidget* MenuGtk::AppendSeparator() { } GtkWidget* MenuGtk::AppendMenuItem(int command_id, GtkWidget* menu_item) { - return AppendMenuItemToMenu(command_id, menu_item, menu_); + return AppendMenuItemToMenu(command_id, menu_item, menu_, true); } GtkWidget* MenuGtk::AppendMenuItemToMenu(int command_id, GtkWidget* menu_item, - GtkWidget* menu) { - SetMenuItemID(menu_item, command_id); - g_signal_connect(menu_item, "activate", - G_CALLBACK(OnMenuItemActivated), this); + GtkWidget* menu, + bool connect_to_activate) { + // Native menu items do their own thing, so only selectively listen for the + // activate signal. + if (connect_to_activate) { + SetMenuItemID(menu_item, command_id); + g_signal_connect(menu_item, "activate", + G_CALLBACK(OnMenuItemActivated), this); + } gtk_widget_show(menu_item); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); @@ -231,6 +241,7 @@ void MenuGtk::BuildSubmenuFromModel(menus::MenuModel* model, GtkWidget* menu) { SkBitmap icon; std::string label = ConvertAcceleratorsFromWindowsStyle(UTF16ToUTF8(model->GetLabelAt(i))); + bool connect_to_activate = true; switch (model->GetTypeAt(i)) { case menus::MenuModel::TYPE_SEPARATOR: @@ -255,6 +266,15 @@ void MenuGtk::BuildSubmenuFromModel(menus::MenuModel* model, GtkWidget* menu) { } break; } + case menus::MenuModel::TYPE_BUTTON_ITEM: { + menus::ButtonMenuItemModel* button_menu_item_model = + model->GetButtonMenuItemAt(i); + + menu_item = BuildButtomMenuItem(button_menu_item_model); + + connect_to_activate = false; + break; + } case menus::MenuModel::TYPE_SUBMENU: case menus::MenuModel::TYPE_COMMAND: if (model->GetIconAt(i, &icon)) @@ -285,7 +305,7 @@ void MenuGtk::BuildSubmenuFromModel(menus::MenuModel* model, GtkWidget* menu) { g_object_set_data(G_OBJECT(menu_item), "model", reinterpret_cast<void*>(model)); - AppendMenuItemToMenu(i, menu_item, menu); + AppendMenuItemToMenu(i, menu_item, menu, connect_to_activate); if (model->IsLabelDynamicAt(i)) { g_signal_connect(menu, "show", G_CALLBACK(OnSubmenuShow), @@ -296,6 +316,50 @@ void MenuGtk::BuildSubmenuFromModel(menus::MenuModel* model, GtkWidget* menu) { } } +GtkWidget* MenuGtk::BuildButtomMenuItem(menus::ButtonMenuItemModel* model) { + GtkWidget* menu_item = gtk_custom_menu_item_new( + RemoveWindowsStyleAccelerators(UTF16ToUTF8(model->label())).c_str()); + for (int i = 0; i < model->GetItemCount(); ++i) { + switch (model->GetTypeAt(i)) { + case menus::ButtonMenuItemModel::TYPE_SPACE: { + gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item)); + break; + } + case menus::ButtonMenuItemModel::TYPE_BUTTON: { + GtkWidget* 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)) { + // TODO(erg): This should go through the GtkThemeProvider so we can + // get a version tinted to label color. + gtk_button_set_image( + GTK_BUTTON(button), + gtk_image_new_from_pixbuf( + ResourceBundle::GetSharedInstance(). + GetPixbufNamed(icon_idr))); + } else { + gtk_button_set_label( + GTK_BUTTON(button), + RemoveWindowsStyleAccelerators( + UTF16ToUTF8(model->GetLabelAt(i))).c_str()); + } + + // TODO(erg): Set up dynamic labels here. + break; + } + } + } + + // Set up the callback to the model for when it is clicked. + g_object_set_data(G_OBJECT(menu_item), "button-model", + reinterpret_cast<void*>(model)); + g_signal_connect(menu_item, "button-pushed", + G_CALLBACK(OnMenuButtonPressed), this); + return menu_item; +} + // static void MenuGtk::OnMenuItemActivated(GtkMenuItem* menuitem, MenuGtk* menu) { if (block_activation_) @@ -324,6 +388,19 @@ void MenuGtk::OnMenuItemActivated(GtkMenuItem* menuitem, MenuGtk* menu) { menu->ExecuteCommand(model, id); } +void MenuGtk::OnMenuButtonPressed(GtkMenuItem* menu_item, int command_id, + MenuGtk* menu) { + menus::ButtonMenuItemModel* model = + reinterpret_cast<menus::ButtonMenuItemModel*>( + g_object_get_data(G_OBJECT(menu_item), "button-model")); + if (model) { + if (menu->delegate_) + menu->delegate_->CommandWillBeExecuted(); + + model->ActivatedCommand(command_id); + } +} + // static void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu, int* x, diff --git a/chrome/browser/gtk/menu_gtk.h b/chrome/browser/gtk/menu_gtk.h index 0612d4c..a9a703a 100644 --- a/chrome/browser/gtk/menu_gtk.h +++ b/chrome/browser/gtk/menu_gtk.h @@ -16,6 +16,7 @@ class SkBitmap; namespace menus { +class ButtonMenuItemModel; class MenuModel; } @@ -60,7 +61,8 @@ class MenuGtk { GtkWidget* AppendMenuItem(int command_id, GtkWidget* menu_item); GtkWidget* AppendMenuItemToMenu(int command_id, GtkWidget* menu_item, - GtkWidget* menu); + GtkWidget* menu, + bool connect_to_activate); // Displays the menu. |timestamp| is the time of activation. The popup is // statically positioned at |widget|. @@ -114,6 +116,8 @@ class MenuGtk { void BuildMenuFromModel(); // Implementation of the above; called recursively. void BuildSubmenuFromModel(menus::MenuModel* model, GtkWidget* menu); + // Builds a menu item with buttons in it from the data in the model. + GtkWidget* BuildButtomMenuItem(menus::ButtonMenuItemModel* model); // Contains implementation for OnMenuShow. void UpdateMenu(); @@ -123,6 +127,10 @@ class MenuGtk { // Callback for when a menu item is clicked. static void OnMenuItemActivated(GtkMenuItem* menuitem, MenuGtk* menu); + // Called when one of the buttons are pressed. + static void OnMenuButtonPressed(GtkMenuItem* menuitem, int command_id, + MenuGtk* menu); + // Sets the check mark and enabled/disabled state on our menu items. static void SetMenuItemInfo(GtkWidget* widget, void* raw_menu); diff --git a/chrome/browser/gtk/notifications/notification_options_menu_model.cc b/chrome/browser/gtk/notifications/notification_options_menu_model.cc index 0cbb81e..9b5225a 100644 --- a/chrome/browser/gtk/notifications/notification_options_menu_model.cc +++ b/chrome/browser/gtk/notifications/notification_options_menu_model.cc @@ -64,6 +64,11 @@ bool NotificationOptionsMenuModel::GetIconAt(int index, SkBitmap* icon) const { return false; } +menus::ButtonMenuItemModel* NotificationOptionsMenuModel::GetButtonMenuItemAt( + int index) const { + return NULL; +} + bool NotificationOptionsMenuModel::IsEnabledAt(int index) const { return true; } diff --git a/chrome/browser/gtk/notifications/notification_options_menu_model.h b/chrome/browser/gtk/notifications/notification_options_menu_model.h index f4b67f3..a88adb7 100644 --- a/chrome/browser/gtk/notifications/notification_options_menu_model.h +++ b/chrome/browser/gtk/notifications/notification_options_menu_model.h @@ -27,6 +27,7 @@ class NotificationOptionsMenuModel : public menus::MenuModel { virtual bool IsItemCheckedAt(int index) const; virtual int GetGroupIdAt(int index) const; virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const; virtual bool IsEnabledAt(int index) const; virtual MenuModel* GetSubmenuModelAt(int index) const; virtual void HighlightChangedTo(int index); diff --git a/chrome/browser/wrench_menu_model.cc b/chrome/browser/wrench_menu_model.cc index d4c94f0..326e12f 100644 --- a/chrome/browser/wrench_menu_model.cc +++ b/chrome/browser/wrench_menu_model.cc @@ -5,20 +5,26 @@ #include "chrome/browser/wrench_menu_model.h" #include <algorithm> +#include <cmath> #include "app/l10n_util.h" +#include "app/menus/button_menu_item_model.h" #include "app/resource_bundle.h" #include "base/command_line.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/browser.h" +#include "chrome/browser/host_zoom_map.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/defaults.h" #include "chrome/browser/page_menu_model.h" #include "chrome/browser/profile.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/sync_ui_util.h" +#include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/upgrade_detector.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" @@ -61,8 +67,13 @@ void ToolsMenuModel::Build(Browser* browser) { WrenchMenuModel::WrenchMenuModel(menus::SimpleMenuModel::Delegate* delegate, Browser* browser) : menus::SimpleMenuModel(delegate), + delegate_(delegate), browser_(browser) { Build(); + UpdateZoomControls(); + + registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED, + Source<Profile>(browser_->profile())); } WrenchMenuModel::~WrenchMenuModel() { @@ -125,15 +136,59 @@ bool WrenchMenuModel::GetIconAt(int index, SkBitmap* icon) const { return false; } +bool WrenchMenuModel::IsLabelForCommandIdDynamic(int command_id) const { + return false; +} + +string16 WrenchMenuModel::GetLabelForCommandId(int command_id) const { + // TODO(erg): Hook up percentage calculation once I add that widget. + return string16(); +} + +void WrenchMenuModel::ExecuteCommand(int command_id) { + if (delegate_) + delegate_->ExecuteCommand(command_id); +} + +void WrenchMenuModel::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK_EQ(NotificationType::ZOOM_LEVEL_CHANGED, type.value); + UpdateZoomControls(); +} + void WrenchMenuModel::Build() { AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB); AddItemWithStringId(IDC_NEW_WINDOW, IDS_NEW_WINDOW); AddItemWithStringId(IDC_NEW_INCOGNITO_WINDOW, IDS_NEW_INCOGNITO_WINDOW); AddSeparator(); +#if defined(OS_LINUX) + edit_menu_item_model_.reset(new menus::ButtonMenuItemModel(IDS_EDIT, this)); + edit_menu_item_model_->AddItemWithStringId(IDC_CUT, IDS_CUT); + edit_menu_item_model_->AddItemWithStringId(IDC_COPY, IDS_COPY); + edit_menu_item_model_->AddItemWithStringId(IDC_PASTE, IDS_PASTE); + AddButtonItem(0, edit_menu_item_model_.get()); +#else + // TODO(port): Move to the above. CreateCutCopyPaste(); +#endif + AddSeparator(); +#if defined(OS_LINUX) + zoom_menu_item_model_.reset( + new menus::ButtonMenuItemModel(IDS_ZOOM_MENU, this)); + zoom_menu_item_model_->AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS2); + zoom_menu_item_model_->AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS2); + zoom_menu_item_model_->AddSpace(); + zoom_menu_item_model_->AddItemWithImage( + IDC_FULLSCREEN, IDR_FULLSCREEN_MENU_BUTTON); + AddButtonItem(0, zoom_menu_item_model_.get()); +#else + // TODO(port): Move to the above. CreateZoomFullscreen(); +#endif + AddSeparator(); AddItemWithStringId(IDC_SAVE_PAGE, IDS_SAVE_PAGE); AddItemWithStringId(IDC_FIND, IDS_FIND); @@ -188,6 +243,37 @@ void WrenchMenuModel::CreateZoomFullscreen() { AddItemWithStringId(IDC_FULLSCREEN, IDS_FULLSCREEN); } +void WrenchMenuModel::UpdateZoomControls() { + bool enable_increment, enable_decrement; + int zoom_percent = + static_cast<int>(GetZoom(&enable_increment, &enable_decrement) * 100); + + // TODO(erg): Route the stringified zoom_percent through + // GetLabelForCommandId(). Also make a way to use enable_increment/decrement. + zoom_label_ = l10n_util::GetStringFUTF16( + IDS_ZOOM_PERCENT, IntToString16(zoom_percent)); +} + +double WrenchMenuModel::GetZoom(bool* enable_increment, + bool* enable_decrement) { + TabContents* selected_tab = browser_->GetSelectedTabContents(); + *enable_decrement = *enable_increment = false; + if (!selected_tab) + return 1; + + HostZoomMap* zoom_map = selected_tab->profile()->GetHostZoomMap(); + if (!zoom_map) + return 1; + + // This code comes from WebViewImpl::setZoomLevel. + int zoom_level = zoom_map->GetZoomLevel(selected_tab->GetURL()); + double value = static_cast<double>( + std::max(std::min(std::pow(1.2, zoom_level), 3.0), .5)); + *enable_decrement = (value != .5); + *enable_increment = (value != 3.0); + return value; +} + string16 WrenchMenuModel::GetSyncMenuLabel() const { return sync_ui_util::GetSyncMenuLabel( browser_->profile()->GetOriginalProfile()->GetProfileSyncService()); diff --git a/chrome/browser/wrench_menu_model.h b/chrome/browser/wrench_menu_model.h index f86a170..5b7d2d5 100644 --- a/chrome/browser/wrench_menu_model.h +++ b/chrome/browser/wrench_menu_model.h @@ -11,14 +11,19 @@ #include "app/menus/simple_menu_model.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" class Browser; class EncodingMenuModel; +namespace menus { +class ButtonMenuItemModel; +} // namespace menus + class ToolsMenuModel : public menus::SimpleMenuModel { public: - explicit ToolsMenuModel(menus::SimpleMenuModel::Delegate* delegate, - Browser* browser); + ToolsMenuModel(menus::SimpleMenuModel::Delegate* delegate, Browser* browser); virtual ~ToolsMenuModel(); private: @@ -30,10 +35,12 @@ class ToolsMenuModel : public menus::SimpleMenuModel { }; // A menu model that builds the contents of the wrench menu. -class WrenchMenuModel : public menus::SimpleMenuModel { +class WrenchMenuModel : public menus::SimpleMenuModel, + public menus::ButtonMenuItemModel::Delegate, + public NotificationObserver { public: - explicit WrenchMenuModel(menus::SimpleMenuModel::Delegate* delegate, - Browser* browser); + WrenchMenuModel(menus::SimpleMenuModel::Delegate* delegate, + Browser* browser); virtual ~WrenchMenuModel(); // Returns true if the WrenchMenuModel is enabled. @@ -45,27 +52,48 @@ class WrenchMenuModel : public menus::SimpleMenuModel { virtual bool HasIcons() const { return true; } virtual bool GetIconAt(int index, SkBitmap* icon) const; - protected: - // Adds the cut/copy/paste items to the menu. The default implementation adds - // three real menu items, while platform specific subclasses add their own - // native monstrosities. - virtual void CreateCutCopyPaste(); + // Overridden from menus::ButtonMenuItemModel::Delegate: + virtual bool IsLabelForCommandIdDynamic(int command_id) const; + virtual string16 GetLabelForCommandId(int command_id) const; + virtual void ExecuteCommand(int command_id); - // Adds the zoom/fullscreen items to the menu. Like CreateCutCopyPaste(). - virtual void CreateZoomFullscreen(); + // Overridden from NotificationObserver: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); private: void Build(); + // Adds custom items to the menu. Deprecated in favor of a cross platform + // model for button items. + void CreateCutCopyPaste(); + void CreateZoomFullscreen(); + + // Calculates |zoom_label_| in response to a zoom change. + void UpdateZoomControls(); + double GetZoom(bool* enable_increment, bool* enable_decrement); + string16 GetSyncMenuLabel() const; string16 GetAboutEntryMenuLabel() const; bool IsDynamicItem(int index) const; + // Models for the special menu items with buttons. + scoped_ptr<menus::ButtonMenuItemModel> edit_menu_item_model_; + scoped_ptr<menus::ButtonMenuItemModel> zoom_menu_item_model_; + + // Label of the zoom label in the zoom menu item. + string16 zoom_label_; + // Tools menu. scoped_ptr<ToolsMenuModel> tools_menu_model_; + menus::SimpleMenuModel::Delegate* delegate_; // weak + Browser* browser_; // weak + NotificationRegistrar registrar_; + DISALLOW_COPY_AND_ASSIGN(WrenchMenuModel); }; |