diff options
28 files changed, 1088 insertions, 32 deletions
diff --git a/app/app_base.gypi b/app/app_base.gypi index facbd4c..4cd5e66 100644 --- a/app/app_base.gypi +++ b/app/app_base.gypi @@ -159,6 +159,8 @@ 'menus/accelerator.h', 'menus/accelerator_gtk.h', 'menus/accelerator_cocoa.h', + 'menus/button_menu_item_model.cc', + 'menus/button_menu_item_model.h', 'menus/menu_model.cc', 'menus/menu_model.h', 'menus/simple_menu_model.cc', diff --git a/app/menus/button_menu_item_model.cc b/app/menus/button_menu_item_model.cc new file mode 100644 index 0000000..c5064fb --- /dev/null +++ b/app/menus/button_menu_item_model.cc @@ -0,0 +1,65 @@ +// 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 "app/menus/button_menu_item_model.h" + +#include "app/l10n_util.h" + +namespace menus { + +ButtonMenuItemModel::ButtonMenuItemModel( + int string_id, + ButtonMenuItemModel::Delegate* delegate) + : item_label_(l10n_util::GetStringUTF16(string_id)), + delegate_(delegate) { +} + +void ButtonMenuItemModel::AddItemWithStringId(int command_id, int string_id) { + Item item = { command_id, TYPE_BUTTON, l10n_util::GetStringUTF16(string_id), + SIDE_BOTH, -1 }; + items_.push_back(item); +} + +void ButtonMenuItemModel::AddItemWithImage(int command_id, + int icon_idr) { + Item item = { command_id, TYPE_BUTTON, string16(), SIDE_BOTH, icon_idr }; + items_.push_back(item); +} + +void ButtonMenuItemModel::AddSpace() { + Item item = { 0, TYPE_SPACE, string16(), SIDE_NONE, -1 }; + items_.push_back(item); +} + +int ButtonMenuItemModel::GetItemCount() const { + return static_cast<int>(items_.size()); +} + +ButtonMenuItemModel::ButtonType ButtonMenuItemModel::GetTypeAt( + int index) const { + return items_[index].type; +} + +int ButtonMenuItemModel::GetCommandIdAt(int index) const { + return items_[index].command_id; +} + +const string16& ButtonMenuItemModel::GetLabelAt(int index) const { + return items_[index].label; +} + +bool ButtonMenuItemModel::GetIconAt(int index, int* icon_idr) const { + if (items_[index].icon_idr == -1) + return false; + + *icon_idr = items_[index].icon_idr; + return true; +} + +void ButtonMenuItemModel::ActivatedCommand(int command_id) { + if (delegate_) + delegate_->ExecuteCommand(command_id); +} + +} // namespace menus diff --git a/app/menus/button_menu_item_model.h b/app/menus/button_menu_item_model.h new file mode 100644 index 0000000..b8418d1 --- /dev/null +++ b/app/menus/button_menu_item_model.h @@ -0,0 +1,103 @@ +// 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 APP_MENUS_BUTTON_MENU_ITEM_MODEL_H_ +#define APP_MENUS_BUTTON_MENU_ITEM_MODEL_H_ + +#include <vector> + +#include "base/string16.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace menus { + +// A model representing the rows of buttons that should be inserted in a button +// containing menu item. +// +// TODO(erg): There are still two major pieces missing from this model. It +// needs to be able to group buttons together so they all have the same +// width. ButtonSides needs to be used to communicate how buttons are squashed +// together. +class ButtonMenuItemModel { + public: + // Types of buttons. + enum ButtonType { + TYPE_SPACE, + TYPE_BUTTON + }; + + // Which sides of the button are visible. + enum ButtonSides { + SIDE_NONE = 0, + SIDE_LEFT = 1 << 0, + SIDE_RIGHT = 1 << 1, + SIDE_BOTH = SIDE_LEFT | SIDE_RIGHT + }; + + class Delegate { + public: + // Some command ids have labels that change over time. + virtual bool IsLabelForCommandIdDynamic(int command_id) const { + return false; + } + virtual string16 GetLabelForCommandId(int command_id) const { + return string16(); + } + + // Performs the action associated with the specified command id. + virtual void ExecuteCommand(int command_id) = 0; + }; + + ButtonMenuItemModel(int string_id, ButtonMenuItemModel::Delegate* delegate); + + // Adds a button that will emit |command_id|. + void AddItemWithStringId(int command_id, int string_id); + + // Adds a button that has an icon instead of a label. + void AddItemWithImage(int command_id, int icon_idr); + + // Adds a small horizontal space. + void AddSpace(); + + // Returns the number of items for iteration. + int GetItemCount() const; + + // Returns what kind of item is at |index|. + ButtonType GetTypeAt(int index) const; + + // Changes a position into a command ID. + int GetCommandIdAt(int index) const; + + const string16& GetLabelAt(int index) const; + + // If the button at |index| should have an icon instead, returns true and + // sets the IDR |icon|. + bool GetIconAt(int index, int* icon) const; + + // Called from implementations. + void ActivatedCommand(int command_id); + + const string16& label() const { return item_label_; } + + private: + // The non-clickable label to the left of the buttons. + string16 item_label_; + + struct Item { + int command_id; + ButtonType type; + string16 label; + int sides; + int icon_idr; + }; + std::vector<Item> items_; + + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(ButtonMenuItemModel); +}; + +} // namespace menus + +#endif // APP_MENUS_BUTTON_MENU_ITEM_MODEL_H_ diff --git a/app/menus/menu_model.h b/app/menus/menu_model.h index 50f2e54..f14c8b5 100644 --- a/app/menus/menu_model.h +++ b/app/menus/menu_model.h @@ -20,6 +20,7 @@ class Font; namespace menus { class Accelerator; +class ButtonMenuItemModel; // An interface implemented by an object that provides the content of a menu. class MenuModel { @@ -32,6 +33,7 @@ class MenuModel { TYPE_CHECK, TYPE_RADIO, TYPE_SEPARATOR, + TYPE_BUTTON_ITEM, TYPE_SUBMENU }; @@ -85,6 +87,9 @@ class MenuModel { // is an icon, false otherwise. virtual bool GetIconAt(int index, SkBitmap* icon) const = 0; + // Returns the model for a menu item with a line of buttons at |index|. + virtual ButtonMenuItemModel* GetButtonMenuItemAt(int index) const = 0; + // Returns the enabled state of the item at the specified index. virtual bool IsEnabledAt(int index) const = 0; diff --git a/app/menus/simple_menu_model.cc b/app/menus/simple_menu_model.cc index 8631666..3e51986 100644 --- a/app/menus/simple_menu_model.cc +++ b/app/menus/simple_menu_model.cc @@ -20,7 +20,7 @@ SimpleMenuModel::~SimpleMenuModel() { } void SimpleMenuModel::AddItem(int command_id, const string16& label) { - Item item = { command_id, label, SkBitmap(), TYPE_COMMAND, -1, NULL }; + Item item = { command_id, label, SkBitmap(), TYPE_COMMAND, -1, NULL, NULL }; AppendItem(item); } @@ -30,7 +30,7 @@ void SimpleMenuModel::AddItemWithStringId(int command_id, int string_id) { void SimpleMenuModel::AddSeparator() { Item item = { kSeparatorId, string16(), SkBitmap(), TYPE_SEPARATOR, -1, - NULL }; + NULL, NULL }; AppendItem(item); } @@ -45,7 +45,8 @@ void SimpleMenuModel::AddCheckItemWithStringId(int command_id, int string_id) { void SimpleMenuModel::AddRadioItem(int command_id, const string16& label, int group_id) { - Item item = { command_id, label, SkBitmap(), TYPE_RADIO, group_id, NULL }; + Item item = { command_id, label, SkBitmap(), TYPE_RADIO, group_id, NULL, + NULL }; AppendItem(item); } @@ -54,9 +55,15 @@ void SimpleMenuModel::AddRadioItemWithStringId(int command_id, int string_id, AddRadioItem(command_id, l10n_util::GetStringUTF16(string_id), group_id); } +void SimpleMenuModel::AddButtonItem(int command_id, + ButtonMenuItemModel* model) { + Item item = { 0, string16(), SkBitmap(), TYPE_BUTTON_ITEM, -1, NULL, model }; + AppendItem(item); +} + void SimpleMenuModel::AddSubMenu(int command_id, const string16& label, MenuModel* model) { - Item item = { command_id, label, SkBitmap(), TYPE_SUBMENU, -1, model }; + Item item = { command_id, label, SkBitmap(), TYPE_SUBMENU, -1, model, NULL }; AppendItem(item); } @@ -67,7 +74,7 @@ void SimpleMenuModel::AddSubMenuWithStringId(int command_id, void SimpleMenuModel::InsertItemAt( int index, int command_id, const string16& label) { - Item item = { command_id, label, SkBitmap(), TYPE_COMMAND, -1, NULL }; + Item item = { command_id, label, SkBitmap(), TYPE_COMMAND, -1, NULL, NULL }; InsertItemAtIndex(item, index); } @@ -78,13 +85,13 @@ void SimpleMenuModel::InsertItemWithStringIdAt( void SimpleMenuModel::InsertSeparatorAt(int index) { Item item = { kSeparatorId, string16(), SkBitmap(), TYPE_SEPARATOR, -1, - NULL }; + NULL, NULL }; InsertItemAtIndex(item, index); } void SimpleMenuModel::InsertCheckItemAt( int index, int command_id, const string16& label) { - Item item = { command_id, label, SkBitmap(), TYPE_CHECK, -1, NULL }; + Item item = { command_id, label, SkBitmap(), TYPE_CHECK, -1, NULL, NULL }; InsertItemAtIndex(item, index); } @@ -96,7 +103,8 @@ void SimpleMenuModel::InsertCheckItemWithStringIdAt( void SimpleMenuModel::InsertRadioItemAt( int index, int command_id, const string16& label, int group_id) { - Item item = { command_id, label, SkBitmap(), TYPE_RADIO, group_id, NULL }; + Item item = { command_id, label, SkBitmap(), TYPE_RADIO, group_id, NULL, + NULL }; InsertItemAtIndex(item, index); } @@ -108,7 +116,7 @@ void SimpleMenuModel::InsertRadioItemWithStringIdAt( void SimpleMenuModel::InsertSubMenuAt( int index, int command_id, const string16& label, MenuModel* model) { - Item item = { command_id, label, SkBitmap(), TYPE_SUBMENU, -1, model }; + Item item = { command_id, label, SkBitmap(), TYPE_SUBMENU, -1, model, NULL }; InsertItemAtIndex(item, index); } @@ -199,9 +207,14 @@ bool SimpleMenuModel::GetIconAt(int index, SkBitmap* icon) const { return true; } +ButtonMenuItemModel* SimpleMenuModel::GetButtonMenuItemAt(int index) const { + return items_.at(FlipIndex(index)).button_model; +} + bool SimpleMenuModel::IsEnabledAt(int index) const { int command_id = GetCommandIdAt(index); - if (!delegate_ || command_id == kSeparatorId) + if (!delegate_ || command_id == kSeparatorId || + items_.at(FlipIndex(index)).button_model) return true; return delegate_->IsCommandIdEnabled(command_id); } diff --git a/app/menus/simple_menu_model.h b/app/menus/simple_menu_model.h index 8dd6c64..b6ae852 100644 --- a/app/menus/simple_menu_model.h +++ b/app/menus/simple_menu_model.h @@ -8,6 +8,7 @@ #include <vector> #include "base/string16.h" +#include "app/menus/button_menu_item_model.h" #include "app/menus/menu_model.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -60,6 +61,10 @@ class SimpleMenuModel : public MenuModel { void AddCheckItemWithStringId(int command_id, int string_id); void AddRadioItem(int command_id, const string16& label, int group_id); void AddRadioItemWithStringId(int command_id, int string_id, int group_id); + + // These three methods take pointers to various sub-models. These models + // should be owned by the same owner of this SimpleMenuModel. + void AddButtonItem(int command_id, ButtonMenuItemModel* model); void AddSubMenu(int command_id, const string16& label, MenuModel* model); void AddSubMenuWithStringId(int command_id, int string_id, MenuModel* model); @@ -102,6 +107,7 @@ class SimpleMenuModel : public 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 void HighlightChangedTo(int index); virtual void ActivatedAt(int index); @@ -125,6 +131,7 @@ class SimpleMenuModel : public MenuModel { ItemType type; int group_id; MenuModel* submenu; + ButtonMenuItemModel* button_model; }; std::vector<Item> items_; diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 5f69015..450a158 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -822,12 +822,21 @@ each locale. --> <message name="IDS_ZOOM_PLUS" desc="In Title Case: The text label of the Make Text Larger menu item"> &Larger </message> + <message name="IDS_ZOOM_PLUS2" desc="The text label of the Make Text Larger menu item in the merged menu"> + + + </message> <message name="IDS_ZOOM_NORMAL" desc="In Title Case: The text label of the Make Text Normal Size menu item"> &Normal </message> <message name="IDS_ZOOM_MINUS" desc="In Title Case: The text label of the Make Text Smaller menu item"> &Smaller </message> + <message name="IDS_ZOOM_MINUS2" desc="The text label of the Make Text Smaller menu item in the merged menu"> + - + </message> + <message name="IDS_ZOOM_PERCENT" desc="Current pages zoom factor; shown in merged menu"> + <ph name="VALUE">$1<ex>100</ex></ph>% + </message> <message name="IDS_ENCODING_MENU" desc="In Title Case: The text label of the Encoding submenu"> &Encoding </message> 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); }; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 3f49e89..f382ff7 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1395,6 +1395,10 @@ 'browser/gtk/gtk_chrome_link_button.h', 'browser/gtk/gtk_chrome_shrinkable_hbox.cc', 'browser/gtk/gtk_chrome_shrinkable_hbox.h', + 'browser/gtk/gtk_custom_menu.cc', + 'browser/gtk/gtk_custom_menu.h', + 'browser/gtk/gtk_custom_menu_item.cc', + 'browser/gtk/gtk_custom_menu_item.h', 'browser/gtk/gtk_expanded_container.cc', 'browser/gtk/gtk_expanded_container.h', 'browser/gtk/gtk_floating_container.cc', @@ -3279,6 +3283,10 @@ ['include', '^browser/gtk/gtk_chrome_button.h'], ['include', '^browser/gtk/gtk_chrome_link_button.cc'], ['include', '^browser/gtk/gtk_chrome_link_button.h'], + ['include', '^browser/gtk/gtk_custom_menu.cc'], + ['include', '^browser/gtk/gtk_custom_menu.h'], + ['include', '^browser/gtk/gtk_custom_menu_item.cc'], + ['include', '^browser/gtk/gtk_custom_menu_item.h'], ['include', '^browser/gtk/gtk_theme_provider.cc'], ['include', '^browser/gtk/gtk_theme_provider.h'], ['include', '^browser/gtk/gtk_tree.cc'], |