diff options
-rw-r--r-- | chrome/browser/bookmark_bar_context_menu_controller.cc | 397 | ||||
-rw-r--r-- | chrome/browser/bookmark_bar_context_menu_controller.h | 86 | ||||
-rw-r--r-- | chrome/browser/bookmark_bar_context_menu_controller_test.cc | 164 | ||||
-rw-r--r-- | chrome/browser/browser.vcproj | 8 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_bar_view.cc | 387 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_bar_view.h | 35 | ||||
-rw-r--r-- | chrome/test/unit/unittests.vcproj | 8 |
7 files changed, 694 insertions, 391 deletions
diff --git a/chrome/browser/bookmark_bar_context_menu_controller.cc b/chrome/browser/bookmark_bar_context_menu_controller.cc new file mode 100644 index 0000000..75bd4fb --- /dev/null +++ b/chrome/browser/bookmark_bar_context_menu_controller.cc @@ -0,0 +1,397 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/bookmark_bar_context_menu_controller.h" + +#include "chrome/browser/bookmark_bar_model.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/page_navigator.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/views/bookmark_editor_view.h" +#include "chrome/browser/views/input_window.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" + +#include "generated_resources.h" + +namespace { + +// Returns true if the specified node is of type URL, or has a descendant +// of type URL. +bool NodeHasURLs(BookmarkBarNode* node) { + if (node->GetType() == history::StarredEntry::URL) + return true; + + for (int i = 0; i < node->GetChildCount(); ++i) { + if (NodeHasURLs(node->GetChild(i))) + return true; + } + return false; +} + +// Opens a tab/window for node and recursively opens all descendants. +// If open_first_in_new_window is true, the first opened node is opened +// in a new window. navigator indicates the PageNavigator to use for +// new tabs. It is reset if open_first_in_new_window is true. +// opened_url is set to true the first time a new tab is opened. +void OpenAll(BookmarkBarNode* node, + bool open_first_in_new_window, + PageNavigator** navigator, + bool* opened_url) { + if (node->GetType() == history::StarredEntry::URL) { + WindowOpenDisposition disposition; + if (*opened_url) + disposition = NEW_BACKGROUND_TAB; + else if (open_first_in_new_window) + disposition = NEW_WINDOW; + else // Open in current window. + disposition = CURRENT_TAB; + (*navigator)->OpenURL(node->GetURL(), disposition, + PageTransition::AUTO_BOOKMARK); + if (!*opened_url) { + *opened_url = true; + if (open_first_in_new_window || disposition == CURRENT_TAB) { + // We opened the tab in a new window or in the current tab which + // likely reset the navigator. Need to reset the page navigator + // appropriately. + Browser* new_browser = BrowserList::GetLastActive(); + if (new_browser) { + TabContents* current_tab = new_browser->GetSelectedTabContents(); + DCHECK(new_browser && current_tab); + if (new_browser && current_tab) + *navigator = current_tab; + } // else, new_browser == NULL, which happens during testing. + } + } + } else { + // Group, recurse through children. + for (int i = 0; i < node->GetChildCount(); ++i) { + OpenAll(node->GetChild(i), open_first_in_new_window, navigator, + opened_url); + } + } +} + +// EditFolderController ------------------------------------------------------- + +// EditFolderController manages the editing and/or creation of a folder. If the +// user presses ok, the name change is committed to the database. +// +// EditFolderController deletes itself when the window is closed. +// +class EditFolderController : public InputWindowDelegate, + public BookmarkBarView::ModelChangedListener { + public: + EditFolderController(BookmarkBarView* view, + BookmarkBarNode* node, + int visual_order, + bool is_new) + : view_(view), + node_(node), + visual_order_(visual_order), + is_new_(is_new) { + DCHECK(is_new_ || node); + window_ = CreateInputWindow(view->GetViewContainer()->GetHWND(), this); + view_->SetModelChangedListener(this); + } + + void Show() { + window_->Show(); + } + + virtual void ModelChanged() { + window_->Close(); + } + + private: + virtual std::wstring GetTextFieldLabel() { + return l10n_util::GetString(IDS_BOOMARK_BAR_EDIT_FOLDER_LABEL); + } + + virtual std::wstring GetTextFieldContents() { + if (is_new_) + return l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME); + return node_->GetTitle(); + } + + virtual bool IsValid(const std::wstring& text) { + return !text.empty(); + } + + virtual void InputAccepted(const std::wstring& text) { + view_->ClearModelChangedListenerIfEquals(this); + BookmarkBarModel* model = view_->GetProfile()->GetBookmarkBarModel(); + if (is_new_) + model->AddGroup(node_, visual_order_, text); + else + model->SetTitle(node_, text); + } + + virtual void InputCanceled() { + view_->ClearModelChangedListenerIfEquals(this); + } + + virtual void WindowClosing() { + view_->ClearModelChangedListenerIfEquals(this); + delete this; + } + + virtual std::wstring GetWindowTitle() const { + return is_new_ ? + l10n_util::GetString(IDS_BOOMARK_FOLDER_EDITOR_WINDOW_TITLE_NEW) : + l10n_util::GetString(IDS_BOOMARK_FOLDER_EDITOR_WINDOW_TITLE); + } + + virtual ChromeViews::View* GetContentsView() { + return view_; + } + + BookmarkBarView* view_; + + // If is_new is true, this is the parent to create the new node under. + // Otherwise this is the node to change the title of. + BookmarkBarNode* node_; + + int visual_order_; + bool is_new_; + ChromeViews::Window* window_; + + DISALLOW_EVIL_CONSTRUCTORS(EditFolderController); +}; + +} // namespace + +// BookmarkBarContextMenuController ------------------------------------------- + +const int BookmarkBarContextMenuController::always_show_command_id = 1; +const int BookmarkBarContextMenuController::open_bookmark_id = 2; +const int BookmarkBarContextMenuController::open_bookmark_in_new_window_id = 3; +const int BookmarkBarContextMenuController::open_bookmark_in_new_tab_id = 4; +const int BookmarkBarContextMenuController::open_all_bookmarks_id = 5; +const int + BookmarkBarContextMenuController::open_all_bookmarks_in_new_window_id = 6; +const int BookmarkBarContextMenuController::edit_bookmark_id = 7; +const int BookmarkBarContextMenuController::delete_bookmark_id = 8; +const int BookmarkBarContextMenuController::add_bookmark_id = 9; +const int BookmarkBarContextMenuController::new_folder_id = 10; + +BookmarkBarContextMenuController::BookmarkBarContextMenuController( + BookmarkBarView* view, + BookmarkBarNode* node) + : view_(view), + node_(node), + menu_(this) { + if (node->GetType() == history::StarredEntry::URL) { + menu_.AppendMenuItemWithLabel( + open_bookmark_id, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN)); + menu_.AppendMenuItemWithLabel( + open_bookmark_in_new_tab_id, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB)); + menu_.AppendMenuItemWithLabel( + open_bookmark_in_new_window_id, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW)); + } else { + menu_.AppendMenuItemWithLabel( + open_all_bookmarks_id, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL)); + menu_.AppendMenuItemWithLabel( + open_all_bookmarks_in_new_window_id, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + } + menu_.AppendSeparator(); + + if (node->GetParent() != + view->GetProfile()->GetBookmarkBarModel()->root_node()) { + menu_.AppendMenuItemWithLabel(edit_bookmark_id, + l10n_util::GetString(IDS_BOOKMARK_BAR_EDIT)); + menu_.AppendMenuItemWithLabel( + delete_bookmark_id, + l10n_util::GetString(IDS_BOOKMARK_BAR_REMOVE)); + } + + menu_.AppendMenuItemWithLabel( + add_bookmark_id, + l10n_util::GetString(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + menu_.AppendMenuItemWithLabel( + new_folder_id, + l10n_util::GetString(IDS_BOOMARK_BAR_NEW_FOLDER)); + menu_.AppendSeparator(); + menu_.AppendMenuItem(always_show_command_id, + l10n_util::GetString(IDS_BOOMARK_BAR_ALWAYS_SHOW), + ChromeViews::MenuItemView::CHECKBOX); +} + +void BookmarkBarContextMenuController::RunMenuAt(int x, int y) { + // Record the current ModelChangedListener. It will be non-null when we're + // used as the context menu for another menu. + ModelChangedListener* last_listener = view_->GetModelChangedListener(); + + view_->SetModelChangedListener(this); + + // width/height don't matter here. + menu_.RunMenuAt(view_->GetViewContainer()->GetHWND(), gfx::Rect(x, y, 0, 0), + ChromeViews::MenuItemView::TOPLEFT, false); + + if (view_->GetModelChangedListener() == this) + view_->SetModelChangedListener(last_listener); +} + +void BookmarkBarContextMenuController::ModelChanged() { + menu_.Cancel(); +} + +void BookmarkBarContextMenuController::ExecuteCommand(int id) { + Profile* profile = view_->GetProfile(); + + switch (id) { + case open_bookmark_id: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Open", profile); + + view_->GetPageNavigator()->OpenURL(node_->GetURL(), CURRENT_TAB, + PageTransition::AUTO_BOOKMARK); + break; + + case open_bookmark_in_new_window_id: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewWindow", + profile); + + view_->GetPageNavigator()->OpenURL(node_->GetURL(), NEW_WINDOW, + PageTransition::AUTO_BOOKMARK); + break; + + case open_bookmark_in_new_tab_id: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewTab", + profile); + + view_->GetPageNavigator()->OpenURL(node_->GetURL(), NEW_FOREGROUND_TAB, + PageTransition::AUTO_BOOKMARK); + break; + + case open_all_bookmarks_id: + case open_all_bookmarks_in_new_window_id: { + if (id == open_all_bookmarks_id) { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenAll", + profile); + } else { + UserMetrics::RecordAction( + L"BookmarkBar_ContextMenu_OpenAllInNewWindow", profile); + } + + BookmarkBarNode* node = node_; + PageNavigator* navigator = view_->GetPageNavigator(); + bool opened_url = false; + OpenAll(node, (id == open_all_bookmarks_in_new_window_id), &navigator, + &opened_url); + break; + } + + case edit_bookmark_id: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Edit", profile); + + if (node_->GetType() == history::StarredEntry::URL) { + BookmarkEditorView::Show(view_->GetViewContainer()->GetHWND(), + view_->GetProfile(), node_->GetURL(), + node_->GetTitle()); + } else { + // Controller deletes itself when done. + EditFolderController* controller = new EditFolderController( + view_, node_, -1, false); + controller->Show(); + } + break; + + case delete_bookmark_id: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Remove", profile); + + view_->GetModel()->Remove(node_->GetParent(), + node_->GetParent()->IndexOfChild(node_)); + break; + } + + case add_bookmark_id: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Add", profile); + + BookmarkEditorView::Show(view_->GetViewContainer()->GetHWND(), + view_->GetProfile(), GURL(), std::wstring()); + break; + } + + case new_folder_id: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_NewFolder", + profile); + + int visual_order; + BookmarkBarNode* parent = + GetParentAndVisualOrderForNewNode(&visual_order); + GetParentAndVisualOrderForNewNode(&visual_order); + // Controller deletes itself when done. + EditFolderController* controller = + new EditFolderController(view_, parent, visual_order, true); + controller->Show(); + break; + } + + case always_show_command_id: + view_->ToggleWhenVisible(); + break; + + default: + NOTREACHED(); + } +} + +bool BookmarkBarContextMenuController::IsItemChecked(int id) const { + DCHECK(id == always_show_command_id); + return view_->GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); +} + +bool BookmarkBarContextMenuController::IsCommandEnabled(int id) const { + if (id == open_all_bookmarks_id || id == open_all_bookmarks_in_new_window_id) + return NodeHasURLs(node_); + + return true; +} + +// Returns the parent node and visual_order to use when adding new +// bookmarks/folders. +BookmarkBarNode* BookmarkBarContextMenuController:: + GetParentAndVisualOrderForNewNode(int* visual_order) { + if (node_->GetType() != history::StarredEntry::URL) { + // Adding to a group always adds to the end. + *visual_order = node_->GetChildCount(); + return node_; + } else { + DCHECK(node_->GetParent()); + *visual_order = node_->GetParent()->IndexOfChild(node_) + 1; + return node_->GetParent(); + } +} diff --git a/chrome/browser/bookmark_bar_context_menu_controller.h b/chrome/browser/bookmark_bar_context_menu_controller.h new file mode 100644 index 0000000..28bf329 --- /dev/null +++ b/chrome/browser/bookmark_bar_context_menu_controller.h @@ -0,0 +1,86 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_BOOKMARK_BAR_CONTEXT_MENU_CONTROLLER_H_ +#define CHROME_BROWSER_BOOKMARK_BAR_CONTEXT_MENU_CONTROLLER_H_ + +#include "chrome/views/chrome_menu.h" +#include "chrome/browser/views/bookmark_bar_view.h" + +class BookmarkBarNode; +class PageNavigator; + +// BookmarkBarContextMenuController manages the context menus shown for the +// bookmark bar, items on the bookmark bar, and submens of folders on the +// bookmark bar. +class BookmarkBarContextMenuController : public ChromeViews::MenuDelegate, + public BookmarkBarView::ModelChangedListener { + public: + BookmarkBarContextMenuController(BookmarkBarView* view, + BookmarkBarNode* node); + + // Shows the menu at the specified place. + void RunMenuAt(int x, int y); + + // ModelChangedListener method, cancels the menu. + virtual void ModelChanged(); + + // Returns the menu. + ChromeViews::MenuItemView* menu() { return &menu_; } + + // Menu::Delegate methods. + virtual void ExecuteCommand(int id); + virtual bool IsItemChecked(int id) const; + virtual bool IsCommandEnabled(int id) const; + + // IDs used for the menus. Public for testing. + static const int always_show_command_id; + static const int open_bookmark_id; + static const int open_bookmark_in_new_window_id; + static const int open_bookmark_in_new_tab_id; + static const int open_all_bookmarks_id; + static const int open_all_bookmarks_in_new_window_id; + static const int edit_bookmark_id; + static const int delete_bookmark_id; + static const int add_bookmark_id; + static const int new_folder_id; + + private: + // Returns the parent node and visual_order to use when adding new + // bookmarks/folders. + BookmarkBarNode* GetParentAndVisualOrderForNewNode(int* visual_order); + + ChromeViews::MenuItemView menu_; + BookmarkBarView* view_; + BookmarkBarNode* node_; + + DISALLOW_EVIL_CONSTRUCTORS(BookmarkBarContextMenuController); +}; + +#endif // CHROME_BROWSER_BOOKMARK_BAR_CONTEXT_MENU_CONTROLLER_H_ diff --git a/chrome/browser/bookmark_bar_context_menu_controller_test.cc b/chrome/browser/bookmark_bar_context_menu_controller_test.cc new file mode 100644 index 0000000..a90ea9d --- /dev/null +++ b/chrome/browser/bookmark_bar_context_menu_controller_test.cc @@ -0,0 +1,164 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/bookmark_bar_context_menu_controller.h" +#include "chrome/browser/bookmark_bar_model.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/views/bookmark_bar_view.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/browser/page_navigator.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// PageNavigator implementation that records the URL. +class TestingPageNavigator : public PageNavigator { + public: + virtual void OpenURL(const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + urls_.push_back(url); + } + + std::vector<GURL> urls_; +}; + +} // namespace + +class BookmarkBarContextMenuControllerTest : public testing::Test { + public: + BookmarkBarContextMenuControllerTest() : bb_view_(NULL), model_(NULL) { + } + + virtual void SetUp() { + BookmarkBarView::testing_ = true; + + profile_.reset(new TestingProfile()); + profile_->set_has_history_service(true); + profile_->CreateBookmarkBarModel(); + + model_ = profile_->GetBookmarkBarModel(); + + bb_view_ = new BookmarkBarView(profile_.get(), NULL); + bb_view_->SetPageNavigator(&navigator_); + + AddTestData(); + } + + virtual void TearDown() { + BookmarkBarView::testing_ = false; + } + + protected: + BookmarkBarModel* model_; + BookmarkBarView* bb_view_; + TestingPageNavigator navigator_; + + private: + // Creates the following structure: + // a + // F1 + // f1a + // F11 + // f11a + // F2 + void AddTestData() { + std::string test_base = "file:///c:/tmp/"; + + model_->AddURL(model_->GetBookmarkBarNode(), 0, L"a", + GURL(test_base + "a")); + BookmarkBarNode* f1 = + model_->AddGroup(model_->GetBookmarkBarNode(), 1, L"F1"); + model_->AddURL(f1, 0, L"f1a", GURL(test_base + "f1a")); + BookmarkBarNode* f11 = model_->AddGroup(f1, 1, L"F11"); + model_->AddURL(f11, 0, L"f11a", GURL(test_base + "f11a")); + model_->AddGroup(model_->GetBookmarkBarNode(), 2, L"F2"); + } + + scoped_ptr<TestingProfile> profile_; +}; + +// Tests Deleting from the menu. +TEST_F(BookmarkBarContextMenuControllerTest, DeleteURL) { + BookmarkBarContextMenuController controller( + bb_view_, model_->GetBookmarkBarNode()->GetChild(0)); + GURL url = model_->GetBookmarkBarNode()->GetChild(0)->GetURL(); + ASSERT_TRUE(controller.IsCommandEnabled( + BookmarkBarContextMenuController::delete_bookmark_id)); + // Delete the URL. + controller.ExecuteCommand( + BookmarkBarContextMenuController::delete_bookmark_id); + // Model shouldn't have URL anymore. + ASSERT_TRUE(model_->GetNodeByURL(url) == NULL); +} + +// Tests openning from the menu. +TEST_F(BookmarkBarContextMenuControllerTest, OpenURL) { + BookmarkBarContextMenuController controller( + bb_view_, model_->GetBookmarkBarNode()->GetChild(0)); + GURL url = model_->GetBookmarkBarNode()->GetChild(0)->GetURL(); + ASSERT_TRUE(controller.IsCommandEnabled( + BookmarkBarContextMenuController::open_bookmark_id)); + // Open it. + controller.ExecuteCommand( + BookmarkBarContextMenuController::open_bookmark_id); + // Should have navigated to it. + ASSERT_EQ(1, navigator_.urls_.size()); + ASSERT_TRUE(url == navigator_.urls_[0]); +} + +// Tests open all on a folder with a couple of bookmarks. +TEST_F(BookmarkBarContextMenuControllerTest, OpenAll) { + BookmarkBarNode* folder = model_->GetBookmarkBarNode()->GetChild(1); + BookmarkBarContextMenuController controller(bb_view_, folder); + ASSERT_TRUE(controller.IsCommandEnabled( + BookmarkBarContextMenuController::open_all_bookmarks_id)); + ASSERT_TRUE(controller.IsCommandEnabled( + BookmarkBarContextMenuController::open_all_bookmarks_in_new_window_id)); + // Open it. + controller.ExecuteCommand( + BookmarkBarContextMenuController::open_all_bookmarks_id); + // Should have navigated to F1's children. + ASSERT_EQ(2, navigator_.urls_.size()); + ASSERT_TRUE(folder->GetChild(0)->GetURL() == navigator_.urls_[0]); + ASSERT_TRUE(folder->GetChild(1)->GetChild(0)->GetURL() == + navigator_.urls_[1]); +} + +// Tests that menus are appropriately disabled for empty folders. +TEST_F(BookmarkBarContextMenuControllerTest, DisableForEmptyFolder) { + BookmarkBarNode* folder = model_->GetBookmarkBarNode()->GetChild(2); + BookmarkBarContextMenuController controller(bb_view_, folder); + EXPECT_FALSE(controller.IsCommandEnabled( + BookmarkBarContextMenuController::open_all_bookmarks_id)); + EXPECT_FALSE(controller.IsCommandEnabled( + BookmarkBarContextMenuController::open_all_bookmarks_in_new_window_id)); +} diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj index 1669c46..98286a1 100644 --- a/chrome/browser/browser.vcproj +++ b/chrome/browser/browser.vcproj @@ -802,6 +802,14 @@ Name="Bookmark Bar" > <File + RelativePath=".\bookmark_bar_context_menu_controller.cc" + > + </File> + <File + RelativePath=".\bookmark_bar_context_menu_controller.h" + > + </File> + <File RelativePath=".\bookmark_bar_model.cc" > </File> diff --git a/chrome/browser/views/bookmark_bar_view.cc b/chrome/browser/views/bookmark_bar_view.cc index 6579a0f..d249d38 100644 --- a/chrome/browser/views/bookmark_bar_view.cc +++ b/chrome/browser/views/bookmark_bar_view.cc @@ -34,6 +34,7 @@ #include "base/base_drag_source.h" #include "base/gfx/skia_utils.h" #include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/bookmark_bar_context_menu_controller.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" @@ -326,384 +327,6 @@ struct DropInfo { BookmarkDragData data; }; -// ModelChangedListener ------------------------------------------------------- - -// Interface implemented by controllers/views that need to be notified any -// time the model changes, typically to cancel an operation that is showing -// data from the model such as a menu. This isn't intended as a general -// way to be notified of changes, rather for cases where a controller/view is -// showing data from the model in a modal like setting and needs to cleanly -// exit the modal loop if the model changes out from under it. -// -// A controller/view that needs this notification should install itself as the -// ModelChangeListener via the SetModelChangedListener method when shown and -// reset the ModelChangeListener of the BookmarkBarView when it closes by way -// of either the SetModelChangedListener method or the -// ClearModelChangedListenerIfEquals method. -// -class ModelChangedListener { - public: - virtual ~ModelChangedListener() {} - // Invoked when the model changes. Should cancel the edit and close any - // dialogs. - virtual void ModelChanged() = 0; -}; - -// EditFolderController ------------------------------------------------------- - -// EditFolderController manages the editing and/or creation of a folder. If the -// user presses ok, the name change is committed to the database. -// -// EditFolderController deletes itself when the window is closed. -// -class EditFolderController : public InputWindowDelegate, - public ModelChangedListener { - public: - EditFolderController(BookmarkBarView* view, - BookmarkBarNode* node, - int visual_order, - bool is_new) - : view_(view), - node_(node), - visual_order_(visual_order), - is_new_(is_new) { - DCHECK(is_new_ || node); - window_ = CreateInputWindow(view->GetViewContainer()->GetHWND(), this); - view_->SetModelChangedListener(this); - } - - void Show() { - window_->Show(); - } - - virtual void ModelChanged() { - window_->Close(); - } - - private: - virtual std::wstring GetTextFieldLabel() { - return l10n_util::GetString(IDS_BOOMARK_BAR_EDIT_FOLDER_LABEL); - } - - virtual std::wstring GetTextFieldContents() { - if (is_new_) - return l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME); - return node_->GetTitle(); - } - - virtual bool IsValid(const std::wstring& text) { - return !text.empty(); - } - - virtual void InputAccepted(const std::wstring& text) { - view_->ClearModelChangedListenerIfEquals(this); - BookmarkBarModel* model = view_->GetProfile()->GetBookmarkBarModel(); - if (is_new_) - model->AddGroup(node_, visual_order_, text); - else - model->SetTitle(node_, text); - } - - virtual void InputCanceled() { - view_->ClearModelChangedListenerIfEquals(this); - } - - virtual void WindowClosing() { - view_->ClearModelChangedListenerIfEquals(this); - delete this; - } - - virtual std::wstring GetWindowTitle() const { - return is_new_ ? - l10n_util::GetString(IDS_BOOMARK_FOLDER_EDITOR_WINDOW_TITLE_NEW) : - l10n_util::GetString(IDS_BOOMARK_FOLDER_EDITOR_WINDOW_TITLE); - } - - virtual ChromeViews::View* GetContentsView() { - return view_; - } - - BookmarkBarView* view_; - - // If is_new is true, this is the parent to create the new node under. - // Otherwise this is the node to change the title of. - BookmarkBarNode* node_; - - int visual_order_; - bool is_new_; - ChromeViews::Window* window_; - - DISALLOW_EVIL_CONSTRUCTORS(EditFolderController); -}; - -// BookmarkNodeMenuController ------------------------------------------------- - -// IDs for the menus we create. - -static const int kOpenBookmarkID = 2; -static const int kOpenBookmarkInNewWindowID = 3; -static const int kOpenBookmarkInNewTabID = 4; -static const int kOpenAllBookmarksID = 5; -static const int kOpenAllBookmarksInNewWindowID = 6; -static const int kEditBookmarkID = 7; -static const int kDeleteBookmarkID = 8; -static const int kAddBookmarkID = 9; -static const int kNewFolderID = 10; - -// BookmarkNodeMenuController manages the context menus shown for the bookmark -// bar and buttons on the bookmark bar. - -class BookmarkNodeMenuController : public ChromeViews::MenuDelegate, - public ModelChangedListener { - public: - BookmarkNodeMenuController(BookmarkBarView* view, - BookmarkBarNode* node) - : view_(view), - node_(node), - menu_(this) { - if (node->GetType() == history::StarredEntry::URL) { - menu_.AppendMenuItemWithLabel(kOpenBookmarkID, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN)); - menu_.AppendMenuItemWithLabel(kOpenBookmarkInNewTabID, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB)); - menu_.AppendMenuItemWithLabel(kOpenBookmarkInNewWindowID, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW)); - } else { - menu_.AppendMenuItemWithLabel(kOpenAllBookmarksID, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL)); - menu_.AppendMenuItemWithLabel(kOpenAllBookmarksInNewWindowID, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); - } - menu_.AppendSeparator(); - - if (node->GetParent() != - view->GetProfile()->GetBookmarkBarModel()->root_node()) { - menu_.AppendMenuItemWithLabel(kEditBookmarkID, - l10n_util::GetString(IDS_BOOKMARK_BAR_EDIT)); - menu_.AppendMenuItemWithLabel(kDeleteBookmarkID, - l10n_util::GetString(IDS_BOOKMARK_BAR_REMOVE)); - } - - menu_.AppendMenuItemWithLabel(kAddBookmarkID, - l10n_util::GetString(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); - menu_.AppendMenuItemWithLabel(kNewFolderID, - l10n_util::GetString(IDS_BOOMARK_BAR_NEW_FOLDER)); - menu_.AppendSeparator(); - menu_.AppendMenuItem(kAlwaysShowCommandID, - l10n_util::GetString(IDS_BOOMARK_BAR_ALWAYS_SHOW), - MenuItemView::CHECKBOX); - } - - void RunMenuAt(int x, int y) { - // Record the current ModelChangedListener. It will be non-null when we're - // used as the context menu for another menu. - ModelChangedListener* last_listener = view_->GetModelChangedListener(); - - view_->SetModelChangedListener(this); - - // width/height don't matter here. - menu_.RunMenuAt(view_->GetViewContainer()->GetHWND(), gfx::Rect(x, y, 0, 0), - MenuItemView::TOPLEFT, false); - - if (view_->GetModelChangedListener() == this) - view_->SetModelChangedListener(last_listener); - } - - virtual void ModelChanged() { - menu_.Cancel(); - } - - MenuItemView* menu() { return &menu_; } - - private: - // Menu::Delegate method. Does the appropriate operation based on chosen - // menu item. - virtual void ExecuteCommand(int id) { - Profile* profile = view_->GetProfile(); - - switch (id) { - case kOpenBookmarkID: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Open", profile); - - view_->GetPageNavigator()->OpenURL(node_->GetURL(), CURRENT_TAB, - PageTransition::AUTO_BOOKMARK); - break; - - case kOpenBookmarkInNewWindowID: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewWindow", - profile); - - view_->GetPageNavigator()->OpenURL(node_->GetURL(), NEW_WINDOW, - PageTransition::AUTO_BOOKMARK); - break; - - case kOpenBookmarkInNewTabID: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewTab", - profile); - - view_->GetPageNavigator()->OpenURL(node_->GetURL(), NEW_FOREGROUND_TAB, - PageTransition::AUTO_BOOKMARK); - break; - - case kOpenAllBookmarksID: - case kOpenAllBookmarksInNewWindowID: { - if (id == kOpenAllBookmarksID) { - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenAll", - profile); - } else { - UserMetrics::RecordAction( - L"BookmarkBar_ContextMenu_OpenAllInNewWindow", profile); - } - - BookmarkBarNode* node = node_; - PageNavigator* navigator = view_->GetPageNavigator(); - bool opened_url = false; - OpenAll(node, (id == kOpenAllBookmarksInNewWindowID), &navigator, - &opened_url); - break; - } - - case kEditBookmarkID: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Edit", profile); - - if (node_->GetType() == history::StarredEntry::URL) { - BookmarkEditorView::Show(view_->GetViewContainer()->GetHWND(), - view_->GetProfile(), node_->GetURL(), node_->GetTitle()); - } else { - // Controller deletes itself when done. - EditFolderController* controller = new EditFolderController( - view_, node_, -1, false); - controller->Show(); - } - break; - - case kDeleteBookmarkID: { - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Remove", profile); - - view_->model_->Remove(node_->GetParent(), - node_->GetParent()->IndexOfChild(node_)); - break; - } - - case kAddBookmarkID: { - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Add", profile); - - BookmarkEditorView::Show(view_->GetViewContainer()->GetHWND(), - view_->GetProfile(), GURL(), std::wstring()); - break; - } - - case kNewFolderID: { - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_NewFolder", - profile); - - int visual_order; - BookmarkBarNode* parent = - GetParentAndVisualOrderForNewNode(&visual_order); - GetParentAndVisualOrderForNewNode(&visual_order); - // Controller deletes itself when done. - EditFolderController* controller = - new EditFolderController(view_, parent, visual_order, true); - controller->Show(); - break; - } - - case kAlwaysShowCommandID: - view_->ToggleWhenVisible(); - break; - - default: - NOTREACHED(); - } - } - - bool IsItemChecked(int id) const { - DCHECK(id == kAlwaysShowCommandID); - return view_->GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); - } - - // Opens a tab/window for node and recursively opens all descendants. - // If open_first_in_new_window is true, the first opened node is opened - // in a new window. navigator indicates the PageNavigator to use for - // new tabs. It is reset if open_first_in_new_window is true. - // opened_url is set to true the first time a new tab is opened. - void OpenAll(BookmarkBarNode* node, - bool open_first_in_new_window, - PageNavigator** navigator, - bool* opened_url) { - if (node->GetType() == history::StarredEntry::URL) { - WindowOpenDisposition disposition; - if (*opened_url) - disposition = NEW_BACKGROUND_TAB; - else if (open_first_in_new_window) - disposition = NEW_WINDOW; - else // Open in current window. - disposition = CURRENT_TAB; - (*navigator)->OpenURL(node->GetURL(), disposition, - PageTransition::AUTO_BOOKMARK); - if (!*opened_url) { - *opened_url = true; - if (open_first_in_new_window || disposition == CURRENT_TAB) { - // We opened the tab in a new window or in the current tab which - // likely reset the navigator. Need to reset the page navigator - // appropriately. - Browser* new_browser = BrowserList::GetLastActive(); - TabContents* current_tab = new_browser->GetSelectedTabContents(); - DCHECK(new_browser && current_tab); - if (new_browser && current_tab) - *navigator = current_tab; - } - } - } else { - // Group, recurse through children. - for (int i = 0; i < node->GetChildCount(); ++i) { - OpenAll(node->GetChild(i), open_first_in_new_window, navigator, - opened_url); - } - } - } - - virtual bool IsCommandEnabled(int id) const { - if (id == kOpenAllBookmarksID || id == kOpenAllBookmarksInNewWindowID) - return NodeHasURLs(node_); - - return true; - } - - // Returns true if the specified node is of type URL, or has a descendant - // of type URL. - static bool NodeHasURLs(BookmarkBarNode* node) { - if (node->GetType() == history::StarredEntry::URL) - return true; - - for (int i = 0; i < node->GetChildCount(); ++i) { - if (NodeHasURLs(node->GetChild(i))) - return true; - } - return false; - } - - // Returns the parent node and visual_order to use when adding new - // bookmarks/folders. - BookmarkBarNode* GetParentAndVisualOrderForNewNode(int* visual_order) { - if (node_->GetType() != history::StarredEntry::URL) { - // Adding to a group always adds to the end. - *visual_order = node_->GetChildCount(); - return node_; - } else { - DCHECK(node_->GetParent()); - *visual_order = node_->GetParent()->IndexOfChild(node_) + 1; - return node_->GetParent(); - } - } - - MenuItemView menu_; - BookmarkBarView* view_; - BookmarkBarNode* node_; - - DISALLOW_EVIL_CONSTRUCTORS(BookmarkNodeMenuController); -}; - // MenuRunner ----------------------------------------------------------------- // MenuRunner manages creation and showing of a menu containing BookmarkNodes. @@ -711,7 +334,7 @@ class BookmarkNodeMenuController : public ChromeViews::MenuDelegate, // bookmark bar, other folder, or overflow bookmarks. // class MenuRunner : public ChromeViews::MenuDelegate, - public ModelChangedListener { + public BookmarkBarView::ModelChangedListener { public: // start_child_index is the index of the first child in node to add to the // menu. @@ -894,7 +517,7 @@ class MenuRunner : public ChromeViews::MenuDelegate, bool is_mouse_gesture) { DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); context_menu_.reset( - new BookmarkNodeMenuController(view_, menu_id_to_node_map_[id])); + new BookmarkBarContextMenuController(view_, menu_id_to_node_map_[id])); context_menu_->RunMenuAt(x, y); context_menu_.reset(NULL); } @@ -942,7 +565,7 @@ class MenuRunner : public ChromeViews::MenuDelegate, // Data for the drop. BookmarkDragData drop_data_; - scoped_ptr<BookmarkNodeMenuController> context_menu_; + scoped_ptr<BookmarkBarContextMenuController> context_menu_; DISALLOW_EVIL_CONSTRUCTORS(MenuRunner); }; @@ -1788,7 +1411,7 @@ void BookmarkBarView::ShowContextMenu(View* source, bookmark_button_index < GetBookmarkButtonCount()); node = model_->GetBookmarkBarNode()->GetChild(bookmark_button_index); } - BookmarkNodeMenuController controller(this, node); + BookmarkBarContextMenuController controller(this, node); controller.RunMenuAt(x, y); } diff --git a/chrome/browser/views/bookmark_bar_view.h b/chrome/browser/views/bookmark_bar_view.h index 287f8d7..590ab9d 100644 --- a/chrome/browser/views/bookmark_bar_view.h +++ b/chrome/browser/views/bookmark_bar_view.h @@ -27,8 +27,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H__ -#define CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H__ +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H_ +#define CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H_ #include "chrome/browser/bookmark_bar_model.h" #include "chrome/browser/bookmark_drag_data.h" @@ -39,14 +39,11 @@ #include "chrome/views/view.h" #include "chrome/views/view_menu_delegate.h" +class Browser; class PageNavigator; class PrefService; -class Browser; namespace { -class BookmarkNodeMenuController; -// See description in declaration at bookmark_bar_view.cc. -class ModelChangedListener; class MenuRunner; class ButtonSeparatorView; struct DropInfo; @@ -73,11 +70,31 @@ class BookmarkBarView : public ChromeViews::View, public ChromeViews::ContextMenuController, public ChromeViews::DragController, public AnimationDelegate { - friend class BookmarkNodeMenuController; friend class MenuRunner; friend class ShowFolderMenuTask; public: + // Interface implemented by controllers/views that need to be notified any + // time the model changes, typically to cancel an operation that is showing + // data from the model such as a menu. This isn't intended as a general + // way to be notified of changes, rather for cases where a controller/view is + // showing data from the model in a modal like setting and needs to cleanly + // exit the modal loop if the model changes out from under it. + // + // A controller/view that needs this notification should install itself as the + // ModelChangeListener via the SetModelChangedListener method when shown and + // reset the ModelChangeListener of the BookmarkBarView when it closes by way + // of either the SetModelChangedListener method or the + // ClearModelChangedListenerIfEquals method. + class ModelChangedListener { + public: + virtual ~ModelChangedListener() {} + + // Invoked when the model changes. Should cancel the edit and close any + // dialogs. + virtual void ModelChanged() = 0; + }; + explicit BookmarkBarView(Profile* profile, Browser* browser); virtual ~BookmarkBarView(); @@ -443,7 +460,7 @@ class BookmarkBarView : public ChromeViews::View, // overflow_button_ or a button on the bar. ChromeViews::BaseButton* throbbing_view_; - DISALLOW_EVIL_CONSTRUCTORS(BookmarkBarView); + DISALLOW_COPY_AND_ASSIGN(BookmarkBarView); }; -#endif // CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H__ +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H_ diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index b605e26..245996e 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -211,6 +211,14 @@ </File> </Filter> <Filter + Name="TestBookmarkBarContextMenuController" + > + <File + RelativePath="..\..\browser\bookmark_bar_context_menu_controller_test.cc" + > + </File> + </Filter> + <Filter Name="TestBookmarkBarView" > <File |