summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/bookmarks/bookmark_context_menu_controller.cc20
-rw-r--r--chrome/browser/bookmarks/bookmark_context_menu_controller.h7
-rw-r--r--chrome/browser/views/bookmark_bar_view_test.cc78
-rw-r--r--chrome/browser/views/bookmark_context_menu.cc18
-rw-r--r--chrome/browser/views/bookmark_context_menu.h21
-rw-r--r--chrome/browser/views/bookmark_menu_controller_views.cc81
-rw-r--r--chrome/browser/views/bookmark_menu_controller_views.h23
-rw-r--r--views/controls/menu/menu_controller.cc70
-rw-r--r--views/controls/menu/menu_controller.h24
-rw-r--r--views/controls/menu/menu_delegate.h9
-rw-r--r--views/controls/menu/menu_item_view.cc14
-rw-r--r--views/controls/menu/menu_item_view.h8
-rw-r--r--views/controls/menu/submenu_view.cc4
-rw-r--r--views/controls/menu/submenu_view.h3
14 files changed, 342 insertions, 38 deletions
diff --git a/chrome/browser/bookmarks/bookmark_context_menu_controller.cc b/chrome/browser/bookmarks/bookmark_context_menu_controller.cc
index a46110b..0ce5386 100644
--- a/chrome/browser/bookmarks/bookmark_context_menu_controller.cc
+++ b/chrome/browser/bookmarks/bookmark_context_menu_controller.cc
@@ -278,6 +278,8 @@ void BookmarkContextMenuController::BuildMenu() {
}
void BookmarkContextMenuController::ExecuteCommand(int id) {
+ BookmarkModel* model = RemoveModelObserver();
+
switch (id) {
case IDS_BOOMARK_BAR_OPEN_ALL:
case IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO:
@@ -327,12 +329,13 @@ void BookmarkContextMenuController::ExecuteCommand(int id) {
case IDS_BOOKMARK_BAR_REMOVE: {
UserMetrics::RecordAction("BookmarkBar_ContextMenu_Remove", profile_);
- BookmarkModel* model = RemoveModelObserver();
+ delegate_->WillRemoveBookmarks(selection_);
for (size_t i = 0; i < selection_.size(); ++i) {
model->Remove(selection_[i]->GetParent(),
selection_[i]->GetParent()->IndexOfChild(selection_[i]));
}
+ delegate_->DidRemoveBookmarks();
selection_.clear();
break;
}
@@ -387,13 +390,17 @@ void BookmarkContextMenuController::ExecuteCommand(int id) {
case IDS_BOOKMARK_MANAGER_SORT:
UserMetrics::RecordAction("BookmarkManager_Sort", profile_);
- model_->SortChildren(parent_);
+ model->SortChildren(parent_);
break;
- case IDS_COPY:
case IDS_CUT:
- bookmark_utils::CopyToClipboard(profile_->GetBookmarkModel(),
- selection_, id == IDS_CUT);
+ delegate_->WillRemoveBookmarks(selection_);
+ bookmark_utils::CopyToClipboard(model, selection_, false);
+ delegate_->DidRemoveBookmarks();
+ break;
+
+ case IDS_COPY:
+ bookmark_utils::CopyToClipboard(model, selection_, false);
break;
case IDS_PASTE: {
@@ -405,8 +412,7 @@ void BookmarkContextMenuController::ExecuteCommand(int id) {
parent_->IndexOfChild(selection_[0]) : -1;
if (index != -1)
index++;
- bookmark_utils::PasteFromClipboard(profile_->GetBookmarkModel(),
- parent_, index);
+ bookmark_utils::PasteFromClipboard(model, parent_, index);
break;
}
diff --git a/chrome/browser/bookmarks/bookmark_context_menu_controller.h b/chrome/browser/bookmarks/bookmark_context_menu_controller.h
index 0934b70..3812e28 100644
--- a/chrome/browser/bookmarks/bookmark_context_menu_controller.h
+++ b/chrome/browser/bookmarks/bookmark_context_menu_controller.h
@@ -29,6 +29,13 @@ class BookmarkContextMenuControllerDelegate {
virtual void AddItemWithStringId(int command_id, int string_id) = 0;
virtual void AddSeparator() = 0;
virtual void AddCheckboxItem(int command_id) = 0;
+
+ // Sent before bookmarks are removed.
+ virtual void WillRemoveBookmarks(
+ const std::vector<const BookmarkNode*>& bookmarks) {}
+
+ // Sent after bookmarks have been removed.
+ virtual void DidRemoveBookmarks() {}
};
// BookmarkContextMenuController creates and manages state for the context menu
diff --git a/chrome/browser/views/bookmark_bar_view_test.cc b/chrome/browser/views/bookmark_bar_view_test.cc
index 8e24366..3d84ff8 100644
--- a/chrome/browser/views/bookmark_bar_view_test.cc
+++ b/chrome/browser/views/bookmark_bar_view_test.cc
@@ -14,6 +14,7 @@
#include "chrome/common/pref_service.h"
#include "chrome/test/testing_profile.h"
#include "chrome/test/interactive_ui/view_event_test_base.h"
+#include "grit/generated_resources.h"
#include "views/controls/button/menu_button.h"
#include "views/controls/button/text_button.h"
#include "views/controls/menu/menu_controller.h"
@@ -38,8 +39,8 @@ class TestingPageNavigator : public PageNavigator {
} // namespace
// Base class for event generating bookmark view tests. These test are intended
-// to exercise ChromeMenus, but that's easier done with BookmarkBarView rather
-// than ChromeMenu itself.
+// to exercise View's menus, but that's easier done with BookmarkBarView rather
+// than View's menu itself.
//
// SetUp creates a bookmark model with the following structure.
// All folders are in upper case, all URLs in lower case.
@@ -1101,3 +1102,76 @@ class BookmarkBarViewTest14 : public BookmarkBarViewEventTestBase {
};
VIEW_TEST(BookmarkBarViewTest14, ContextMenus2)
+
+// Makes sure deleting from the context menu keeps the bookmark menu showing.
+class BookmarkBarViewTest15 : public BookmarkBarViewEventTestBase {
+ public:
+ BookmarkBarViewTest15() : deleted_menu_id_(0) {}
+
+ protected:
+ virtual void DoTestOnMessageLoop() {
+ // Show the other bookmarks.
+ views::TextButton* button = bb_view_->other_bookmarked_button();
+ ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT,
+ ui_controls::DOWN | ui_controls::UP,
+ CreateEventTask(this, &BookmarkBarViewTest15::Step2));
+ }
+
+ private:
+ void Step2() {
+ // Menu should be showing.
+ views::MenuItemView* menu = bb_view_->GetMenu();
+ ASSERT_TRUE(menu != NULL);
+ ASSERT_TRUE(menu->GetSubmenu()->IsShowing());
+
+ views::MenuItemView* child_menu =
+ menu->GetSubmenu()->GetMenuItemAt(1);
+ ASSERT_TRUE(child_menu != NULL);
+
+ deleted_menu_id_ = child_menu->GetCommand();
+
+ // Right click on the second child to get its context menu.
+ ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::RIGHT,
+ ui_controls::DOWN | ui_controls::UP,
+ CreateEventTask(this, &BookmarkBarViewTest15::Step3));
+ }
+
+ void Step3() {
+ // Make sure the context menu is showing.
+ views::MenuItemView* menu = bb_view_->GetContextMenu();
+ ASSERT_TRUE(menu != NULL);
+ ASSERT_TRUE(menu->GetSubmenu());
+ ASSERT_TRUE(menu->GetSubmenu()->IsShowing());
+
+ views::MenuItemView* delete_menu =
+ menu->GetMenuItemByID(IDS_BOOKMARK_BAR_REMOVE);
+ ASSERT_TRUE(delete_menu);
+
+ // Click on the delete button.
+ ui_controls::MoveMouseToCenterAndPress(delete_menu,
+ ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP,
+ CreateEventTask(this, &BookmarkBarViewTest15::Step4));
+ }
+
+ void Step4() {
+ // The context menu should not be showing.
+ views::MenuItemView* context_menu = bb_view_->GetContextMenu();
+ ASSERT_TRUE(context_menu == NULL);
+
+ // But the menu should be showing.
+ views::MenuItemView* menu = bb_view_->GetMenu();
+ ASSERT_TRUE(menu != NULL);
+ ASSERT_TRUE(menu->GetSubmenu()->IsShowing());
+
+ // And the deleted_menu_id_ should have been removed.
+ ASSERT_TRUE(menu->GetMenuItemByID(deleted_menu_id_) == NULL);
+
+ bb_view_->GetMenu()->GetMenuController()->Cancel(true);
+
+ Done();
+ }
+
+ int deleted_menu_id_;
+};
+
+VIEW_TEST(BookmarkBarViewTest15, MenuStaysVisibleAfterDelete)
diff --git a/chrome/browser/views/bookmark_context_menu.cc b/chrome/browser/views/bookmark_context_menu.cc
index a3f3a15..21eb200 100644
--- a/chrome/browser/views/bookmark_context_menu.cc
+++ b/chrome/browser/views/bookmark_context_menu.cc
@@ -25,7 +25,8 @@ BookmarkContextMenu::BookmarkContextMenu(
parent, selection,
configuration))),
parent_window_(parent_window),
- ALLOW_THIS_IN_INITIALIZER_LIST(menu_(new views::MenuItemView(this))) {
+ ALLOW_THIS_IN_INITIALIZER_LIST(menu_(new views::MenuItemView(this))),
+ observer_(NULL) {
controller_->BuildMenu();
}
@@ -56,6 +57,10 @@ bool BookmarkContextMenu::IsCommandEnabled(int command_id) const {
return controller_->IsCommandEnabled(command_id);
}
+bool BookmarkContextMenu::ShouldCloseAllMenusOnExecute(int id) {
+ return id != IDS_BOOKMARK_BAR_REMOVE;
+}
+
////////////////////////////////////////////////////////////////////////////////
// BookmarkContextMenu, BookmarkContextMenuControllerDelegate implementation:
@@ -79,3 +84,14 @@ void BookmarkContextMenu::AddCheckboxItem(int command_id) {
menu_->AppendMenuItem(command_id, l10n_util::GetString(command_id),
views::MenuItemView::CHECKBOX);
}
+
+void BookmarkContextMenu::WillRemoveBookmarks(
+ const std::vector<const BookmarkNode*>& bookmarks) {
+ if (observer_)
+ observer_->WillRemoveBookmarks(bookmarks);
+}
+
+void BookmarkContextMenu::DidRemoveBookmarks() {
+ if (observer_)
+ observer_->DidRemoveBookmarks();
+}
diff --git a/chrome/browser/views/bookmark_context_menu.h b/chrome/browser/views/bookmark_context_menu.h
index 94c2d97..47b8ade 100644
--- a/chrome/browser/views/bookmark_context_menu.h
+++ b/chrome/browser/views/bookmark_context_menu.h
@@ -8,6 +8,17 @@
#include "chrome/browser/bookmarks/bookmark_context_menu_controller.h"
#include "views/controls/menu/menu_delegate.h"
+// Observer for the BookmarkContextMenu.
+class BookmarkContextMenuObserver {
+ public:
+ // Invoked before the specified items are removed from the bookmark model.
+ virtual void WillRemoveBookmarks(
+ const std::vector<const BookmarkNode*>& bookmarks) = 0;
+
+ // Invoked after the items have been removed from the model.
+ virtual void DidRemoveBookmarks() = 0;
+};
+
class BookmarkContextMenu : public BookmarkContextMenuControllerDelegate,
public views::MenuDelegate {
public:
@@ -25,10 +36,15 @@ class BookmarkContextMenu : public BookmarkContextMenuControllerDelegate,
views::MenuItemView* menu() const { return menu_.get(); }
+ void set_observer(BookmarkContextMenuObserver* observer) {
+ observer_ = observer;
+ }
+
// Overridden from views::MenuDelegate:
virtual void ExecuteCommand(int command_id);
virtual bool IsItemChecked(int command_id) const;
virtual bool IsCommandEnabled(int command_id) const;
+ virtual bool ShouldCloseAllMenusOnExecute(int id);
// Overridden from BookmarkContextMenuControllerDelegate:
virtual void CloseMenu();
@@ -36,6 +52,9 @@ class BookmarkContextMenu : public BookmarkContextMenuControllerDelegate,
virtual void AddItemWithStringId(int command_id, int string_id);
virtual void AddSeparator();
virtual void AddCheckboxItem(int command_id);
+ virtual void WillRemoveBookmarks(
+ const std::vector<const BookmarkNode*>& bookmarks);
+ virtual void DidRemoveBookmarks();
private:
scoped_ptr<BookmarkContextMenuController> controller_;
@@ -46,6 +65,8 @@ class BookmarkContextMenu : public BookmarkContextMenuControllerDelegate,
// The menu itself.
scoped_ptr<views::MenuItemView> menu_;
+ BookmarkContextMenuObserver* observer_;
+
DISALLOW_COPY_AND_ASSIGN(BookmarkContextMenu);
};
diff --git a/chrome/browser/views/bookmark_menu_controller_views.cc b/chrome/browser/views/bookmark_menu_controller_views.cc
index 5a7256b..cb334ed 100644
--- a/chrome/browser/views/bookmark_menu_controller_views.cc
+++ b/chrome/browser/views/bookmark_menu_controller_views.cc
@@ -206,6 +206,7 @@ bool BookmarkMenuController::ShowContextMenu(MenuItemView* source,
nodes[0]->GetParent(),
nodes,
BookmarkContextMenuController::BOOKMARK_BAR));
+ context_menu_->set_observer(this);
context_menu_->RunMenuAt(gfx::Point(x, y));
context_menu_.reset(NULL);
return true;
@@ -271,8 +272,7 @@ void BookmarkMenuController::BookmarkModelChanged() {
void BookmarkMenuController::BookmarkNodeFavIconLoaded(
BookmarkModel* model, const BookmarkNode* node) {
- std::map<const BookmarkNode*, int>::iterator menu_pair =
- node_to_menu_id_map_.find(node);
+ NodeToMenuIDMap::iterator menu_pair = node_to_menu_id_map_.find(node);
if (menu_pair == node_to_menu_id_map_.end())
return; // We're not showing a menu item for the node.
@@ -287,6 +287,19 @@ void BookmarkMenuController::BookmarkNodeFavIconLoaded(
}
}
+void BookmarkMenuController::WillRemoveBookmarks(
+ const std::vector<const BookmarkNode*>& bookmarks) {
+ std::set<MenuItemView*> removed_menus;
+
+ WillRemoveBookmarksImpl(bookmarks, &removed_menus);
+
+ STLDeleteElements(&removed_menus);
+}
+
+void BookmarkMenuController::DidRemoveBookmarks() {
+ profile_->GetBookmarkModel()->AddObserver(this);
+}
+
MenuItemView* BookmarkMenuController::CreateMenu(const BookmarkNode* parent,
int start_child_index) {
MenuItemView* menu = new MenuItemView(this);
@@ -337,6 +350,7 @@ void BookmarkMenuController::BuildMenu(const BookmarkNode* parent,
GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER);
MenuItemView* submenu =
menu->AppendSubMenuWithIcon(id, node->GetTitle(), *folder_icon);
+ node_to_menu_id_map_[node] = id;
BuildMenu(node, 0, submenu, next_menu_id);
} else {
NOTREACHED();
@@ -351,3 +365,66 @@ BookmarkMenuController::~BookmarkMenuController() {
observer_->BookmarkMenuDeleted(this);
STLDeleteValues(&node_to_menu_map_);
}
+
+MenuItemView* BookmarkMenuController::GetMenuByID(int id) {
+ for (NodeToMenuMap::const_iterator i = node_to_menu_map_.begin();
+ i != node_to_menu_map_.end(); ++i) {
+ MenuItemView* menu = i->second->GetMenuItemByID(id);
+ if (menu)
+ return menu;
+ }
+ return NULL;
+}
+
+void BookmarkMenuController::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.
+ 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->GetParent()->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/views/bookmark_menu_controller_views.h b/chrome/browser/views/bookmark_menu_controller_views.h
index f19c3cc..13e1295 100644
--- a/chrome/browser/views/bookmark_menu_controller_views.h
+++ b/chrome/browser/views/bookmark_menu_controller_views.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_VIEWS_BOOKMARK_MENU_CONTROLLER_VIEWS_H_
#include <map>
+#include <set>
#include "app/gfx/native_widget_types.h"
#include "chrome/browser/bookmarks/base_bookmark_model_observer.h"
@@ -35,7 +36,8 @@ class Profile;
// BookmarkMenuController deletes itself as necessary, although the menu can
// be explicitly hidden by way of the Cancel method.
class BookmarkMenuController : public BaseBookmarkModelObserver,
- public views::MenuDelegate {
+ public views::MenuDelegate,
+ public BookmarkContextMenuObserver {
public:
// The observer is notified prior to the menu being deleted.
class Observer {
@@ -112,7 +114,14 @@ class BookmarkMenuController : public BaseBookmarkModelObserver,
virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model,
const BookmarkNode* node);
+ // BookmarkContextMenu::Observer methods.
+ virtual void WillRemoveBookmarks(
+ const std::vector<const BookmarkNode*>& bookmarks);
+ virtual void DidRemoveBookmarks();
+
private:
+ typedef std::map<const BookmarkNode*, int> NodeToMenuIDMap;
+
// BookmarkMenuController deletes itself as necessary.
~BookmarkMenuController();
@@ -132,6 +141,16 @@ class BookmarkMenuController : public BaseBookmarkModelObserver,
views::MenuItemView* menu,
int* next_menu_id);
+ // 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);
+
Browser* browser_;
Profile* profile_;
@@ -149,7 +168,7 @@ class BookmarkMenuController : public BaseBookmarkModelObserver,
// Mapping from node to menu id. This only contains entries for nodes of type
// URL.
- std::map<const BookmarkNode*, int> node_to_menu_id_map_;
+ NodeToMenuIDMap node_to_menu_id_map_;
// Current menu.
views::MenuItemView* menu_;
diff --git a/views/controls/menu/menu_controller.cc b/views/controls/menu/menu_controller.cc
index ef33bc7..fb35908 100644
--- a/views/controls/menu/menu_controller.cc
+++ b/views/controls/menu/menu_controller.cc
@@ -149,7 +149,7 @@ MenuItemView* MenuController::Run(gfx::NativeWindow parent,
const gfx::Rect& bounds,
MenuItemView::AnchorPosition position,
int* result_mouse_event_flags) {
- exit_all_ = false;
+ exit_type_ = EXIT_NONE;
possible_drag_ = false;
bool nested_menu = showing_;
@@ -228,15 +228,18 @@ MenuItemView* MenuController::Run(gfx::NativeWindow parent,
if (result_mouse_event_flags)
*result_mouse_event_flags = result_mouse_event_flags_;
- if (nested_menu && result) {
- // We're nested and about to return a value. The caller might enter another
- // blocking loop. We need to make sure all menus are hidden before that
- // happens otherwise the menus will stay on screen.
- CloseAllNestedMenus();
-
- // Set exit_all_ to true, which makes sure all nested loops exit
- // immediately.
- exit_all_ = true;
+ if (exit_type_ == EXIT_OUTERMOST) {
+ exit_type_ = EXIT_NONE;
+ } else {
+ if (nested_menu && result) {
+ // We're nested and about to return a value. The caller might enter
+ // another blocking loop. We need to make sure all menus are hidden
+ // before that happens otherwise the menus will stay on screen.
+ CloseAllNestedMenus();
+
+ // Set exit_all_, which makes sure all nested loops exit immediately.
+ exit_type_ = EXIT_ALL;
+ }
}
if (menu_button_) {
@@ -291,7 +294,7 @@ void MenuController::Cancel(bool all) {
}
MenuItemView* selected = state_.item;
- exit_all_ = all;
+ exit_type_ = all ? EXIT_ALL : EXIT_OUTERMOST;
// Hide windows immediately.
SetSelection(NULL, false, true);
@@ -605,7 +608,7 @@ int MenuController::OnPerformDrop(SubmenuView* source,
// Set state such that we exit.
showing_ = false;
- exit_all_ = true;
+ exit_type_ = EXIT_ALL;
if (!IsBlockingRun())
item->GetRootMenuItem()->DropMenuClosed(false);
@@ -648,7 +651,7 @@ void MenuController::SetActiveInstance(MenuController* controller) {
bool MenuController::Dispatch(const MSG& msg) {
DCHECK(blocking_run_);
- if (exit_all_) {
+ if (exit_type_ == EXIT_ALL) {
// We must translate/dispatch the message here, otherwise we would drop
// the message on the floor.
TranslateMessage(&msg);
@@ -697,14 +700,14 @@ bool MenuController::Dispatch(const MSG& msg) {
}
TranslateMessage(&msg);
DispatchMessage(&msg);
- return !exit_all_;
+ return exit_type_ == EXIT_NONE;
}
#else
bool MenuController::Dispatch(GdkEvent* event) {
gtk_main_do_event(event);
- if (exit_all_)
+ if (exit_type_ == EXIT_ALL)
return false;
switch (event->type) {
@@ -721,7 +724,7 @@ bool MenuController::Dispatch(GdkEvent* event) {
break;
}
- return !exit_all_;
+ return exit_type_ == EXIT_NONE;
}
#endif
@@ -799,7 +802,7 @@ bool MenuController::OnKeyDown(int key_code
MenuController::MenuController(bool blocking)
: blocking_run_(blocking),
showing_(false),
- exit_all_(false),
+ exit_type_(EXIT_NONE),
did_capture_(false),
result_(NULL),
result_mouse_event_flags_(0),
@@ -845,7 +848,12 @@ void MenuController::UpdateInitialLocation(
void MenuController::Accept(MenuItemView* item, int mouse_event_flags) {
DCHECK(IsBlockingRun());
result_ = item;
- exit_all_ = true;
+ if (item && !menu_stack_.empty() &&
+ !item->GetDelegate()->ShouldCloseAllMenusOnExecute(item->GetCommand())) {
+ exit_type_ = EXIT_OUTERMOST;
+ } else {
+ exit_type_ = EXIT_ALL;
+ }
result_mouse_event_flags_ = mouse_event_flags;
}
@@ -1114,6 +1122,11 @@ void MenuController::OpenMenu(MenuItemView* item) {
return;
}
+ OpenMenuImpl(item, true);
+ did_capture_ = true;
+}
+
+void MenuController::OpenMenuImpl(MenuItemView* item, bool show) {
bool prefer_leading =
state_.open_leading.empty() ? true : state_.open_leading.back();
bool resulting_direction;
@@ -1122,9 +1135,26 @@ void MenuController::OpenMenu(MenuItemView* item) {
state_.open_leading.push_back(resulting_direction);
bool do_capture = (!did_capture_ && blocking_run_);
showing_submenu_ = true;
- item->GetSubmenu()->ShowAt(owner_, bounds, do_capture);
+ if (show)
+ item->GetSubmenu()->ShowAt(owner_, bounds, do_capture);
+ else
+ item->GetSubmenu()->Reposition(bounds);
showing_submenu_ = false;
- did_capture_ = true;
+}
+
+void MenuController::MenuChildrenChanged(MenuItemView* item) {
+ DCHECK(item);
+ DCHECK(item->GetSubmenu()->IsShowing());
+
+ // Currently this only supports adjusting the bounds of the last menu.
+ DCHECK(item == state_.item->GetParentMenuItem());
+
+ // Make sure the submenu isn't showing for the current item (the position may
+ // have changed or the menu removed). This also moves the selection back to
+ // the parent, which handles the case where the selected item was removed.
+ SetSelection(state_.item->GetParentMenuItem(), true, true);
+
+ OpenMenuImpl(item, false);
}
void MenuController::BuildPathsAndCalculateDiff(
diff --git a/views/controls/menu/menu_controller.h b/views/controls/menu/menu_controller.h
index 2ef1bb7..c92f4b9 100644
--- a/views/controls/menu/menu_controller.h
+++ b/views/controls/menu/menu_controller.h
@@ -98,6 +98,18 @@ class MenuController : public MessageLoopForUI::Dispatcher {
void OnDragExitedScrollButton(SubmenuView* source);
private:
+ // Enumeration of how the menu should exit.
+ enum ExitType {
+ // Don't exit.
+ EXIT_NONE,
+
+ // All menus, including nested, should be exited.
+ EXIT_ALL,
+
+ // Only the outermost menu should be exited.
+ EXIT_OUTERMOST
+ };
+
class MenuScrollTask;
// Tracks selection information.
@@ -243,6 +255,14 @@ class MenuController : public MessageLoopForUI::Dispatcher {
// in anyway.
void OpenMenu(MenuItemView* item);
+ // Implementation of OpenMenu. If |show| is true, this invokes show on the
+ // menu, otherwise Reposition is invoked.
+ void OpenMenuImpl(MenuItemView* item, bool show);
+
+ // Invoked when the children of a menu change and the menu is showing.
+ // This closes any submenus and resizes the submenu.
+ void MenuChildrenChanged(MenuItemView* item);
+
// Builds the paths of the two menu items into the two paths, and
// sets first_diff_at to the location of the first difference between the
// two paths.
@@ -317,8 +337,8 @@ class MenuController : public MessageLoopForUI::Dispatcher {
// If true, we're showing.
bool showing_;
- // If true, all nested run loops should be exited.
- bool exit_all_;
+ // Indicates what to exit.
+ ExitType exit_type_;
// Whether we did a capture. We do a capture only if we're blocking and
// the mouse was down when Run.
diff --git a/views/controls/menu/menu_delegate.h b/views/controls/menu/menu_delegate.h
index cb06f21..cbb295a 100644
--- a/views/controls/menu/menu_delegate.h
+++ b/views/controls/menu/menu_delegate.h
@@ -84,6 +84,15 @@ class MenuDelegate : Controller {
virtual void ExecuteCommand(int id) {
}
+ // If nested menus are showing (nested menus occur when a menu shows a context
+ // menu) this is invoked to determine if all the menus should be closed when
+ // the user selects the menu with the command |id|. This returns true to
+ // indicate that all menus should be closed. Return false if only the
+ // context menu should be closed.
+ virtual bool ShouldCloseAllMenusOnExecute(int id) {
+ return true;
+ }
+
// Executes the specified command. mouse_event_flags give the flags of the
// mouse event that triggered this to be invoked (views::MouseEvent
// flags). mouse_event_flags is 0 if this is triggered by a user gesture
diff --git a/views/controls/menu/menu_item_view.cc b/views/controls/menu/menu_item_view.cc
index 5e92186..698502b 100644
--- a/views/controls/menu/menu_item_view.cc
+++ b/views/controls/menu/menu_item_view.cc
@@ -233,6 +233,20 @@ MenuItemView* MenuItemView::GetMenuItemByID(int id) {
return NULL;
}
+void MenuItemView::ChildrenChanged() {
+ MenuController* controller = GetMenuController();
+ if (!controller)
+ return; // We're not showing, nothing to do.
+
+ // Handles the case where we were empty and are no longer empty.
+ RemoveEmptyMenus();
+
+ // Handles the case where we were not empty, but now are.
+ AddEmptyMenus();
+
+ controller->MenuChildrenChanged(this);
+}
+
MenuItemView::MenuItemView(MenuItemView* parent,
int command,
MenuItemView::Type type) {
diff --git a/views/controls/menu/menu_item_view.h b/views/controls/menu/menu_item_view.h
index c528671..936ff1e 100644
--- a/views/controls/menu/menu_item_view.h
+++ b/views/controls/menu/menu_item_view.h
@@ -221,6 +221,10 @@ class MenuItemView : public View {
// Returns the descendant with the specified command.
MenuItemView* GetMenuItemByID(int id);
+ // Invoke if you remove/add children to the menu while it's showing. This
+ // recalculates the bounds.
+ void ChildrenChanged();
+
protected:
// Creates a MenuItemView. This is used by the various AddXXX methods.
MenuItemView(MenuItemView* parent, int command, Type type);
@@ -255,8 +259,8 @@ class MenuItemView : public View {
int GetDrawStringFlags();
// If this menu item has no children a child is added showing it has no
- // children. Otherwise AddEmtpyMenuIfNecessary is recursively invoked on
- // child menu items that have children.
+ // children. Otherwise AddEmtpyMenus is recursively invoked on child menu
+ // items that have children.
void AddEmptyMenus();
// Undoes the work of AddEmptyMenus.
diff --git a/views/controls/menu/submenu_view.cc b/views/controls/menu/submenu_view.cc
index b6ca627..0170e0c 100644
--- a/views/controls/menu/submenu_view.cc
+++ b/views/controls/menu/submenu_view.cc
@@ -234,6 +234,10 @@ void SubmenuView::ShowAt(gfx::NativeWindow parent,
host_->Init(parent, bounds, scroll_view_container_, do_capture);
}
+void SubmenuView::Reposition(const gfx::Rect& bounds) {
+ host_->SetBounds(bounds);
+}
+
void SubmenuView::Close() {
if (host_) {
host_->Close();
diff --git a/views/controls/menu/submenu_view.h b/views/controls/menu/submenu_view.h
index 8cc4461..1e82f73 100644
--- a/views/controls/menu/submenu_view.h
+++ b/views/controls/menu/submenu_view.h
@@ -76,6 +76,9 @@ class SubmenuView : public View {
// coordinates. max_width gives the max width the view should be.
void ShowAt(gfx::NativeWindow parent, const gfx::Rect& bounds, bool do_capture);
+ // Resets the bounds of the submenu to |bounds|.
+ void Reposition(const gfx::Rect& bounds);
+
// Closes the menu, destroying the host.
void Close();