diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-25 03:59:54 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-25 03:59:54 +0000 |
commit | 2c6ba36933e03b2ce7dfed10f5a8ca6f77f46a2d (patch) | |
tree | 3a76761c951a272de703d260375af0db53f2bb95 | |
parent | 1d39ec059f33a99601c724b1d8e8f4528b01d049 (diff) | |
download | chromium_src-2c6ba36933e03b2ce7dfed10f5a8ca6f77f46a2d.zip chromium_src-2c6ba36933e03b2ce7dfed10f5a8ca6f77f46a2d.tar.gz chromium_src-2c6ba36933e03b2ce7dfed10f5a8ca6f77f46a2d.tar.bz2 |
Moves ownership of MenuItemView to MenuRunner as well as responbility
for showing (running) the menu entirely to MenuRunner. This way I can
guarantee if MenuRunner is deleted, the related menu classes aren't
deleted until the nested message loop completes.
BUG=57890
TEST=thorougly test all menus in chrome: bookmarks, wrench menu,
context menu on page, extension menus...
R=ben@chromium.org
Review URL: http://codereview.chromium.org/7720012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@98179 0039d316-1c4b-4281-b951-d872f2087c98
56 files changed, 793 insertions, 567 deletions
diff --git a/chrome/browser/chromeos/frame/browser_view.cc b/chrome/browser/chromeos/frame/browser_view.cc index 1721a93..c25ccbd 100644 --- a/chrome/browser/chromeos/frame/browser_view.cc +++ b/chrome/browser/chromeos/frame/browser_view.cc @@ -41,6 +41,7 @@ #include "views/controls/button/image_button.h" #include "views/controls/menu/menu_delegate.h" #include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/root_view.h" #include "views/widget/widget.h" #include "views/window/hit_test.h" @@ -511,10 +512,11 @@ void BrowserView::ShowContextMenuForView(views::View* source, if (hit_test == HTCAPTION || hit_test == HTNOWHERE) { // rebuild menu so it reflects current application state InitSystemMenu(); - system_menu_->RunMenuAt(source->GetWidget(), NULL, - gfx::Rect(p, gfx::Size(0,0)), - views::MenuItemView::TOPLEFT, - true); + if (system_menu_runner_->RunMenuAt(source->GetWidget(), NULL, + gfx::Rect(p, gfx::Size(0,0)), views::MenuItemView::TOPLEFT, + views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } } @@ -620,14 +622,16 @@ void BrowserView::GetAccessiblePanes( // BrowserView private. void BrowserView::InitSystemMenu() { - system_menu_.reset(new views::MenuItemView(system_menu_delegate_.get())); - system_menu_->AppendDelegateMenuItem(IDC_RESTORE_TAB); - - system_menu_->AppendMenuItemWithLabel( + views::MenuItemView* menu = + new views::MenuItemView(system_menu_delegate_.get()); + // MenuRunner takes ownership of menu. + system_menu_runner_.reset(new views::MenuRunner(menu)); + menu->AppendDelegateMenuItem(IDC_RESTORE_TAB); + menu->AppendMenuItemWithLabel( IDC_NEW_TAB, UTF16ToWide(l10n_util::GetStringUTF16(IDS_NEW_TAB))); - system_menu_->AppendSeparator(); - system_menu_->AppendMenuItemWithLabel( + menu->AppendSeparator(); + menu->AppendMenuItemWithLabel( IDC_TASK_MANAGER, UTF16ToWide(l10n_util::GetStringUTF16(IDS_TASK_MANAGER))); } diff --git a/chrome/browser/chromeos/frame/browser_view.h b/chrome/browser/chromeos/frame/browser_view.h index 0c24c6e..9d7ae95 100644 --- a/chrome/browser/chromeos/frame/browser_view.h +++ b/chrome/browser/chromeos/frame/browser_view.h @@ -28,6 +28,7 @@ class ImageButton; class ImageView; class MenuDelegate; class MenuItemView; +class MenuRunner; } // namespace views namespace chromeos { @@ -129,7 +130,7 @@ class BrowserView : public ::BrowserView, LayoutModeButton* layout_mode_button_; // System menu. - scoped_ptr<views::MenuItemView> system_menu_; + scoped_ptr<views::MenuRunner> system_menu_runner_; scoped_ptr<views::MenuDelegate> system_menu_delegate_; // Focused native widget before wrench menu shows up. We need this to properly diff --git a/chrome/browser/chromeos/login/language_switch_menu.cc b/chrome/browser/chromeos/login/language_switch_menu.cc index 50425be..c1c39d4 100644 --- a/chrome/browser/chromeos/login/language_switch_menu.cc +++ b/chrome/browser/chromeos/login/language_switch_menu.cc @@ -23,6 +23,7 @@ #include "ui/gfx/platform_font_gtk.h" #include "views/controls/button/menu_button.h" #include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/widget/widget.h" @@ -38,7 +39,8 @@ const int kMoreLanguagesSubMenu = 200; namespace chromeos { LanguageSwitchMenu::LanguageSwitchMenu() - : ALLOW_THIS_IN_INITIALIZER_LIST(menu_(new views::MenuItemView(this))) { + : ALLOW_THIS_IN_INITIALIZER_LIST(menu_(new views::MenuItemView(this))), + menu_runner_(new views::MenuRunner(menu_)) { } LanguageSwitchMenu::~LanguageSwitchMenu() {} @@ -171,8 +173,10 @@ void LanguageSwitchMenu::RunMenu(views::View* source, const gfx::Point& pt) { else new_pt.set_x(pt.x() - reverse_offset); - menu_->RunMenuAt(button->GetWidget(), button, - gfx::Rect(new_pt, gfx::Size()), views::MenuItemView::TOPLEFT, true); + if (menu_runner_->RunMenuAt(button->GetWidget(), button, + gfx::Rect(new_pt, gfx::Size()), views::MenuItemView::TOPLEFT, + views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED) + return; } //////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/chromeos/login/language_switch_menu.h b/chrome/browser/chromeos/login/language_switch_menu.h index 2b99a66..c1681f6 100644 --- a/chrome/browser/chromeos/login/language_switch_menu.h +++ b/chrome/browser/chromeos/login/language_switch_menu.h @@ -18,6 +18,7 @@ class WizardControllerTest_SwitchLanguage_Test; namespace views { class MenuItemView; +class MenuRunner; } // namespace views namespace chromeos { @@ -58,8 +59,11 @@ class LanguageSwitchMenu : public views::ViewMenuDelegate, // views::MenuDelegate implementation. virtual void ExecuteCommand(int command_id) OVERRIDE; - // Dialog controls that we own ourselves. - scoped_ptr<views::MenuItemView> menu_; + // The menu. + views::MenuItemView* menu_; + + // Runs and owns |menu_|. + scoped_ptr<views::MenuRunner> menu_runner_; // Language locale name storage. scoped_ptr<LanguageList> language_list_; diff --git a/chrome/browser/chromeos/notifications/balloon_view.cc b/chrome/browser/chromeos/notifications/balloon_view.cc index 73a6906..b4fe5aa 100644 --- a/chrome/browser/chromeos/notifications/balloon_view.cc +++ b/chrome/browser/chromeos/notifications/balloon_view.cc @@ -34,6 +34,7 @@ #include "views/controls/label.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/view_menu_delegate.h" #include "views/widget/widget.h" @@ -120,17 +121,17 @@ class NotificationControlView : public views::View, CreateOptionsMenu(); views::MenuModelAdapter menu_model_adapter(options_menu_contents_.get()); - views::MenuItemView menu(&menu_model_adapter); - menu_model_adapter.BuildMenu(&menu); + views::MenuRunner menu_runner(menu_model_adapter.CreateMenu()); gfx::Point screen_location; views::View::ConvertPointToScreen(options_menu_button_, &screen_location); - menu.RunMenuAt(source->GetWidget()->GetTopLevelWidget(), - options_menu_button_, - gfx::Rect(screen_location, options_menu_button_->size()), - views::MenuItemView::TOPRIGHT, - true); + if (menu_runner.RunMenuAt( + source->GetWidget()->GetTopLevelWidget(), options_menu_button_, + gfx::Rect(screen_location, options_menu_button_->size()), + views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } // views::ButtonListener implements. diff --git a/chrome/browser/chromeos/status/clock_menu_button.cc b/chrome/browser/chromeos/status/clock_menu_button.cc index 5e17066..3b0384c 100644 --- a/chrome/browser/chromeos/status/clock_menu_button.cc +++ b/chrome/browser/chromeos/status/clock_menu_button.cc @@ -20,6 +20,7 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/canvas.h" #include "ui/gfx/font.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" #include "unicode/datefmt.h" @@ -155,12 +156,11 @@ void ClockMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { gfx::Point screen_location; views::View::ConvertPointToScreen(source, &screen_location); gfx::Rect bounds(screen_location, source->size()); - menu_->RunMenuAt( - source->GetWidget()->GetTopLevelWidget(), - this, - bounds, - views::MenuItemView::TOPRIGHT, - true); + if (menu_runner_->RunMenuAt( + source->GetWidget()->GetTopLevelWidget(), this, bounds, + views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } // ClockMenuButton, views::View implementation: @@ -170,23 +170,26 @@ void ClockMenuButton::OnLocaleChanged() { } void ClockMenuButton::EnsureMenu() { - if (!menu_.get()) { - menu_.reset(new views::MenuItemView(this)); - - // Text for this item will be set by GetLabel(). - menu_->AppendDelegateMenuItem(CLOCK_DISPLAY_ITEM); - - // If options dialog is unavailable, don't show a separator and configure - // menu item. - if (host_->ShouldOpenButtonOptions(this)) { - menu_->AppendSeparator(); - - const string16 clock_open_options_label = - l10n_util::GetStringUTF16(IDS_STATUSBAR_CLOCK_OPEN_OPTIONS_DIALOG); - menu_->AppendMenuItemWithLabel( - CLOCK_OPEN_OPTIONS_ITEM, - UTF16ToWide(clock_open_options_label)); - } + if (menu_runner_.get()) + return; + + views::MenuItemView* menu = new views::MenuItemView(this); + // menu_runner_ takes ownership of menu. + menu_runner_.reset(new views::MenuRunner(menu)); + + // Text for this item will be set by GetLabel(). + menu->AppendDelegateMenuItem(CLOCK_DISPLAY_ITEM); + + // If options dialog is unavailable, don't show a separator and configure + // menu item. + if (host_->ShouldOpenButtonOptions(this)) { + menu->AppendSeparator(); + + const string16 clock_open_options_label = + l10n_util::GetStringUTF16(IDS_STATUSBAR_CLOCK_OPEN_OPTIONS_DIALOG); + menu->AppendMenuItemWithLabel( + CLOCK_OPEN_OPTIONS_ITEM, + UTF16ToWide(clock_open_options_label)); } } diff --git a/chrome/browser/chromeos/status/clock_menu_button.h b/chrome/browser/chromeos/status/clock_menu_button.h index 7864b97..ac712c7 100644 --- a/chrome/browser/chromeos/status/clock_menu_button.h +++ b/chrome/browser/chromeos/status/clock_menu_button.h @@ -21,7 +21,7 @@ #include "views/controls/menu/view_menu_delegate.h" namespace views { -class MenuItemView; +class MenuRunner; } namespace chromeos { @@ -80,9 +80,7 @@ class ClockMenuButton : public StatusAreaButton, base::OneShotTimer<ClockMenuButton> timer_; // The clock menu. - // NOTE: we use a scoped_ptr here as menu calls into 'this' from the - // constructor. - scoped_ptr<views::MenuItemView> menu_; + scoped_ptr<views::MenuRunner> menu_runner_; PrefChangeRegistrar registrar_; diff --git a/chrome/browser/chromeos/status/input_method_menu.cc b/chrome/browser/chromeos/status/input_method_menu.cc index 6c0fa3f..f73532a 100644 --- a/chrome/browser/chromeos/status/input_method_menu.cc +++ b/chrome/browser/chromeos/status/input_method_menu.cc @@ -25,6 +25,7 @@ #include "ui/base/models/simple_menu_model.h" #include "ui/base/resource/resource_bundle.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/widget/widget.h" @@ -139,6 +140,7 @@ InputMethodMenu::InputMethodMenu(PrefService* pref_service, new views::MenuModelAdapter(this))), input_method_menu_( new views::MenuItemView(input_method_menu_delegate_.get())), + input_method_menu_runner_(new views::MenuRunner(input_method_menu_)), minimum_input_method_menu_width_(0), menu_alignment_(views::MenuItemView::TOPRIGHT), pref_service_(pref_service), @@ -388,8 +390,11 @@ void InputMethodMenu::RunMenu(views::View* source, const gfx::Point& pt) { gfx::Point screen_location; views::View::ConvertPointToScreen(source, &screen_location); gfx::Rect bounds(screen_location, source->size()); - input_method_menu_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), - NULL, bounds, menu_alignment_, true); + if (input_method_menu_runner_->RunMenuAt( + source->GetWidget()->GetTopLevelWidget(), NULL, bounds, + menu_alignment_, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } //////////////////////////////////////////////////////////////////////////////// @@ -536,7 +541,7 @@ void InputMethodMenu::RebuildModel() { } // Rebuild the menu from the model. - input_method_menu_delegate_->BuildMenu(input_method_menu_.get()); + input_method_menu_delegate_->BuildMenu(input_method_menu_); } bool InputMethodMenu::IndexIsInInputMethodList(int index) const { diff --git a/chrome/browser/chromeos/status/input_method_menu.h b/chrome/browser/chromeos/status/input_method_menu.h index be736d9..0382380 100644 --- a/chrome/browser/chromeos/status/input_method_menu.h +++ b/chrome/browser/chromeos/status/input_method_menu.h @@ -28,6 +28,7 @@ class SimpleMenuModel; namespace views { class MenuItemView; class MenuModelAdapter; +class MenuRunner; } // namespace views namespace chromeos { @@ -173,7 +174,8 @@ class InputMethodMenu : public views::ViewMenuDelegate, // views::MenuDelegate interface required for MenuItemView. scoped_ptr<ui::SimpleMenuModel> model_; scoped_ptr<views::MenuModelAdapter> input_method_menu_delegate_; - scoped_ptr<views::MenuItemView> input_method_menu_; + views::MenuItemView* input_method_menu_; + scoped_ptr<views::MenuRunner> input_method_menu_runner_; int minimum_input_method_menu_width_; diff --git a/chrome/browser/chromeos/status/memory_menu_button.cc b/chrome/browser/chromeos/status/memory_menu_button.cc index 02f3495..291fa67 100644 --- a/chrome/browser/chromeos/status/memory_menu_button.cc +++ b/chrome/browser/chromeos/status/memory_menu_button.cc @@ -15,6 +15,7 @@ #include "content/common/notification_service.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" #if defined(USE_TCMALLOC) @@ -211,38 +212,35 @@ void MemoryMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { // View passed in must be a views::MenuButton, i.e. the MemoryMenuButton. DCHECK_EQ(source, this); - EnsureMenu(); - + views::MenuRunner menu_runner(CreateMenu()); gfx::Point screen_location; views::View::ConvertPointToScreen(source, &screen_location); gfx::Rect bounds(screen_location, source->size()); - menu_->RunMenuAt( - source->GetWidget()->GetTopLevelWidget(), - this, - bounds, - views::MenuItemView::TOPRIGHT, - true); + if (menu_runner.RunMenuAt( + source->GetWidget()->GetTopLevelWidget(), this, bounds, + views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } -// MemoryMenuButton, views::View implementation: - -void MemoryMenuButton::EnsureMenu() { +views::MenuItemView* MemoryMenuButton::CreateMenu() { // Just rebuild the menu each time to ensure the labels are up-to-date. - menu_.reset(new views::MenuItemView(this)); + views::MenuItemView* menu = new views::MenuItemView(this); // Text for these items will be set by GetLabel(). - menu_->AppendDelegateMenuItem(MEM_TOTAL_ITEM); - menu_->AppendDelegateMenuItem(MEM_FREE_ITEM); - menu_->AppendDelegateMenuItem(MEM_BUFFERS_ITEM); - menu_->AppendDelegateMenuItem(MEM_CACHE_ITEM); - menu_->AppendDelegateMenuItem(SHMEM_ITEM); - menu_->AppendSeparator(); - menu_->AppendDelegateMenuItem(PURGE_MEMORY_ITEM); + menu->AppendDelegateMenuItem(MEM_TOTAL_ITEM); + menu->AppendDelegateMenuItem(MEM_FREE_ITEM); + menu->AppendDelegateMenuItem(MEM_BUFFERS_ITEM); + menu->AppendDelegateMenuItem(MEM_CACHE_ITEM); + menu->AppendDelegateMenuItem(SHMEM_ITEM); + menu->AppendSeparator(); + menu->AppendDelegateMenuItem(PURGE_MEMORY_ITEM); #if defined(USE_TCMALLOC) - menu_->AppendSeparator(); - menu_->AppendDelegateMenuItem(TOGGLE_PROFILING_ITEM); + menu->AppendSeparator(); + menu->AppendDelegateMenuItem(TOGGLE_PROFILING_ITEM); if (IsHeapProfilerRunning()) - menu_->AppendDelegateMenuItem(DUMP_PROFILING_ITEM); + menu->AppendDelegateMenuItem(DUMP_PROFILING_ITEM); #endif + return menu; } ///////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/chromeos/status/memory_menu_button.h b/chrome/browser/chromeos/status/memory_menu_button.h index 7e2c83a..2802c26 100644 --- a/chrome/browser/chromeos/status/memory_menu_button.h +++ b/chrome/browser/chromeos/status/memory_menu_button.h @@ -20,6 +20,7 @@ struct SystemMemoryInfoKB; namespace views { class MenuItemView; +class MenuRunner; } namespace chromeos { @@ -58,18 +59,14 @@ class MemoryMenuButton : public StatusAreaButton, // Execute command id for each renderer. Used for heap profiling. void SendCommandToRenderers(int id); - // Create and initialize menu if not already present. - void EnsureMenu(); + // Creates and returns the menu. The caller owns the returned value. + views::MenuItemView* CreateMenu(); // Updates text and schedules the timer to fire at the next minute interval. void UpdateTextAndSetNextTimer(); base::OneShotTimer<MemoryMenuButton> timer_; - // NOTE: we use a scoped_ptr here as menu calls into 'this' from the - // constructor. - scoped_ptr<views::MenuItemView> menu_; - // Raw data from /proc/meminfo scoped_ptr<base::SystemMemoryInfoKB> meminfo_; diff --git a/chrome/browser/chromeos/status/network_menu.cc b/chrome/browser/chromeos/status/network_menu.cc index 7aeb2db..61901df3 100644 --- a/chrome/browser/chromeos/status/network_menu.cc +++ b/chrome/browser/chromeos/status/network_menu.cc @@ -37,6 +37,7 @@ #include "ui/gfx/skbitmap_operations.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/widget/widget.h" @@ -1029,11 +1030,12 @@ NetworkMenu::NetworkMenu(Delegate* delegate, bool is_browser_mode) : delegate_(delegate), is_browser_mode_(is_browser_mode), refreshing_menu_(false), + menu_item_view_(NULL), min_width_(kDefaultMinimumWidth) { main_menu_model_.reset(new MainMenuModel(this)); menu_model_adapter_.reset( new views::MenuModelAdapter(main_menu_model_.get())); - menu_item_view_.reset(new views::MenuItemView(menu_model_adapter_.get())); + menu_item_view_ = new views::MenuItemView(menu_model_adapter_.get()); menu_item_view_->set_has_icons(true); menu_item_view_->set_menu_position( views::MenuItemView::POSITION_BELOW_BOUNDS); @@ -1047,7 +1049,7 @@ ui::MenuModel* NetworkMenu::GetMenuModel() { } void NetworkMenu::CancelMenu() { - menu_item_view_->Cancel(); + menu_runner_->Cancel(); } void NetworkMenu::UpdateMenu() { @@ -1057,8 +1059,8 @@ void NetworkMenu::UpdateMenu() { main_menu_model_->InitMenuItems( is_browser_mode(), delegate_->ShouldOpenButtonOptions()); - menu_model_adapter_->BuildMenu(menu_item_view_.get()); - SetMenuMargins(menu_item_view_.get(), kTopMargin, kBottomMargin); + menu_model_adapter_->BuildMenu(menu_item_view_); + SetMenuMargins(menu_item_view_, kTopMargin, kBottomMargin); menu_item_view_->ChildrenChanged(); refreshing_menu_ = false; @@ -1074,9 +1076,10 @@ void NetworkMenu::RunMenu(views::View* source) { views::View::ConvertPointToScreen(source, &screen_location); gfx::Rect bounds(screen_location, source->size()); menu_item_view_->GetSubmenu()->set_minimum_preferred_width(min_width_); - menu_item_view_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), - delegate_->GetMenuButton(), bounds, - views::MenuItemView::TOPRIGHT, true); + if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), + delegate_->GetMenuButton(), bounds, views::MenuItemView::TOPRIGHT, + views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED) + return; } void NetworkMenu::ShowTabbedNetworkSettings(const Network* network) const { diff --git a/chrome/browser/chromeos/status/network_menu.h b/chrome/browser/chromeos/status/network_menu.h index 75e932a..980e25c 100644 --- a/chrome/browser/chromeos/status/network_menu.h +++ b/chrome/browser/chromeos/status/network_menu.h @@ -36,6 +36,7 @@ namespace views { class MenuItemView; class MenuButton; class MenuModelAdapter; +class MenuRunner; } namespace chromeos { @@ -123,7 +124,8 @@ class NetworkMenu { // The network menu. scoped_ptr<NetworkMenuModel> main_menu_model_; scoped_ptr<views::MenuModelAdapter> menu_model_adapter_; - scoped_ptr<views::MenuItemView> menu_item_view_; + views::MenuItemView* menu_item_view_; + scoped_ptr<views::MenuRunner> menu_runner_; // Holds minimum width of the menu. int min_width_; diff --git a/chrome/browser/chromeos/status/power_menu_button.cc b/chrome/browser/chromeos/status/power_menu_button.cc index 22f971e..05578636 100644 --- a/chrome/browser/chromeos/status/power_menu_button.cc +++ b/chrome/browser/chromeos/status/power_menu_button.cc @@ -6,6 +6,7 @@ #include <algorithm> +#include "base/auto_reset.h" #include "base/string_number_conversions.h" #include "base/stringprintf.h" #include "base/time.h" @@ -19,6 +20,7 @@ #include "ui/gfx/canvas_skia.h" #include "ui/gfx/font.h" #include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/widget/widget.h" @@ -220,8 +222,8 @@ class PowerMenuButton::StatusView : public View { void OnMouseReleased(const views::MouseEvent& event) { if (event.IsLeftMouseButton()) { - DCHECK(menu_button_->menu_); - menu_button_->menu_->Cancel(); + DCHECK(menu_button_->menu_runner_); + menu_button_->menu_runner_->Cancel(); } } @@ -243,7 +245,7 @@ PowerMenuButton::PowerMenuButton(StatusAreaHost* host) battery_time_to_full_(TimeDelta::FromMicroseconds(kInitialMS)), battery_time_to_empty_(TimeDelta::FromMicroseconds(kInitialMS)), status_(NULL), - menu_(NULL) { + menu_runner_(NULL) { UpdateIconAndLabelInfo(); CrosLibrary::Get()->GetPowerLibrary()->AddObserver(this); } @@ -315,31 +317,31 @@ void PowerMenuButton::OnLocaleChanged() { // PowerMenuButton, views::ViewMenuDelegate implementation: void PowerMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { - menu_ = new views::MenuItemView(this); - views::MenuItemView* submenu = - menu_->AppendMenuItem( + views::MenuItemView* menu = new views::MenuItemView(this); + // MenuRunner takes ownership of |menu|. + views::MenuRunner menu_runner(menu); + views::MenuItemView* submenu = menu->AppendMenuItem( POWER_BATTERY_PERCENTAGE_ITEM, std::wstring(), views::MenuItemView::NORMAL); status_ = new StatusView(this); submenu->AddChildView(status_); - menu_->CreateSubmenu()->set_resize_open_menu(true); - menu_->SetMargins(0, 0); + menu->CreateSubmenu()->set_resize_open_menu(true); + menu->SetMargins(0, 0); submenu->SetMargins(0, 0); - menu_->ChildrenChanged(); + menu->ChildrenChanged(); gfx::Point screen_location; views::View::ConvertPointToScreen(source, &screen_location); gfx::Rect bounds(screen_location, source->size()); - menu_->RunMenuAt( - source->GetWidget()->GetTopLevelWidget(), - this, - bounds, - views::MenuItemView::TOPRIGHT, - true); - delete menu_; + AutoReset<views::MenuRunner*> menu_runner_reseter(&menu_runner_, + &menu_runner); + if (menu_runner.RunMenuAt( + source->GetWidget()->GetTopLevelWidget(), this, bounds, + views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; status_ = NULL; - menu_ = NULL; } //////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/chromeos/status/power_menu_button.h b/chrome/browser/chromeos/status/power_menu_button.h index f27f07e..aef5aec7 100644 --- a/chrome/browser/chromeos/status/power_menu_button.h +++ b/chrome/browser/chromeos/status/power_menu_button.h @@ -15,6 +15,10 @@ namespace base { class TimeDelta; } +namespace views { +class MenuRunner; +} + class SkBitmap; namespace chromeos { @@ -75,8 +79,8 @@ class PowerMenuButton : public StatusAreaButton, // The currently showing status view. NULL if menu is not being displayed. StatusView* status_; - // The currently showing menu. NULL if menu is not being displayed. - views::MenuItemView* menu_; + // If non-null the menu is showing. + views::MenuRunner* menu_runner_; DISALLOW_COPY_AND_ASSIGN(PowerMenuButton); }; diff --git a/chrome/browser/ui/panels/panel_browser_frame_view.cc b/chrome/browser/ui/panels/panel_browser_frame_view.cc index 88f1705..ef907fd 100644 --- a/chrome/browser/ui/panels/panel_browser_frame_view.cc +++ b/chrome/browser/ui/panels/panel_browser_frame_view.cc @@ -214,7 +214,8 @@ PanelBrowserFrameView::PanelBrowserFrameView(BrowserFrame* frame, title_label_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(settings_menu_contents_(this)), settings_menu_adapter_(&settings_menu_contents_), - settings_menu_(&settings_menu_adapter_) { + settings_menu_(new views::MenuItemView(&settings_menu_adapter_)), + settings_menu_runner_(settings_menu_) { EnsureResourcesInitialized(); frame_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM); @@ -442,9 +443,11 @@ void PanelBrowserFrameView::RunMenu(View* source, const gfx::Point& pt) { DCHECK_EQ(settings_button_, source); gfx::Point screen_point; views::View::ConvertPointToScreen(source, &screen_point); - settings_menu_.RunMenuAt(source->GetWidget(), - settings_button_, gfx::Rect(screen_point, source->size()), - views::MenuItemView::TOPRIGHT, true); + if (settings_menu_runner_.RunMenuAt(source->GetWidget(), + settings_button_, gfx::Rect(screen_point, source->size()), + views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } bool PanelBrowserFrameView::IsCommandIdChecked(int command_id) const { @@ -750,5 +753,5 @@ void PanelBrowserFrameView::EnsureSettingsMenuCreated() { settings_menu_contents_.AddItem( COMMAND_MANAGE, l10n_util::GetStringUTF16(IDS_MANAGE_EXTENSIONS)); - settings_menu_adapter_.BuildMenu(&settings_menu_); + settings_menu_adapter_.BuildMenu(settings_menu_); } diff --git a/chrome/browser/ui/panels/panel_browser_frame_view.h b/chrome/browser/ui/panels/panel_browser_frame_view.h index 16b7efa..fa98d54 100644 --- a/chrome/browser/ui/panels/panel_browser_frame_view.h +++ b/chrome/browser/ui/panels/panel_browser_frame_view.h @@ -16,6 +16,7 @@ #include "views/controls/button/button.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/view_menu_delegate.h" class Extension; @@ -193,7 +194,9 @@ class PanelBrowserFrameView : public BrowserNonClientFrameView, scoped_ptr<MouseWatcher> mouse_watcher_; ui::SimpleMenuModel settings_menu_contents_; views::MenuModelAdapter settings_menu_adapter_; - views::MenuItemView settings_menu_; + // Owned by |settings_menu_runner_|. + views::MenuItemView* settings_menu_; + views::MenuRunner settings_menu_runner_; scoped_ptr<ExtensionUninstallDialog> extension_uninstall_dialog_; DISALLOW_COPY_AND_ASSIGN(PanelBrowserFrameView); diff --git a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc index 0a4633f..e908105 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc +++ b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc @@ -14,6 +14,7 @@ #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" #include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" //////////////////////////////////////////////////////////////////////////////// @@ -31,6 +32,7 @@ BookmarkContextMenu::BookmarkContextMenu( this, profile, page_navigator, parent, selection))), parent_widget_(parent_widget), ALLOW_THIS_IN_INITIALIZER_LIST(menu_(new views::MenuItemView(this))), + menu_runner_(new views::MenuRunner(menu_)), parent_node_(parent), observer_(NULL), close_on_remove_(close_on_remove) { @@ -46,10 +48,12 @@ void BookmarkContextMenu::RunMenuAt(const gfx::Point& point) { Source<BookmarkContextMenu>(this), NotificationService::NoDetails()); // width/height don't matter here. - views::MenuItemView::AnchorPosition anchor = base::i18n::IsRTL() ? - views::MenuItemView::TOPRIGHT : views::MenuItemView::TOPLEFT; - menu_->RunMenuAt(parent_widget_, NULL, gfx::Rect(point.x(), point.y(), 0, 0), - anchor, true); + if (menu_runner_->RunMenuAt( + parent_widget_, NULL, gfx::Rect(point.x(), point.y(), 0, 0), + views::MenuItemView::TOPLEFT, + (views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::IS_NESTED)) == + views::MenuRunner::MENU_DELETED) + return; } //////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h index de732d1..064eb0d 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h +++ b/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h @@ -10,6 +10,7 @@ #include "views/controls/menu/menu_delegate.h" namespace views { +class MenuRunner; class Widget; } @@ -42,7 +43,7 @@ class BookmarkContextMenu : public BookmarkContextMenuControllerViewsDelegate, // Shows the context menu at the specified point. void RunMenuAt(const gfx::Point& point); - views::MenuItemView* menu() const { return menu_.get(); } + views::MenuItemView* menu() const { return menu_; } void set_observer(BookmarkContextMenuObserver* observer) { observer_ = observer; @@ -69,8 +70,11 @@ class BookmarkContextMenu : public BookmarkContextMenuControllerViewsDelegate, // The parent of dialog boxes opened from the context menu. views::Widget* parent_widget_; - // The menu itself. - scoped_ptr<views::MenuItemView> menu_; + // The menu itself. This is owned by |menu_runner_|. + views::MenuItemView* menu_; + + // Responsible for running the menu. + scoped_ptr<views::MenuRunner> menu_runner_; // The node we're showing the menu for. const BookmarkNode* parent_node_; diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc index d6f5e81..964bc6f 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc +++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc @@ -24,6 +24,7 @@ #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/base/resource/resource_bundle.h" #include "views/controls/button/menu_button.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" using views::MenuItemView; @@ -40,6 +41,7 @@ BookmarkMenuController::BookmarkMenuController(Profile* profile, bookmark_bar_(NULL) { menu_delegate_->Init(this, NULL, node, start_child_index, BookmarkMenuDelegate::HIDE_OTHER_FOLDER); + menu_runner_.reset(new views::MenuRunner(menu_delegate_->menu())); } void BookmarkMenuController::RunMenuAt(BookmarkBarView* bookmark_bar, @@ -49,27 +51,19 @@ void BookmarkMenuController::RunMenuAt(BookmarkBarView* bookmark_bar, DCHECK(menu_button); MenuItemView::AnchorPosition anchor; bookmark_bar_->GetAnchorPositionForButton(menu_button, &anchor); - RunMenuAt(menu_button, anchor, for_drop); -} - -void BookmarkMenuController::RunMenuAt( - views::MenuButton* button, - MenuItemView::AnchorPosition position, - bool for_drop) { gfx::Point screen_loc; - views::View::ConvertPointToScreen(button, &screen_loc); + views::View::ConvertPointToScreen(menu_button, &screen_loc); // Subtract 1 from the height to make the popup flush with the button border. - gfx::Rect bounds(screen_loc.x(), screen_loc.y(), button->width(), - button->height() - 1); + gfx::Rect bounds(screen_loc.x(), screen_loc.y(), menu_button->width(), + menu_button->height() - 1); for_drop_ = for_drop; menu_delegate_->profile()->GetBookmarkModel()->AddObserver(this); - if (for_drop) { - menu()->RunMenuForDropAt(menu_delegate_->parent(), bounds, position); - } else { - menu()->RunMenuAt(menu_delegate_->parent(), button, bounds, position, - false); + // We only delete ourself after the menu completes, so we can safely ignore + // the return value. + ignore_result(menu_runner_->RunMenuAt(menu_delegate_->parent(), menu_button, + bounds, anchor, for_drop ? views::MenuRunner::FOR_DROP : 0)); + if (!for_drop) delete this; - } } void BookmarkMenuController::Cancel() { diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h index 770d2b8..f8b8227 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h +++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h @@ -29,6 +29,7 @@ class OSExchangeData; namespace views { class MenuButton; +class MenuRunner; class Widget; } // namespace views @@ -58,11 +59,6 @@ class BookmarkMenuController : public BaseBookmarkModelObserver, void RunMenuAt(BookmarkBarView* bookmark_bar, bool for_drop); - // Shows the menu. - void RunMenuAt(views::MenuButton* button, - views::MenuItemView::AnchorPosition position, - bool for_drop); - // Hides the menu. void Cancel(); @@ -119,6 +115,8 @@ class BookmarkMenuController : public BaseBookmarkModelObserver, // BookmarkMenuController deletes itself as necessary. virtual ~BookmarkMenuController(); + scoped_ptr<views::MenuRunner> menu_runner_; + scoped_ptr<BookmarkMenuDelegate> menu_delegate_; // The node we're showing the contents of. diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc b/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc index 7e1731b..fe3e988 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc +++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc @@ -4,7 +4,6 @@ #include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h" -#include "base/stl_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/bookmarks/bookmark_node_data.h" @@ -51,7 +50,6 @@ BookmarkMenuDelegate::BookmarkMenuDelegate(Profile* profile, BookmarkMenuDelegate::~BookmarkMenuDelegate() { profile_->GetBookmarkModel()->RemoveObserver(this); - STLDeleteValues(&node_to_menu_map_); } void BookmarkMenuDelegate::Init(views::MenuDelegate* real_delegate, @@ -313,9 +311,56 @@ void BookmarkMenuDelegate::WillRemoveBookmarks( const std::vector<const BookmarkNode*>& bookmarks) { DCHECK(!is_mutating_model_); is_mutating_model_ = true; - std::set<MenuItemView*> removed_menus; - WillRemoveBookmarksImpl(bookmarks, &removed_menus); - STLDeleteElements(&removed_menus); + + // Remove the observer so that when the remove happens we don't prematurely + // cancel the menu. The observer ias added back in DidRemoveBookmarks. + profile_->GetBookmarkModel()->RemoveObserver(this); + + // Remove the menu items. + std::set<MenuItemView*> changed_parent_menus; + for (std::vector<const BookmarkNode*>::const_iterator i = bookmarks.begin(); + i != bookmarks.end(); ++i) { + NodeToMenuIDMap::iterator node_to_menu = node_to_menu_id_map_.find(*i); + if (node_to_menu != node_to_menu_id_map_.end()) { + MenuItemView* menu = GetMenuByID(node_to_menu->second); + DCHECK(menu); // If there an entry in node_to_menu_id_map_, there should + // be a menu. + DCHECK(menu->GetParentMenuItem()); + changed_parent_menus.insert(menu->GetParentMenuItem()); + menu->GetParentMenuItem()->RemoveMenuItemAt( + menu->parent()->GetIndexOf(menu)); + node_to_menu_id_map_.erase(node_to_menu); + } + } + + // All the bookmarks in |bookmarks| should have the same parent. It's possible + // to support different parents, but this would need to prune any nodes whose + // parent has been removed. As all nodes currently have the same parent, there + // is the DCHECK. + DCHECK(changed_parent_menus.size() <= 1); + + for (std::set<MenuItemView*>::const_iterator i = changed_parent_menus.begin(); + i != changed_parent_menus.end(); ++i) { + (*i)->ChildrenChanged(); + } + + // Remove any descendants of the removed nodes in node_to_menu_id_map_. + for (NodeToMenuIDMap::iterator i = node_to_menu_id_map_.begin(); + i != node_to_menu_id_map_.end(); ) { + bool ancestor_removed = false; + for (std::vector<const BookmarkNode*>::const_iterator j = bookmarks.begin(); + j != bookmarks.end(); ++j) { + if (i->first->HasAncestor(*j)) { + ancestor_removed = true; + break; + } + } + if (ancestor_removed) { + node_to_menu_id_map_.erase(i++); + } else { + ++i; + } + } } void BookmarkMenuDelegate::DidRemoveBookmarks() { @@ -396,56 +441,3 @@ MenuItemView* BookmarkMenuDelegate::GetMenuByID(int id) { return parent_menu_item_ ? parent_menu_item_->GetMenuItemByID(id) : NULL; } - -void BookmarkMenuDelegate::WillRemoveBookmarksImpl( - const std::vector<const BookmarkNode*>& bookmarks, - std::set<views::MenuItemView*>* removed_menus) { - // Remove the observer so that when the remove happens we don't prematurely - // cancel the menu. The observer ias added back in DidRemoveBookmarks. - profile_->GetBookmarkModel()->RemoveObserver(this); - - // Remove the menu items. - std::set<MenuItemView*> changed_parent_menus; - for (std::vector<const BookmarkNode*>::const_iterator i = bookmarks.begin(); - i != bookmarks.end(); ++i) { - NodeToMenuIDMap::iterator node_to_menu = node_to_menu_id_map_.find(*i); - if (node_to_menu != node_to_menu_id_map_.end()) { - MenuItemView* menu = GetMenuByID(node_to_menu->second); - DCHECK(menu); // If there an entry in node_to_menu_id_map_, there should - // be a menu. - removed_menus->insert(menu); - changed_parent_menus.insert(menu->GetParentMenuItem()); - menu->parent()->RemoveChildView(menu); - node_to_menu_id_map_.erase(node_to_menu); - } - } - - // All the bookmarks in |bookmarks| should have the same parent. It's possible - // to support different parents, but this would need to prune any nodes whose - // parent has been removed. As all nodes currently have the same parent, there - // is the DCHECK. - DCHECK(changed_parent_menus.size() <= 1); - - for (std::set<MenuItemView*>::const_iterator i = changed_parent_menus.begin(); - i != changed_parent_menus.end(); ++i) { - (*i)->ChildrenChanged(); - } - - // Remove any descendants of the removed nodes in node_to_menu_id_map_. - for (NodeToMenuIDMap::iterator i = node_to_menu_id_map_.begin(); - i != node_to_menu_id_map_.end(); ) { - bool ancestor_removed = false; - for (std::vector<const BookmarkNode*>::const_iterator j = bookmarks.begin(); - j != bookmarks.end(); ++j) { - if (i->first->HasAncestor(*j)) { - ancestor_removed = true; - break; - } - } - if (ancestor_removed) { - node_to_menu_id_map_.erase(i++); - } else { - ++i; - } - } -} diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h b/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h index 9ad6ca6..35ec1ec 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h +++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h @@ -141,13 +141,6 @@ class BookmarkMenuDelegate : public BaseBookmarkModelObserver, // Returns the menu whose id is |id|. views::MenuItemView* GetMenuByID(int id); - // Does the work of processing WillRemoveBookmarks. On exit the set of removed - // menus is added to |removed_menus|. It's up to the caller to delete the - // the menus added to |removed_menus|. - void WillRemoveBookmarksImpl( - const std::vector<const BookmarkNode*>& bookmarks, - std::set<views::MenuItemView*>* removed_menus); - Profile* profile_; PageNavigator* page_navigator_; diff --git a/chrome/browser/ui/views/browser_actions_container.cc b/chrome/browser/ui/views/browser_actions_container.cc index 1980660..f49cb4c 100644 --- a/chrome/browser/ui/views/browser_actions_container.cc +++ b/chrome/browser/ui/views/browser_actions_container.cc @@ -46,6 +46,7 @@ #include "views/controls/button/text_button.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/drag_utils.h" #include "views/metrics.h" @@ -255,14 +256,15 @@ void BrowserActionButton::ShowContextMenu(const gfx::Point& p, scoped_refptr<ExtensionContextMenuModel> context_menu_contents_( new ExtensionContextMenuModel(extension(), panel_->browser(), panel_)); views::MenuModelAdapter menu_model_adapter(context_menu_contents_.get()); - views::MenuItemView menu(&menu_model_adapter); - menu_model_adapter.BuildMenu(&menu); + views::MenuRunner menu_runner(menu_model_adapter.CreateMenu()); - context_menu_ = &menu; + context_menu_ = menu_runner.GetMenu(); gfx::Point screen_loc; views::View::ConvertPointToScreen(this, &screen_loc); - context_menu_->RunMenuAt(GetWidget(), NULL, gfx::Rect(screen_loc, size()), - views::MenuItemView::TOPLEFT, true); + if (menu_runner.RunMenuAt(GetWidget(), NULL, gfx::Rect(screen_loc, size()), + views::MenuItemView::TOPLEFT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; SetButtonNotPushed(); context_menu_ = NULL; diff --git a/chrome/browser/ui/views/compact_nav/compact_options_bar.cc b/chrome/browser/ui/views/compact_nav/compact_options_bar.cc index bcf5b14..5ba008f 100644 --- a/chrome/browser/ui/views/compact_nav/compact_options_bar.cc +++ b/chrome/browser/ui/views/compact_nav/compact_options_bar.cc @@ -94,7 +94,7 @@ void CompactOptionsBar::RunMenu(views::View* source, const gfx::Point& /* pt */) { DCHECK_EQ(VIEW_ID_APP_MENU, source->id()); - wrench_menu_ = new WrenchMenu(browser_view_->browser()); + wrench_menu_.reset(new WrenchMenu(browser_view_->browser())); wrench_menu_->Init(wrench_menu_model_.get()); for (size_t i = 0; i < menu_listeners_.size(); ++i) diff --git a/chrome/browser/ui/views/compact_nav/compact_options_bar.h b/chrome/browser/ui/views/compact_nav/compact_options_bar.h index 5cff968..13e1518 100644 --- a/chrome/browser/ui/views/compact_nav/compact_options_bar.h +++ b/chrome/browser/ui/views/compact_nav/compact_options_bar.h @@ -70,7 +70,7 @@ class CompactOptionsBar : public views::View, // Button and models for the wrench/app menu. views::MenuButton* app_menu_; scoped_ptr<ui::SimpleMenuModel> wrench_menu_model_; - scoped_refptr<WrenchMenu> wrench_menu_; + scoped_ptr<WrenchMenu> wrench_menu_; std::vector<views::MenuListener*> menu_listeners_; // TODO(stevet): Add the BrowserActionsContainer. diff --git a/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.cc b/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.cc index 621886d..b1472cf 100644 --- a/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.cc +++ b/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.cc @@ -15,6 +15,7 @@ #include "ui/gfx/canvas_skia.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/widget/widget.h" @@ -26,10 +27,12 @@ BrowserActionOverflowMenuController::BrowserActionOverflowMenuController( : owner_(owner), observer_(NULL), menu_button_(menu_button), + menu_(NULL), views_(&views), start_index_(start_index), for_drop_(false) { - menu_.reset(new views::MenuItemView(this)); + menu_ = new views::MenuItemView(this); + menu_runner_.reset(new views::MenuRunner(menu_)); menu_->set_has_icons(true); size_t command_id = 1; // Menu id 0 is reserved, start with 1. @@ -67,10 +70,10 @@ bool BrowserActionOverflowMenuController::RunMenu(views::Widget* window, bounds.set_y(screen_loc.y()); views::MenuItemView::AnchorPosition anchor = views::MenuItemView::TOPRIGHT; - if (for_drop) { - menu_->RunMenuForDropAt(window, bounds, anchor); - } else { - menu_->RunMenuAt(window, menu_button_, bounds, anchor, false); + // As we maintain our own lifetime we can safely ignore the result. + ignore_result(menu_runner_->RunMenuAt(window, menu_button_, bounds, anchor, + for_drop_ ? views::MenuRunner::FOR_DROP : 0)); + if (!for_drop_) { // Give the context menu (if any) a chance to execute the user-selected // command. MessageLoop::current()->DeleteSoon(FROM_HERE, this); @@ -102,12 +105,14 @@ bool BrowserActionOverflowMenuController::ShowContextMenu( new ExtensionContextMenuModel(extension, owner_->browser(), owner_); views::MenuModelAdapter context_menu_model_adapter( context_menu_contents.get()); - views::MenuItemView context_menu(&context_menu_model_adapter); - context_menu_model_adapter.BuildMenu(&context_menu); + views::MenuRunner context_menu_runner( + context_menu_model_adapter.CreateMenu()); + // We can ignore the result as we delete ourself. // This blocks until the user choses something or dismisses the menu. - context_menu.RunMenuAt(menu_button_->GetWidget(), - NULL, gfx::Rect(p, gfx::Size()), views::MenuItemView::TOPLEFT, true); + ignore_result(context_menu_runner.RunMenuAt(menu_button_->GetWidget(), + NULL, gfx::Rect(p, gfx::Size()), views::MenuItemView::TOPLEFT, + views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::IS_NESTED)); // The user is done with the context menu, so we can close the underlying // menu. diff --git a/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.h b/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.h index d9c4e75..9a83432 100644 --- a/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.h +++ b/chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.h @@ -17,6 +17,7 @@ class BrowserActionsContainer; class BrowserActionView; namespace views { +class MenuRunner; class Widget; } @@ -90,8 +91,11 @@ class BrowserActionOverflowMenuController : public views::MenuDelegate { // A pointer to the overflow menu button that we are showing the menu for. views::MenuButton* menu_button_; - // The overflow menu for the menu button. - scoped_ptr<views::MenuItemView> menu_; + // The overflow menu for the menu button. Owned by |menu_runner_|. + views::MenuItemView* menu_; + + // Resposible for running the menu. + scoped_ptr<views::MenuRunner> menu_runner_; // The views vector of all the browser actions the container knows about. We // won't show all items, just the one starting at |start_index| and above. diff --git a/chrome/browser/ui/views/infobars/after_translate_infobar.cc b/chrome/browser/ui/views/infobars/after_translate_infobar.cc index 3ce1c3f..6100958 100644 --- a/chrome/browser/ui/views/infobars/after_translate_infobar.cc +++ b/chrome/browser/ui/views/infobars/after_translate_infobar.cc @@ -12,6 +12,7 @@ #include "views/controls/label.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" AfterTranslateInfoBar::AfterTranslateInfoBar( @@ -176,8 +177,10 @@ void AfterTranslateInfoBar::RunMenu(View* source, const gfx::Point& pt) { } views::MenuModelAdapter menu_model_adapter(menu_model); - views::MenuItemView menu(&menu_model_adapter); - menu_model_adapter.BuildMenu(&menu); - menu.RunMenuAt(source->GetWidget(), NULL, gfx::Rect(pt, gfx::Size()), - views::MenuItemView::TOPRIGHT, true); + views::MenuRunner menu_runner(menu_model_adapter.CreateMenu()); + if (menu_runner.RunMenuAt( + source->GetWidget(), NULL, gfx::Rect(pt, gfx::Size()), + views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } diff --git a/chrome/browser/ui/views/infobars/before_translate_infobar.cc b/chrome/browser/ui/views/infobars/before_translate_infobar.cc index 9a0f66e..61a6b12 100644 --- a/chrome/browser/ui/views/infobars/before_translate_infobar.cc +++ b/chrome/browser/ui/views/infobars/before_translate_infobar.cc @@ -12,6 +12,7 @@ #include "views/controls/label.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" BeforeTranslateInfoBar::BeforeTranslateInfoBar( @@ -197,8 +198,9 @@ void BeforeTranslateInfoBar::RunMenu(View* source, const gfx::Point& pt) { } views::MenuModelAdapter menu_model_adapter(menu_model); - views::MenuItemView menu(&menu_model_adapter); - menu_model_adapter.BuildMenu(&menu); - menu.RunMenuAt(source->GetWidget(), NULL, gfx::Rect(pt, gfx::Size()), - views::MenuItemView::TOPRIGHT, true); + views::MenuRunner menu_runner(menu_model_adapter.CreateMenu()); + if (menu_runner.RunMenuAt(source->GetWidget(), NULL, + gfx::Rect(pt, gfx::Size()), views::MenuItemView::TOPRIGHT, + views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED) + return; } diff --git a/chrome/browser/ui/views/infobars/extension_infobar.cc b/chrome/browser/ui/views/infobars/extension_infobar.cc index 8372982..ad83e0f 100644 --- a/chrome/browser/ui/views/infobars/extension_infobar.cc +++ b/chrome/browser/ui/views/infobars/extension_infobar.cc @@ -19,6 +19,7 @@ #include "views/controls/button/menu_button.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" // ExtensionInfoBarDelegate ---------------------------------------------------- @@ -148,14 +149,14 @@ void ExtensionInfoBar::RunMenu(View* source, const gfx::Point& pt) { scoped_refptr<ExtensionContextMenuModel> options_menu_contents = new ExtensionContextMenuModel(extension, browser, NULL); views::MenuModelAdapter options_menu_delegate(options_menu_contents.get()); - views::MenuItemView options_menu(&options_menu_delegate); - options_menu_delegate.BuildMenu(&options_menu); + views::MenuRunner options_menu_runner(options_menu_delegate.CreateMenu()); gfx::Point screen_point; views::View::ConvertPointToScreen(menu_, &screen_point); - options_menu.RunMenuAt(GetWidget(), menu_, - gfx::Rect(screen_point, menu_->size()), views::MenuItemView::TOPLEFT, - true); + if (options_menu_runner.RunMenuAt(GetWidget(), menu_, + gfx::Rect(screen_point, menu_->size()), views::MenuItemView::TOPLEFT, + views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED) + return; } ExtensionInfoBarDelegate* ExtensionInfoBar::GetDelegate() { diff --git a/chrome/browser/ui/views/location_bar/page_action_image_view.cc b/chrome/browser/ui/views/location_bar/page_action_image_view.cc index 694428f..dc7086c 100644 --- a/chrome/browser/ui/views/location_bar/page_action_image_view.cc +++ b/chrome/browser/ui/views/location_bar/page_action_image_view.cc @@ -17,6 +17,7 @@ #include "ui/base/accessibility/accessible_view_state.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" PageActionImageView::PageActionImageView(LocationBarView* owner, ExtensionAction* page_action) @@ -143,13 +144,14 @@ void PageActionImageView::ShowContextMenu(const gfx::Point& p, scoped_refptr<ExtensionContextMenuModel> context_menu_model( new ExtensionContextMenuModel(extension, owner_->browser(), this)); views::MenuModelAdapter menu_model_adapter(context_menu_model.get()); - views::MenuItemView menu(&menu_model_adapter); - menu_model_adapter.BuildMenu(&menu); + views::MenuRunner menu_runner(menu_model_adapter.CreateMenu()); gfx::Point screen_loc; views::View::ConvertPointToScreen(this, &screen_loc); - menu.RunMenuAt(GetWidget(), NULL, gfx::Rect(screen_loc, size()), - views::MenuItemView::TOPLEFT, true); + if (menu_runner.RunMenuAt(GetWidget(), NULL, gfx::Rect(screen_loc, size()), + views::MenuItemView::TOPLEFT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } void PageActionImageView::OnImageLoaded( diff --git a/chrome/browser/ui/views/menu_item_view_test.cc b/chrome/browser/ui/views/menu_item_view_test.cc index a30769f..ab23e5d 100644 --- a/chrome/browser/ui/views/menu_item_view_test.cc +++ b/chrome/browser/ui/views/menu_item_view_test.cc @@ -7,6 +7,7 @@ #include "views/controls/button/menu_button.h" #include "views/controls/menu/menu_controller.h" #include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/controls/menu/view_menu_delegate.h" #include "views/widget/root_view.h" @@ -30,10 +31,10 @@ class MenuItemViewTestBase : public ViewEventTestBase, public views::ViewMenuDelegate, public views::MenuDelegate { public: - MenuItemViewTestBase() : - ViewEventTestBase(), - button_(NULL), - menu_(NULL) { + MenuItemViewTestBase() + : ViewEventTestBase(), + button_(NULL), + menu_(NULL) { } virtual ~MenuItemViewTestBase() { @@ -43,14 +44,16 @@ class MenuItemViewTestBase : public ViewEventTestBase, virtual void SetUp() OVERRIDE { button_ = new views::MenuButton(NULL, L"Menu Test", this, true); - menu_.reset(new views::MenuItemView(this)); - BuildMenu(menu_.get()); + menu_ = new views::MenuItemView(this); + BuildMenu(menu_); + menu_runner_.reset(new views::MenuRunner(menu_)); ViewEventTestBase::SetUp(); } virtual void TearDown() OVERRIDE { - menu_.reset(NULL); + menu_runner_.reset(NULL); + menu_ = NULL; ViewEventTestBase::TearDown(); } @@ -67,12 +70,12 @@ class MenuItemViewTestBase : public ViewEventTestBase, gfx::Point screen_location; views::View::ConvertPointToScreen(source, &screen_location); gfx::Rect bounds(screen_location, source->size()); - menu_->RunMenuAt( + ignore_result(menu_runner_->RunMenuAt( source->GetWidget(), button_, bounds, views::MenuItemView::TOPLEFT, - true); + views::MenuRunner::HAS_MNEMONICS)); } protected: @@ -89,7 +92,8 @@ class MenuItemViewTestBase : public ViewEventTestBase, } views::MenuButton* button_; - scoped_ptr<views::MenuItemView> menu_; + views::MenuItemView* menu_; + scoped_ptr<views::MenuRunner> menu_runner_; }; // Simple test for clicking a menu item. This template class clicks on an @@ -125,7 +129,7 @@ class MenuItemViewTestBasic : public MenuItemViewTestBase { // Click on item INDEX. void Step1() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); @@ -187,7 +191,7 @@ class MenuItemViewTestInsert : public MenuItemViewTestBase { // Insert item at INSERT_INDEX and click item at SELECT_INDEX. void Step1() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); @@ -208,7 +212,7 @@ class MenuItemViewTestInsert : public MenuItemViewTestBase { // Check clicked item and complete test. void Step2() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); @@ -318,8 +322,8 @@ VIEW_TEST(MenuItemViewTestInsertWithSubmenu1, InsertItemWithSubmenu1) template<int REMOVE_INDEX, int SELECT_INDEX> class MenuItemViewTestRemove : public MenuItemViewTestBase { public: - MenuItemViewTestRemove() : - last_command_(0) { + MenuItemViewTestRemove() + : last_command_(0) { } virtual ~MenuItemViewTestRemove() { @@ -344,7 +348,7 @@ class MenuItemViewTestRemove : public MenuItemViewTestBase { // Remove item at REMOVE_INDEX and click item at SELECT_INDEX. void Step1() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); @@ -363,7 +367,7 @@ class MenuItemViewTestRemove : public MenuItemViewTestBase { // Check clicked item and complete test. void Step2() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); @@ -428,7 +432,7 @@ class MenuItemViewTestRemoveWithSubmenu : public MenuItemViewTestBase { // Post submenu. void Step1() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); @@ -440,7 +444,7 @@ class MenuItemViewTestRemoveWithSubmenu : public MenuItemViewTestBase { // Remove item at REMOVE_INDEX and select it to exit the menu loop. void Step2() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); @@ -457,7 +461,7 @@ class MenuItemViewTestRemoveWithSubmenu : public MenuItemViewTestBase { } void Step3() { - ASSERT_TRUE(menu_.get()); + ASSERT_TRUE(menu_); views::SubmenuView* submenu = menu_->GetSubmenu(); ASSERT_TRUE(submenu); diff --git a/chrome/browser/ui/views/menu_model_adapter_test.cc b/chrome/browser/ui/views/menu_model_adapter_test.cc index 85466d5..a13e165 100644 --- a/chrome/browser/ui/views/menu_model_adapter_test.cc +++ b/chrome/browser/ui/views/menu_model_adapter_test.cc @@ -9,6 +9,7 @@ #include "views/controls/menu/menu_controller.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/controls/menu/view_menu_delegate.h" #include "views/test/test_views_delegate.h" @@ -247,10 +248,11 @@ class TopMenuModel : public CommonMenuModel { class MenuModelAdapterTest : public ViewEventTestBase, public views::ViewMenuDelegate { public: - MenuModelAdapterTest() : - ViewEventTestBase(), - button_(NULL), - menu_model_adapter_(&top_menu_model_) { + MenuModelAdapterTest() + : ViewEventTestBase(), + button_(NULL), + menu_model_adapter_(&top_menu_model_), + menu_(NULL) { old_views_delegate_ = views::ViewsDelegate::views_delegate; views::ViewsDelegate::views_delegate = &views_delegate_; } @@ -264,14 +266,15 @@ class MenuModelAdapterTest : public ViewEventTestBase, virtual void SetUp() OVERRIDE { button_ = new views::MenuButton(NULL, L"Menu Adapter Test", this, true); - menu_.reset(new views::MenuItemView(&menu_model_adapter_)); - menu_model_adapter_.BuildMenu(menu_.get()); + menu_ = menu_model_adapter_.CreateMenu(); + menu_runner_.reset(new views::MenuRunner(menu_)); ViewEventTestBase::SetUp(); } virtual void TearDown() OVERRIDE { - menu_.reset(NULL); + menu_runner_.reset(NULL); + menu_ = NULL; ViewEventTestBase::TearDown(); } @@ -288,12 +291,12 @@ class MenuModelAdapterTest : public ViewEventTestBase, gfx::Point screen_location; views::View::ConvertPointToScreen(source, &screen_location); gfx::Rect bounds(screen_location, source->size()); - menu_->RunMenuAt( + ignore_result(menu_runner_->RunMenuAt( source->GetWidget(), button_, bounds, views::MenuItemView::TOPLEFT, - true); + views::MenuRunner::HAS_MNEMONICS)); } // ViewEventTestBase implementation @@ -321,7 +324,7 @@ class MenuModelAdapterTest : public ViewEventTestBase, ASSERT_TRUE(topmenu->IsShowing()); ASSERT_TRUE(top_menu_model_.IsSubmenuShowing()); - menu_model_adapter_.BuildMenu(menu_.get()); + menu_model_adapter_.BuildMenu(menu_); MessageLoopForUI::current()->PostTask( FROM_HERE, @@ -366,7 +369,8 @@ class MenuModelAdapterTest : public ViewEventTestBase, views::MenuButton* button_; TopMenuModel top_menu_model_; views::MenuModelAdapter menu_model_adapter_; - scoped_ptr<views::MenuItemView> menu_; + views::MenuItemView* menu_; + scoped_ptr<views::MenuRunner> menu_runner_; }; VIEW_TEST(MenuModelAdapterTest, RebuildMenu) diff --git a/chrome/browser/ui/views/tab_contents/render_view_context_menu_views.cc b/chrome/browser/ui/views/tab_contents/render_view_context_menu_views.cc index 41194b4..cdf0ba9 100644 --- a/chrome/browser/ui/views/tab_contents/render_view_context_menu_views.cc +++ b/chrome/browser/ui/views/tab_contents/render_view_context_menu_views.cc @@ -48,9 +48,11 @@ void RenderViewContextMenuViews::RunMenuAt(int x, int y) { static_cast<TabContentsViewViews*>(source_tab_contents_->view()); views::Widget* parent = tab->GetTopLevelWidget(); #endif - menu_runner_->RunMenuAt(parent, NULL, - gfx::Rect(gfx::Point(x, y), gfx::Size()), - views::MenuItemView::TOPLEFT, true); + if (menu_runner_->RunMenuAt(parent, NULL, + gfx::Rect(gfx::Point(x, y), gfx::Size()), + views::MenuItemView::TOPLEFT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } #if defined(OS_WIN) diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc index 7bca147..1a9ab98 100644 --- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc +++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc @@ -24,6 +24,7 @@ #include "content/common/notification_service.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" static TabRendererData::NetworkState TabContentsNetworkState( @@ -40,19 +41,18 @@ class BrowserTabStripController::TabContextMenuContents public: TabContextMenuContents(BaseTab* tab, BrowserTabStripController* controller) - : ALLOW_THIS_IN_INITIALIZER_LIST( - model_(this, - controller->model_, - controller->tabstrip_->GetModelIndexOfBaseTab(tab))), - menu_model_adapter_(&model_), - menu_(&menu_model_adapter_), - tab_(tab), + : tab_(tab), controller_(controller), last_command_(TabStripModel::CommandFirst) { - menu_model_adapter_.BuildMenu(&menu_); + model_.reset(new TabMenuModel( + this, controller->model_, + controller->tabstrip_->GetModelIndexOfBaseTab(tab))); + menu_model_adapter_.reset(new views::MenuModelAdapter(model_.get())); + menu_runner_.reset( + new views::MenuRunner(menu_model_adapter_->CreateMenu())); } + virtual ~TabContextMenuContents() { - menu_.Cancel(); if (controller_) controller_->tabstrip_->StopAllHighlighting(); } @@ -62,9 +62,11 @@ class BrowserTabStripController::TabContextMenuContents } void RunMenuAt(const gfx::Point& point) { - menu_.RunMenuAt(tab_->GetWidget(), NULL, gfx::Rect(point, gfx::Size()), - views::MenuItemView::TOPLEFT, true); - // We could be gone now. Assume |this| is junk! + if (menu_runner_->RunMenuAt( + tab_->GetWidget(), NULL, gfx::Rect(point, gfx::Size()), + views::MenuItemView::TOPLEFT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; } // Overridden from ui::SimpleMenuModel::Delegate: @@ -109,9 +111,9 @@ class BrowserTabStripController::TabContextMenuContents } private: - TabMenuModel model_; - views::MenuModelAdapter menu_model_adapter_; - views::MenuItemView menu_; + scoped_ptr<TabMenuModel> model_; + scoped_ptr<views::MenuModelAdapter> menu_model_adapter_; + scoped_ptr<views::MenuRunner> menu_runner_; // The tab we're showing a menu for. BaseTab* tab_; diff --git a/chrome/browser/ui/views/toolbar_view.cc b/chrome/browser/ui/views/toolbar_view.cc index 1452c96..337635e 100644 --- a/chrome/browser/ui/views/toolbar_view.cc +++ b/chrome/browser/ui/views/toolbar_view.cc @@ -328,7 +328,7 @@ bool ToolbarView::GetAcceleratorInfo(int id, ui::Accelerator* accel) { void ToolbarView::RunMenu(views::View* source, const gfx::Point& /* pt */) { DCHECK_EQ(VIEW_ID_APP_MENU, source->id()); - wrench_menu_ = new WrenchMenu(browser_); + wrench_menu_.reset(new WrenchMenu(browser_)); wrench_menu_->Init(wrench_menu_model_.get()); for (size_t i = 0; i < menu_listeners_.size(); ++i) diff --git a/chrome/browser/ui/views/toolbar_view.h b/chrome/browser/ui/views/toolbar_view.h index fd0a83c..f24c9fb 100644 --- a/chrome/browser/ui/views/toolbar_view.h +++ b/chrome/browser/ui/views/toolbar_view.h @@ -9,7 +9,6 @@ #include <set> #include <vector> -#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "chrome/browser/command_updater.h" #include "chrome/browser/prefs/pref_member.h" @@ -202,7 +201,7 @@ class ToolbarView : public AccessiblePaneView, scoped_ptr<ui::SimpleMenuModel> wrench_menu_model_; // Wrench menu. - scoped_refptr<WrenchMenu> wrench_menu_; + scoped_ptr<WrenchMenu> wrench_menu_; // Vector of listeners to receive callbacks when the menu opens. std::vector<views::MenuListener*> menu_listeners_; diff --git a/chrome/browser/ui/views/wrench_menu.cc b/chrome/browser/ui/views/wrench_menu.cc index 386e8d5..6fb7950 100644 --- a/chrome/browser/ui/views/wrench_menu.cc +++ b/chrome/browser/ui/views/wrench_menu.cc @@ -36,6 +36,7 @@ #include "views/controls/label.h" #include "views/controls/menu/menu_config.h" #include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/menu_scroll_view_container.h" #include "views/controls/menu/submenu_view.h" #include "views/widget/widget.h" @@ -565,35 +566,42 @@ class WrenchMenu::ZoomView : public WrenchMenuView, // WrenchMenu ------------------------------------------------------------------ WrenchMenu::WrenchMenu(Browser* browser) - : browser_(browser), + : root_(NULL), + browser_(browser), selected_menu_model_(NULL), selected_index_(0), bookmark_menu_(NULL), first_bookmark_command_id_(0) { } +WrenchMenu::~WrenchMenu() { + if (bookmark_menu_delegate_.get()) { + BookmarkModel* model = browser_->profile()->GetBookmarkModel(); + if (model) + model->RemoveObserver(this); + } +} + void WrenchMenu::Init(ui::MenuModel* model) { - DCHECK(!root_.get()); - root_.reset(new MenuItemView(this)); + DCHECK(!root_); + root_ = new MenuItemView(this); root_->set_has_icons(true); // We have checks, radios and icons, set this // so we get the taller menu style. int next_id = 1; - PopulateMenu(root_.get(), model, &next_id); + PopulateMenu(root_, model, &next_id); first_bookmark_command_id_ = next_id + 1; + menu_runner_.reset(new views::MenuRunner(root_)); } void WrenchMenu::RunMenu(views::MenuButton* host) { - // Up the ref count while the menu is displaying. This way if the window is - // deleted while we're running we won't prematurely delete the menu. - // TODO(sky): fix this, the menu should really take ownership of the menu - // (57890). - scoped_refptr<WrenchMenu> dont_delete_while_running(this); gfx::Point screen_loc; views::View::ConvertPointToScreen(host, &screen_loc); gfx::Rect bounds(screen_loc, host->size()); UserMetrics::RecordAction(UserMetricsAction("ShowAppMenu")); - root_->RunMenuAt(host->GetWidget(), host, bounds, - MenuItemView::TOPRIGHT, true); + if (menu_runner_->RunMenuAt(host->GetWidget(), host, bounds, + MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == + views::MenuRunner::MENU_DELETED) + return; if (bookmark_menu_delegate_.get()) { BookmarkModel* model = browser_->profile()->GetBookmarkModel(); if (model) @@ -765,9 +773,6 @@ void WrenchMenu::BookmarkModelChanged() { root_->Cancel(); } -WrenchMenu::~WrenchMenu() { -} - void WrenchMenu::PopulateMenu(MenuItemView* parent, MenuModel* model, int* next_id) { diff --git a/chrome/browser/ui/views/wrench_menu.h b/chrome/browser/ui/views/wrench_menu.h index 76c9885..c18c5f0 100644 --- a/chrome/browser/ui/views/wrench_menu.h +++ b/chrome/browser/ui/views/wrench_menu.h @@ -9,7 +9,6 @@ #include <map> #include <utility> -#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "chrome/browser/bookmarks/base_bookmark_model_observer.h" #include "ui/base/models/menu_model.h" @@ -21,15 +20,16 @@ class Browser; namespace views { class MenuButton; class MenuItemView; +class MenuRunner; class View; } // namespace views // WrenchMenu adapts the WrenchMenuModel to view's menu related classes. -class WrenchMenu : public base::RefCounted<WrenchMenu>, - public views::MenuDelegate, +class WrenchMenu : public views::MenuDelegate, public BaseBookmarkModelObserver { public: explicit WrenchMenu(Browser* browser); + virtual ~WrenchMenu(); void Init(ui::MenuModel* model); @@ -72,16 +72,12 @@ class WrenchMenu : public base::RefCounted<WrenchMenu>, virtual void BookmarkModelChanged() OVERRIDE; private: - friend class base::RefCounted<WrenchMenu>; - class CutCopyPasteView; class ZoomView; typedef std::pair<ui::MenuModel*,int> Entry; typedef std::map<int,Entry> IDToEntry; - virtual ~WrenchMenu(); - // Populates |parent| with all the child menus in |model|. Recursively invokes // |PopulateMenu| for any submenu. |next_id| is incremented for every menu // that is created. @@ -110,8 +106,10 @@ class WrenchMenu : public base::RefCounted<WrenchMenu>, return bookmark_menu_delegate_.get() && id >= first_bookmark_command_id_; } - // The views menu. - scoped_ptr<views::MenuItemView> root_; + // The views menu. Owned by |menu_runner_|. + views::MenuItemView* root_; + + scoped_ptr<views::MenuRunner> menu_runner_; // Maps from the ID as understood by MenuItemView to the model/index pair the // item came from. diff --git a/views/controls/button/button_dropdown.cc b/views/controls/button/button_dropdown.cc index 16c0313..44b79d3 100644 --- a/views/controls/button/button_dropdown.cc +++ b/views/controls/button/button_dropdown.cc @@ -13,6 +13,7 @@ #include "ui/base/models/menu_model.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/widget/widget.h" namespace views { @@ -155,20 +156,21 @@ void ButtonDropDown::ShowDropDownMenu(gfx::NativeView window) { if (model_) { MenuModelAdapter menu_delegate(model_); menu_delegate.set_triggerable_event_flags(triggerable_event_flags()); - MenuItemView menu(&menu_delegate); - menu_delegate.BuildMenu(&menu); - - menu.RunMenuAt(GetWidget(), NULL, - gfx::Rect(menu_position, gfx::Size(0, 0)), - views::MenuItemView::TOPLEFT, - true); + MenuRunner runner(menu_delegate.CreateMenu()); + if (runner.RunMenuAt(GetWidget(), NULL, + gfx::Rect(menu_position, gfx::Size(0, 0)), + MenuItemView::TOPLEFT, + MenuRunner::HAS_MNEMONICS) == MenuRunner::MENU_DELETED) + return; } else { MenuDelegate menu_delegate; - MenuItemView menu(&menu_delegate); - menu.RunMenuAt(GetWidget(), NULL, - gfx::Rect(menu_position, gfx::Size(0, 0)), - views::MenuItemView::TOPLEFT, - true); + MenuItemView* menu = new MenuItemView(&menu_delegate); + MenuRunner runner(menu); + if (runner.RunMenuAt(GetWidget(), NULL, + gfx::Rect(menu_position, gfx::Size(0, 0)), + views::MenuItemView::TOPLEFT, + MenuRunner::HAS_MNEMONICS) == MenuRunner::MENU_DELETED) + return; } // Need to explicitly clear mouse handler so that events get sent diff --git a/views/controls/combobox/native_combobox_views.cc b/views/controls/combobox/native_combobox_views.cc index 6a7b318..4696615 100644 --- a/views/controls/combobox/native_combobox_views.cc +++ b/views/controls/combobox/native_combobox_views.cc @@ -18,6 +18,7 @@ #include "views/border.h" #include "views/controls/combobox/combobox.h" #include "views/controls/focusable_border.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/widget/root_view.h" #include "views/widget/widget.h" @@ -163,7 +164,9 @@ void NativeComboboxViews::UpdateFromModel() { int max_width = 0; const gfx::Font &font = GetFont(); - dropdown_list_menu_.reset(new MenuItemView(this)); + MenuItemView* menu = new MenuItemView(this); + // MenuRunner owns |menu|. + dropdown_list_menu_runner_.reset(new MenuRunner(menu)); int num_items = combobox_->model()->GetItemCount(); for (int i = 0; i < num_items; ++i) { @@ -173,10 +176,10 @@ void NativeComboboxViews::UpdateFromModel() { // text is displayed correctly in right-to-left UIs. base::i18n::AdjustStringForLocaleDirection(&text); - dropdown_list_menu_->AppendMenuItem(i + kFirstMenuItemId, UTF16ToWide(text), - MenuItemView::NORMAL); - max_width = std::max(max_width, font.GetStringWidth(text)); - } + menu->AppendMenuItem(i + kFirstMenuItemId, UTF16ToWide(text), + MenuItemView::NORMAL); + max_width = std::max(max_width, font.GetStringWidth(text)); + } content_width_ = max_width; content_height_ = font.GetFontSize(); @@ -342,11 +345,11 @@ void NativeComboboxViews::PaintText(gfx::Canvas* canvas) { void NativeComboboxViews::ShowDropDownMenu() { - if (!dropdown_list_menu_.get()) + if (!dropdown_list_menu_runner_.get()) UpdateFromModel(); // Extend the menu to the width of the combobox. - SubmenuView* submenu = dropdown_list_menu_->CreateSubmenu(); + SubmenuView* submenu = dropdown_list_menu_runner_->GetMenu()->CreateSubmenu(); submenu->set_minimum_preferred_width(size().width()); gfx::Rect lb = GetLocalBounds(); @@ -358,8 +361,10 @@ void NativeComboboxViews::ShowDropDownMenu() { gfx::Rect bounds(menu_position, lb.size()); dropdown_open_ = true; - dropdown_list_menu_->RunMenuAt(NULL, NULL, bounds, MenuItemView::TOPLEFT, - true); + if (dropdown_list_menu_runner_->RunMenuAt( + NULL, NULL, bounds, MenuItemView::TOPLEFT, + MenuRunner::HAS_MNEMONICS) == MenuRunner::MENU_DELETED) + return; dropdown_open_ = false; // Need to explicitly clear mouse handler so that events get sent diff --git a/views/controls/combobox/native_combobox_views.h b/views/controls/combobox/native_combobox_views.h index f4cb56b..5722e96 100644 --- a/views/controls/combobox/native_combobox_views.h +++ b/views/controls/combobox/native_combobox_views.h @@ -19,6 +19,7 @@ namespace views { class KeyEvent; class FocusableBorder; +class MenuRunner; // A views/skia only implementation of NativeComboboxWrapper. // No platform specific code is used. @@ -82,8 +83,8 @@ class NativeComboboxViews : public views::View, // The reference to the border class. The object is owned by View::border_. FocusableBorder* text_border_; - // Context menu and its content list for the combobox. - scoped_ptr<views::MenuItemView> dropdown_list_menu_; + // Responsible for showing the context menu. + scoped_ptr<MenuRunner> dropdown_list_menu_runner_; // Is the drop down list showing bool dropdown_open_; diff --git a/views/controls/menu/menu_controller.cc b/views/controls/menu/menu_controller.cc index c44aa9a..cb53602 100644 --- a/views/controls/menu/menu_controller.cc +++ b/views/controls/menu/menu_controller.cc @@ -14,6 +14,7 @@ #include "ui/gfx/canvas_skia.h" #include "ui/gfx/screen.h" #include "views/controls/button/menu_button.h" +#include "views/controls/menu/menu_controller_delegate.h" #include "views/controls/menu/menu_scroll_view_container.h" #include "views/controls/menu/submenu_view.h" #include "views/drag_utils.h" @@ -369,7 +370,7 @@ void MenuController::Cancel(ExitType type) { // If the menu has already been destroyed, no further cancellation is // needed. We especially don't want to set the |exit_type_| to a lesser // value. - if (exit_type_ == EXIT_DESTROYED) + if (exit_type_ == EXIT_DESTROYED || exit_type_ == type) return; if (!showing_) { @@ -391,7 +392,9 @@ void MenuController::Cancel(ExitType type) { // triggers deleting us. DCHECK(selected); showing_ = false; - selected->GetRootMenuItem()->DropMenuClosed(true); + delegate_->DropMenuClosed( + internal::MenuControllerDelegate::NOTIFY_DELEGATE, + selected->GetRootMenuItem()); // WARNING: the call to MenuClosed deletes us. return; } @@ -716,8 +719,11 @@ int MenuController::OnPerformDrop(SubmenuView* source, if (drop_target->id() == MenuItemView::kEmptyMenuItemViewID) drop_target = drop_target->GetParentMenuItem(); - if (!IsBlockingRun()) - item->GetRootMenuItem()->DropMenuClosed(false); + if (!IsBlockingRun()) { + delegate_->DropMenuClosed( + internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE, + item->GetRootMenuItem()); + } // WARNING: the call to MenuClosed deletes us. @@ -808,11 +814,6 @@ void MenuController::SetSelection(MenuItemView* menu_item, } } -// static -void MenuController::SetActiveInstance(MenuController* controller) { - active_instance_ = controller; -} - #if defined(OS_WIN) bool MenuController::Dispatch(const MSG& msg) { DCHECK(blocking_run_); @@ -994,7 +995,8 @@ bool MenuController::OnKeyDown(int key_code return true; } -MenuController::MenuController(bool blocking) +MenuController::MenuController(bool blocking, + internal::MenuControllerDelegate* delegate) : blocking_run_(blocking), showing_(false), exit_type_(EXIT_NONE), @@ -1008,11 +1010,15 @@ MenuController::MenuController(bool blocking) valid_drop_coordinates_(false), showing_submenu_(false), menu_button_(NULL), - active_mouse_view_(NULL) { + active_mouse_view_(NULL), + delegate_(delegate) { + active_instance_ = this; } MenuController::~MenuController() { DCHECK(!showing_); + if (active_instance_ == this) + active_instance_ = NULL; StopShowTimer(); StopCancelAllTimer(); } @@ -1095,6 +1101,8 @@ bool MenuController::ShowSiblingMenu(SubmenuView* source, if (!alt_menu || (state_.item && state_.item->GetRootMenuItem() == alt_menu)) return false; + delegate_->SiblingMenuCreated(alt_menu); + if (!button) { // If the delegate returns a menu, they must also return a button. NOTREACHED(); diff --git a/views/controls/menu/menu_controller.h b/views/controls/menu/menu_controller.h index cf2fa49..35b7a78 100644 --- a/views/controls/menu/menu_controller.h +++ b/views/controls/menu/menu_controller.h @@ -32,6 +32,11 @@ class MouseEvent; class SubmenuView; class View; +namespace internal { +class MenuControllerDelegate; +class MenuRunnerImpl; +} + // MenuController ------------------------------------------------------------- // MenuController is used internally by the various menu classes to manage @@ -39,10 +44,6 @@ class View; // forwarded to the MenuController from SubmenuView and MenuHost. class VIEWS_EXPORT MenuController : public MessageLoop::Dispatcher { public: - friend class MenuHostRootView; - friend class MenuItemView; - friend class SubmenuView; - // Enumeration of how the menu should exit. enum ExitType { // Don't exit. @@ -122,6 +123,11 @@ class VIEWS_EXPORT MenuController : public MessageLoop::Dispatcher { void OnWidgetActivationChanged(); private: + friend class internal::MenuRunnerImpl; + friend class MenuHostRootView; + friend class MenuItemView; + friend class SubmenuView; + class MenuScrollTask; struct SelectByCharDetails; @@ -209,9 +215,6 @@ class VIEWS_EXPORT MenuController : public MessageLoop::Dispatcher { // to show/hide submenus and update state_. void SetSelection(MenuItemView* menu_item, int types); - // Sets the active MenuController. - static void SetActiveInstance(MenuController* controller); - #if defined(OS_WIN) // Dispatcher method. This returns true if the menu was canceled, or // if the message is such that the menu should be closed. @@ -231,8 +234,9 @@ class VIEWS_EXPORT MenuController : public MessageLoop::Dispatcher { bool OnKeyDown(int key_code); #endif - // Creates a MenuController. If blocking is true, Run blocks the caller - explicit MenuController(bool blocking); + // Creates a MenuController. If |blocking| is true a nested message loop is + // started in |Run|. + MenuController(bool blocking, internal::MenuControllerDelegate* delegate); virtual ~MenuController(); @@ -494,6 +498,8 @@ class VIEWS_EXPORT MenuController : public MessageLoop::Dispatcher { // UpdateActiveMouseView for details. View* active_mouse_view_; + internal::MenuControllerDelegate* delegate_; + DISALLOW_COPY_AND_ASSIGN(MenuController); }; diff --git a/views/controls/menu/menu_controller_delegate.h b/views/controls/menu/menu_controller_delegate.h new file mode 100644 index 0000000..db68579 --- /dev/null +++ b/views/controls/menu/menu_controller_delegate.h @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_CONTROLS_MENU_MENU_CONTROLLER_DELEGATE_H_ +#define VIEWS_CONTROLS_MENU_MENU_CONTROLLER_DELEGATE_H_ +#pragma once + +namespace views { + +class MenuItemView; + +// This is internal as there should be no need for usage of this class outside +// of views. +namespace internal { + +// Used by MenuController to notify of interesting events that are intended for +// the class using MenuController. This is implemented by MenuRunnerImpl. +class MenuControllerDelegate { + public: + enum NotifyType { + NOTIFY_DELEGATE, + DONT_NOTIFY_DELEGATE + }; + + // Invoked when MenuController closes a menu and the MenuController was + // configured for drop (MenuRunner::FOR_DROP). + virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) = 0; + + // Invoked when the MenuDelegate::GetSiblingMenu() returns non-NULL. + virtual void SiblingMenuCreated(MenuItemView* menu) = 0; + + protected: + virtual ~MenuControllerDelegate() {} +}; + +} // namespace internal + +} // namespace view + +#endif // VIEWS_CONTROLS_MENU_MENU_CONTROLLER_DELEGATE_H_ diff --git a/views/controls/menu/menu_item_view.cc b/views/controls/menu/menu_item_view.cc index df4a2a8..8181793 100644 --- a/views/controls/menu/menu_item_view.cc +++ b/views/controls/menu/menu_item_view.cc @@ -19,10 +19,6 @@ #include "views/controls/menu/menu_separator.h" #include "views/controls/menu/submenu_view.h" -#if defined(OS_WIN) -#include "base/win/win_util.h" -#endif - namespace views { namespace { @@ -100,16 +96,6 @@ MenuItemView::MenuItemView(MenuDelegate* delegate) Init(NULL, 0, SUBMENU, delegate); } -MenuItemView::~MenuItemView() { - // TODO(sky): ownership is bit wrong now. In particular if a nested message - // loop is running deletion can't be done, otherwise the stack gets - // thoroughly screwed. The destructor should be made private, and - // MenuController should be the only place handling deletion of the menu. - // (57890). - delete submenu_; - STLDeleteElements(&removed_items_); -} - void MenuItemView::ChildPreferredSizeChanged(View* child) { pref_size_.SetSize(0, 0); PreferredSizeChanged(); @@ -192,86 +178,6 @@ string16 MenuItemView::GetAccessibleNameForMenuItem( return accessible_name; } -void MenuItemView::RunMenuAt(Widget* parent, - MenuButton* button, - const gfx::Rect& bounds, - AnchorPosition anchor, - bool has_mnemonics) { - // Show mnemonics if the button has focus or alt is pressed. - bool show_mnemonics = button ? button->HasFocus() : false; -#if defined(OS_WIN) - // We don't currently need this on gtk as showing the menu gives focus to the - // button first. - if (!show_mnemonics) - show_mnemonics = base::win::IsAltPressed(); -#endif - PrepareForRun(has_mnemonics, show_mnemonics); - int mouse_event_flags; - - MenuController* controller = MenuController::GetActiveInstance(); - if (controller && !controller->IsBlockingRun()) { - // A menu is already showing, but it isn't a blocking menu. Cancel it. - // We can get here during drag and drop if the user right clicks on the - // menu quickly after the drop. - controller->Cancel(MenuController::EXIT_ALL); - controller = NULL; - } - bool owns_controller = false; - if (!controller) { - // No menus are showing, show one. - controller = new MenuController(true); - MenuController::SetActiveInstance(controller); - owns_controller = true; - } else { - // A menu is already showing, use the same controller. - - // Don't support blocking from within non-blocking. - DCHECK(controller->IsBlockingRun()); - } - - controller_ = controller; - - // Run the loop. - MenuItemView* result = - controller->Run(parent, button, this, bounds, anchor, &mouse_event_flags); - - RemoveEmptyMenus(); - - controller_ = NULL; - - if (owns_controller) { - // We created the controller and need to delete it. - if (MenuController::GetActiveInstance() == controller) - MenuController::SetActiveInstance(NULL); - delete controller; - } - // Make sure all the windows we created to show the menus have been - // destroyed. - DestroyAllMenuHosts(); - if (result && delegate_) - delegate_->ExecuteCommand(result->GetCommand(), mouse_event_flags); -} - -void MenuItemView::RunMenuForDropAt(Widget* parent, - const gfx::Rect& bounds, - AnchorPosition anchor) { - PrepareForRun(false, false); - - // If there is a menu, hide it so that only one menu is shown during dnd. - MenuController* current_controller = MenuController::GetActiveInstance(); - if (current_controller) { - current_controller->Cancel(MenuController::EXIT_ALL); - } - - // Always create a new controller for non-blocking. - controller_ = new MenuController(false); - - // Set the instance, that way it can be canceled by another menu. - MenuController::SetActiveInstance(controller_); - - controller_->Run(parent, NULL, this, bounds, anchor, NULL); -} - void MenuItemView::Cancel() { if (controller_ && !canceled_) { canceled_ = true; @@ -558,6 +464,11 @@ MenuItemView::MenuItemView(MenuItemView* parent, Init(parent, command, type, NULL); } +MenuItemView::~MenuItemView() { + delete submenu_; + STLDeleteElements(&removed_items_); +} + std::string MenuItemView::GetClassName() const { return kViewClassName; } @@ -612,23 +523,6 @@ void MenuItemView::Init(MenuItemView* parent, SetEnabled(root_delegate->IsCommandEnabled(command)); } -void MenuItemView::DropMenuClosed(bool notify_delegate) { - DCHECK(controller_); - DCHECK(!controller_->IsBlockingRun()); - if (MenuController::GetActiveInstance() == controller_) - MenuController::SetActiveInstance(NULL); - delete controller_; - controller_ = NULL; - - RemoveEmptyMenus(); - - if (notify_delegate && delegate_) { - // Our delegate is null when invoked from the destructor. - delegate_->DropMenuClosed(this); - } - // WARNING: its possible the delegate deleted us at this point. -} - void MenuItemView::PrepareForRun(bool has_mnemonics, bool show_mnemonics) { // Currently we only support showing the root. DCHECK(!parent_menu_item_); diff --git a/views/controls/menu/menu_item_view.h b/views/controls/menu/menu_item_view.h index af8f0f0..6b91c8d 100644 --- a/views/controls/menu/menu_item_view.h +++ b/views/controls/menu/menu_item_view.h @@ -33,13 +33,16 @@ class MenuModel; namespace views { +namespace internal { +class MenuRunnerImpl; +} + class MenuButton; +struct MenuConfig; class MenuController; class MenuDelegate; class SubmenuView; -struct MenuConfig; - // MenuItemView -------------------------------------------------------------- // MenuItemView represents a single menu item with a label and optional icon. @@ -59,12 +62,8 @@ struct MenuConfig; // focus from the hosting window child views do not actually get focus. Instead // |SetHotTracked| is used as the user navigates around. // -// There are two ways to show a MenuItemView: -// 1. Use RunMenuAt. This blocks the caller, executing the selected command -// on success. -// 2. Use RunMenuForDropAt. This is intended for use during a drop session -// and does NOT block the caller. Instead the delegate is notified when the -// menu closes via the DropMenuClosed method. +// To show the menu use MenuRunner. See MenuRunner for details on how to run +// (show) the menu as well as for details on the life time of the menu. class VIEWS_EXPORT MenuItemView : public View { public: @@ -111,8 +110,6 @@ class VIEWS_EXPORT MenuItemView : public View { // shown to the user, rather its use as the parent for all menu items. explicit MenuItemView(MenuDelegate* delegate); - virtual ~MenuItemView(); - // Overridden from View: virtual bool GetTooltipText(const gfx::Point& p, std::wstring* tooltip) OVERRIDE; @@ -130,20 +127,6 @@ class VIEWS_EXPORT MenuItemView : public View { static string16 GetAccessibleNameForMenuItem( const string16& item_text, const string16& accelerator_text); - // Run methods. See description above class for details. Both Run methods take - // a rectangle, which is used to position the menu. |has_mnemonics| indicates - // whether the items have mnemonics. Mnemonics are identified by way of the - // character following the '&'. The anchor position is specified for non-RTL - // languages; the opposite value will be used for RTL. - void RunMenuAt(Widget* parent, - MenuButton* button, - const gfx::Rect& bounds, - AnchorPosition anchor, - bool has_mnemonics); - void RunMenuForDropAt(Widget* parent, - const gfx::Rect& bounds, - AnchorPosition anchor); - // Hides and cancels the menu. This does nothing if the menu is not open. void Cancel(); @@ -288,6 +271,7 @@ class VIEWS_EXPORT MenuItemView : public View { // Returns the delegate. This returns the delegate of the root menu item. MenuDelegate* GetDelegate(); + void set_delegate(MenuDelegate* delegate) { delegate_ = delegate; } // Returns the root parent, or this if this has no parent. MenuItemView* GetRootMenuItem(); @@ -334,11 +318,16 @@ class VIEWS_EXPORT MenuItemView : public View { // Creates a MenuItemView. This is used by the various AddXXX methods. MenuItemView(MenuItemView* parent, int command, Type type); + // MenuRunner owns MenuItemView and should be the only one deleting it. + virtual ~MenuItemView(); + virtual void ChildPreferredSizeChanged(View* child) OVERRIDE; virtual std::string GetClassName() const OVERRIDE; private: + friend class internal::MenuRunnerImpl; // For access to ~MenuItemView. + // Calculates all sizes that we can from the OS. // // This is invoked prior to Running a menu. @@ -350,10 +339,6 @@ class VIEWS_EXPORT MenuItemView : public View { MenuItemView::Type type, MenuDelegate* delegate); - // Invoked by the MenuController when the menu closes as the result of - // drag and drop run. - void DropMenuClosed(bool notify_delegate); - // The RunXXX methods call into this to set up the necessary state before // running. void PrepareForRun(bool has_mnemonics, bool show_mnemonics); @@ -418,13 +403,14 @@ class VIEWS_EXPORT MenuItemView : public View { actual_menu_position_ = actual_menu_position; } + void set_controller(MenuController* controller) { controller_ = controller; } + // The delegate. This is only valid for the root menu item. You shouldn't // use this directly, instead use GetDelegate() which walks the tree as // as necessary. MenuDelegate* delegate_; - // Returns the controller for the run operation, or NULL if the menu isn't - // showing. + // The controller for the run operation, or NULL if the menu isn't showing. MenuController* controller_; // Used to detect when Cancel was invoked. diff --git a/views/controls/menu/menu_model_adapter.cc b/views/controls/menu/menu_model_adapter.cc index e2c895d..850b6b5 100644 --- a/views/controls/menu/menu_model_adapter.cc +++ b/views/controls/menu/menu_model_adapter.cc @@ -44,6 +44,12 @@ void MenuModelAdapter::BuildMenu(MenuItemView* menu) { menu->ChildrenChanged(); } +MenuItemView* MenuModelAdapter::CreateMenu() { + MenuItemView* item = new MenuItemView(this); + BuildMenu(item); + return item; +} + // MenuModelAdapter, MenuDelegate implementation: void MenuModelAdapter::ExecuteCommand(int id) { diff --git a/views/controls/menu/menu_model_adapter.h b/views/controls/menu/menu_model_adapter.h index a1a90a2..ba50ef4 100644 --- a/views/controls/menu/menu_model_adapter.h +++ b/views/controls/menu/menu_model_adapter.h @@ -30,6 +30,10 @@ class VIEWS_EXPORT MenuModelAdapter : public MenuDelegate { // (including submenus). virtual void BuildMenu(MenuItemView* menu); + // Convenience for creating and populating a menu. The caller owns the + // returned MenuItemView. + MenuItemView* CreateMenu(); + void set_triggerable_event_flags(int triggerable_event_flags) { triggerable_event_flags_ = triggerable_event_flags; } diff --git a/views/controls/menu/menu_model_adapter_unittest.cc b/views/controls/menu/menu_model_adapter_unittest.cc index 035ec16..f797d40 100644 --- a/views/controls/menu/menu_model_adapter_unittest.cc +++ b/views/controls/menu/menu_model_adapter_unittest.cc @@ -7,6 +7,7 @@ #include "ui/base/models/menu_model_delegate.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/menu/submenu_view.h" #include "views/test/views_test_base.h" @@ -200,9 +201,11 @@ TEST_F(MenuModelAdapterTest, BasicTest) { views::MenuModelAdapter delegate(&model); // Create menu. Build menu twice to check that rebuilding works properly. - scoped_ptr<views::MenuItemView> menu(new views::MenuItemView(&delegate)); - delegate.BuildMenu(menu.get()); - delegate.BuildMenu(menu.get()); + MenuItemView* menu = new views::MenuItemView(&delegate); + // MenuRunner takes ownership of menu. + scoped_ptr<MenuRunner> menu_runner(new MenuRunner(menu)); + delegate.BuildMenu(menu); + delegate.BuildMenu(menu); EXPECT_TRUE(menu->HasSubmenu()); // Check top level menu items. @@ -297,7 +300,7 @@ TEST_F(MenuModelAdapterTest, BasicTest) { // Check that selecting the root item is safe. The MenuModel does // not care about the root so MenuModelAdapter should do nothing // (not hit the NOTREACHED check) when the root is selected. - static_cast<views::MenuDelegate*>(&delegate)->SelectionChanged(menu.get()); + static_cast<views::MenuDelegate*>(&delegate)->SelectionChanged(menu); } } // namespace views diff --git a/views/controls/menu/menu_runner.cc b/views/controls/menu/menu_runner.cc index 1941d69a..89cb114 100644 --- a/views/controls/menu/menu_runner.cc +++ b/views/controls/menu/menu_runner.cc @@ -4,30 +4,69 @@ #include "views/controls/menu/menu_runner.h" +#include <set> + +#include "views/controls/button/menu_button.h" +#include "views/controls/menu/menu_controller.h" +#include "views/controls/menu/menu_controller_delegate.h" +#include "views/controls/menu/menu_delegate.h" + +#if defined(OS_WIN) +#include "base/win/win_util.h" +#endif + namespace views { -// Manages the menu. To destroy a Holder invoke Release(). Release() deletes -// immediately if the menu isn't showing. If the menu is showing Release() -// cancels the menu and when the nested RunMenuAt() call returns, deletes itself -// and the menu. -class MenuRunner::Holder { +namespace internal { + +// Manages the menu. To destroy a MenuRunnerImpl invoke Release(). Release() +// deletes immediately if the menu isn't showing. If the menu is showing +// Release() cancels the menu and when the nested RunMenuAt() call returns +// deletes itself and the menu. +class MenuRunnerImpl : public internal::MenuControllerDelegate { public: - explicit Holder(MenuItemView* menu); + explicit MenuRunnerImpl(MenuItemView* menu); + + MenuItemView* menu() { return menu_; } // See description above class for details. void Release(); // Runs the menu. - void RunMenuAt(Widget* parent, - MenuButton* button, - const gfx::Rect& bounds, - MenuItemView::AnchorPosition anchor, - bool has_mnemonics); + MenuRunner::RunResult RunMenuAt(Widget* parent, + MenuButton* button, + const gfx::Rect& bounds, + MenuItemView::AnchorPosition anchor, + int32 types) WARN_UNUSED_RESULT; + + void Cancel(); + + // MenuControllerDelegate: + virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) OVERRIDE; + virtual void SiblingMenuCreated(MenuItemView* menu) OVERRIDE; private: - ~Holder(); + ~MenuRunnerImpl(); + + // Cleans up after the menu is no longer showing. |result| is the menu that + // the user selected, or NULL if nothing was selected. + MenuRunner::RunResult MenuDone(MenuItemView* result, int mouse_event_flags); + + // Returns true if mnemonics should be shown in the menu. + bool ShouldShowMnemonics(MenuButton* button); + + // The menu. We own this. We don't use scoped_ptr as the destructor is + // protected and we're a friend. + MenuItemView* menu_; + + // Any sibling menus. Does not include |menu_|. We own these too. + std::set<MenuItemView*> sibling_menus_; - scoped_ptr<MenuItemView> menu_; + // Created and set as the delegate of the MenuItemView if Release() is + // invoked. This is done to make sure the delegate isn't notified after + // Release() is invoked. We do this as we assume the delegate is no longer + // valid if MenuRunner has been deleted. + scoped_ptr<MenuDelegate> empty_delegate_; // Are we in run waiting for it to return? bool running_; @@ -35,63 +74,198 @@ class MenuRunner::Holder { // Set if |running_| and Release() has been invoked. bool delete_after_run_; - DISALLOW_COPY_AND_ASSIGN(Holder); + // Are we running for a drop? + bool for_drop_; + + // The controller. + MenuController* controller_; + + // Do we own the controller? + bool owns_controller_; + + DISALLOW_COPY_AND_ASSIGN(MenuRunnerImpl); }; -MenuRunner::Holder::Holder(MenuItemView* menu) +MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu) : menu_(menu), running_(false), - delete_after_run_(false) { + delete_after_run_(false), + for_drop_(false), + controller_(NULL), + owns_controller_(false) { } -void MenuRunner::Holder::Release() { +void MenuRunnerImpl::Release() { if (running_) { + if (delete_after_run_) + return; // We already canceled. + // The menu is running a nested message loop, we can't delete it now // otherwise the stack would be in a really bad state (many frames would // have deleted objects on them). Instead cancel the menu, when it returns // Holder will delete itself. delete_after_run_ = true; + + // Swap in a different delegate. That way we know the original MenuDelegate + // won't be notified later on (when it's likely already been deleted). + if (!empty_delegate_.get()) + empty_delegate_.reset(new MenuDelegate()); + menu_->set_delegate(empty_delegate_.get()); + menu_->Cancel(); } else { delete this; } } -void MenuRunner::Holder::RunMenuAt(Widget* parent, - MenuButton* button, - const gfx::Rect& bounds, - MenuItemView::AnchorPosition anchor, - bool has_mnemonics) { +MenuRunner::RunResult MenuRunnerImpl::RunMenuAt( + Widget* parent, + MenuButton* button, + const gfx::Rect& bounds, + MenuItemView::AnchorPosition anchor, + int32 types) { if (running_) { // Ignore requests to show the menu while it's already showing. MenuItemView // doesn't handle this very well (meaning it crashes). - return; + return MenuRunner::NORMAL_EXIT; + } + + MenuController* controller = MenuController::GetActiveInstance(); + if (controller) { + if ((types & MenuRunner::IS_NESTED) != 0) { + if (!controller->IsBlockingRun()) { + controller->CancelAll(); + controller = NULL; + } + } else { + // There's some other menu open and we're not nested. Cancel the menu. + controller->CancelAll(); + if ((types & MenuRunner::FOR_DROP) == 0) { + // We can't open another menu, otherwise the message loop would become + // twice nested. This isn't necessarily a problem, but generally isn't + // expected. + return MenuRunner::NORMAL_EXIT; + } + // Drop menus don't block the message loop, so it's ok to create a new + // MenuController. + controller = NULL; + } } running_ = true; - menu_->RunMenuAt(parent, button, bounds, anchor, has_mnemonics); + for_drop_ = (types & MenuRunner::FOR_DROP) != 0; + bool has_mnemonics = (types & MenuRunner::HAS_MNEMONICS) != 0 && !for_drop_; + menu_->PrepareForRun(has_mnemonics, + !for_drop_ && ShouldShowMnemonics(button)); + int mouse_event_flags = 0; + owns_controller_ = false; + if (!controller) { + // No menus are showing, show one. + controller = new MenuController(!for_drop_, this); + owns_controller_ = true; + } + controller_ = controller; + menu_->set_controller(controller_); + + // Run the loop. + MenuItemView* result = controller->Run(parent, button, menu_, bounds, anchor, + &mouse_event_flags); + + if (for_drop_) { + // Drop menus return immediately. We finish processing in DropMenuClosed. + return MenuRunner::NORMAL_EXIT; + } + + return MenuDone(result, mouse_event_flags); +} + +void MenuRunnerImpl::Cancel() { + if (running_) + controller_->Cancel(MenuController::EXIT_ALL); +} + +void MenuRunnerImpl::DropMenuClosed(NotifyType type, MenuItemView* menu) { + MenuDone(NULL, 0); + + if (type == NOTIFY_DELEGATE && menu->GetDelegate()) { + // Delegate is null when invoked from the destructor. + menu->GetDelegate()->DropMenuClosed(menu); + } +} + +void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) { + if (menu != menu_ && sibling_menus_.count(menu) == 0) + sibling_menus_.insert(menu); +} + +MenuRunnerImpl::~MenuRunnerImpl() { + delete menu_; + for (std::set<MenuItemView*>::iterator i = sibling_menus_.begin(); + i != sibling_menus_.end(); ++i) + delete *i; +} + +MenuRunner::RunResult MenuRunnerImpl::MenuDone(MenuItemView* result, + int mouse_event_flags) { + menu_->RemoveEmptyMenus(); + menu_->set_controller(NULL); + + if (owns_controller_) { + // We created the controller and need to delete it. + delete controller_; + owns_controller_ = false; + } + controller_ = NULL; + // Make sure all the windows we created to show the menus have been + // destroyed. + menu_->DestroyAllMenuHosts(); if (delete_after_run_) { delete this; - return; + return MenuRunner::MENU_DELETED; } running_ = false; + if (result && menu_->GetDelegate()) { + menu_->GetDelegate()->ExecuteCommand(result->GetCommand(), + mouse_event_flags); + } + return MenuRunner::NORMAL_EXIT; } -MenuRunner::Holder::~Holder() { +bool MenuRunnerImpl::ShouldShowMnemonics(MenuButton* button) { + // Show mnemonics if the button has focus or alt is pressed. + bool show_mnemonics = button ? button->HasFocus() : false; +#if defined(OS_WIN) + // We don't currently need this on gtk as showing the menu gives focus to the + // button first. + if (!show_mnemonics) + show_mnemonics = base::win::IsAltPressed(); +#endif + return show_mnemonics; } -MenuRunner::MenuRunner(MenuItemView* menu) : holder_(new Holder(menu)) { +} // namespace internal + +MenuRunner::MenuRunner(MenuItemView* menu) + : holder_(new internal::MenuRunnerImpl(menu)) { } MenuRunner::~MenuRunner() { holder_->Release(); } -void MenuRunner::RunMenuAt(Widget* parent, - MenuButton* button, - const gfx::Rect& bounds, - MenuItemView::AnchorPosition anchor, - bool has_mnemonics) { - holder_->RunMenuAt(parent, button, bounds, anchor, has_mnemonics); +MenuItemView* MenuRunner::GetMenu() { + return holder_->menu(); +} + +MenuRunner::RunResult MenuRunner::RunMenuAt(Widget* parent, + MenuButton* button, + const gfx::Rect& bounds, + MenuItemView::AnchorPosition anchor, + int32 types) { + return holder_->RunMenuAt(parent, button, bounds, anchor, types); +} + +void MenuRunner::Cancel() { + holder_->Cancel(); } } // namespace views diff --git a/views/controls/menu/menu_runner.h b/views/controls/menu/menu_runner.h index 1337d08..5cdb845 100644 --- a/views/controls/menu/menu_runner.h +++ b/views/controls/menu/menu_runner.h @@ -7,6 +7,7 @@ #pragma once #include "base/basictypes.h" +#include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "views/controls/menu/menu_item_view.h" @@ -15,32 +16,73 @@ namespace views { class MenuButton; class Widget; -// MenuRunner handles the lifetime of the root MenuItemView. MenuItemView runs a -// nested message loop, which means care must be taken when the MenuItemView -// needs to be deleted. MenuRunner makes sure the menu is deleted after the -// nested message loop completes. -// -// MenuRunner can be deleted at any time and will correctly handle deleting the -// underlying menu. +namespace internal { +class MenuRunnerImpl; +} + +// MenuRunner is responsible for showing (running) the menu and additionally +// owning the MenuItemView. RunMenuAt() runs a nested message loop. It is safe +// to delete MenuRunner at any point, but MenuRunner internally only deletes the +// MenuItemView *after* the nested message loop completes. If MenuRunner is +// deleted while the menu is showing the delegate of the menu is reset. This is +// done to ensure delegates aren't notified after they may have been deleted. // -// TODO: this is a work around for 57890. If we fix it this class shouldn't be -// needed. +// NOTE: while you can delete a MenuRunner at any point, the nested message loop +// won't return immediately. This means if you delete the object that owns +// the MenuRunner while the menu is running, your object is effectively still +// on the stack. A return value of MENU_DELETED indicated this. In most cases +// if RunMenuAt() returns MENU_DELETED, you should return immediately. class VIEWS_EXPORT MenuRunner { public: + enum RunTypes { + // The menu has mnemonics. + HAS_MNEMONICS = 1 << 0, + + // The menu is a nested context menu. For example, click a folder on the + // bookmark bar, then right click an entry to get its context menu. + IS_NESTED = 1 << 1, + + // Used for showing a menu during a drop operation. This does NOT block the + // caller, instead the delegate is notified when the menu closes via the + // DropMenuClosed method. + FOR_DROP = 1 << 2, + }; + + enum RunResult { + // Indicates RunMenuAt is returning because the MenuRunner was deleted. + MENU_DELETED, + + // Indicates RunMenuAt returned and MenuRunner was not deleted. + NORMAL_EXIT + }; + + // Creates a new MenuRunner. MenuRunner owns the supplied menu. explicit MenuRunner(MenuItemView* menu); ~MenuRunner(); - // Runs the menu. - void RunMenuAt(Widget* parent, - MenuButton* button, - const gfx::Rect& bounds, - MenuItemView::AnchorPosition anchor, - bool has_mnemonics); + // Returns the menu. + MenuItemView* GetMenu(); - private: - class Holder; + // Takes ownership of |menu|, deleting it when MenuRunner is deleted. You + // only need call this if you create additional menus from + // MenuDelegate::GetSiblingMenu. + void OwnMenu(MenuItemView* menu); + + // Runs the menu. |types| is a bitmask of RunTypes. If this returns + // MENU_DELETED the method is returning because the MenuRunner was deleted. + // Typically callers should NOT do any processing if this returns + // MENU_DELETED. + RunResult RunMenuAt(Widget* parent, + MenuButton* button, + const gfx::Rect& bounds, + MenuItemView::AnchorPosition anchor, + int32 types) WARN_UNUSED_RESULT; - Holder* holder_; + // Hides and cancels the menu. This does nothing if the menu is not open. + void Cancel(); + + private: + internal::MenuRunnerImpl* holder_; DISALLOW_COPY_AND_ASSIGN(MenuRunner); }; diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc index 468a9c9..5b43215 100644 --- a/views/controls/textfield/native_textfield_views.cc +++ b/views/controls/textfield/native_textfield_views.cc @@ -22,6 +22,7 @@ #include "views/controls/focusable_border.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_model_adapter.h" +#include "views/controls/menu/menu_runner.h" #include "views/controls/textfield/textfield.h" #include "views/controls/textfield/textfield_controller.h" #include "views/controls/textfield/textfield_views_model.h" @@ -298,11 +299,11 @@ void NativeTextfieldViews::ShowContextMenuForView(View* source, const gfx::Point& p, bool is_mouse_gesture) { UpdateContextMenu(); - context_menu_menu_->RunMenuAt(GetWidget(), - NULL, - gfx::Rect(p, gfx::Size()), - views::MenuItemView::TOPLEFT, - true); + if (context_menu_runner_->RunMenuAt( + GetWidget(), NULL, gfx::Rect(p, gfx::Size()), + views::MenuItemView::TOPLEFT, MenuRunner::HAS_MNEMONICS) == + MenuRunner::MENU_DELETED) + return; } ///////////////////////////////////////////////////////////////// @@ -975,11 +976,11 @@ void NativeTextfieldViews::UpdateContextMenu() { context_menu_delegate_.reset( new views::MenuModelAdapter(context_menu_contents_.get())); - context_menu_menu_.reset( - new views::MenuItemView(context_menu_delegate_.get())); + context_menu_runner_.reset( + new MenuRunner(new views::MenuItemView(context_menu_delegate_.get()))); } - context_menu_delegate_->BuildMenu(context_menu_menu_.get()); + context_menu_delegate_->BuildMenu(context_menu_runner_->GetMenu()); } void NativeTextfieldViews::OnTextInputTypeChanged() { diff --git a/views/controls/textfield/native_textfield_views.h b/views/controls/textfield/native_textfield_views.h index 1e26eb2..8849a3b 100644 --- a/views/controls/textfield/native_textfield_views.h +++ b/views/controls/textfield/native_textfield_views.h @@ -31,8 +31,8 @@ namespace views { class FocusableBorder; class KeyEvent; -class MenuItemView; class MenuModelAdapter; +class MenuRunner; // A views/skia only implementation of NativeTextfieldWrapper. // No platform specific code is used. @@ -249,7 +249,7 @@ class VIEWS_EXPORT NativeTextfieldViews : public TouchSelectionClientView, // Context menu and its content list for the textfield. scoped_ptr<ui::SimpleMenuModel> context_menu_contents_; scoped_ptr<views::MenuModelAdapter> context_menu_delegate_; - scoped_ptr<views::MenuItemView> context_menu_menu_; + scoped_ptr<views::MenuRunner> context_menu_runner_; scoped_ptr<TouchSelectionController> touch_selection_controller_; diff --git a/views/views.gyp b/views/views.gyp index e82ea8b..6a6416d 100644 --- a/views/views.gyp +++ b/views/views.gyp @@ -103,6 +103,7 @@ 'controls/menu/menu_config_win.cc', 'controls/menu/menu_controller.cc', 'controls/menu/menu_controller.h', + 'controls/menu/menu_controller_delegate.h', 'controls/menu/menu_delegate.cc', 'controls/menu/menu_delegate.h', 'controls/menu/menu_gtk.cc', |