diff options
author | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-29 23:38:06 +0000 |
---|---|---|
committer | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-29 23:38:06 +0000 |
commit | 7f856bee73ffdccdbbbbbab4cb79185290d38359 (patch) | |
tree | a837328e08a113abdc70b2fc0ae2f2a4a3f804eb /chrome/browser | |
parent | 281fe14063dd8fb81cea102f5abb7b82f407c3d1 (diff) | |
download | chromium_src-7f856bee73ffdccdbbbbbab4cb79185290d38359.zip chromium_src-7f856bee73ffdccdbbbbbab4cb79185290d38359.tar.gz chromium_src-7f856bee73ffdccdbbbbbab4cb79185290d38359.tar.bz2 |
First cut at the bookmark manager. There are still a fair number of
rough edges, but I'm at a good point where I want to land what I
have. Here's what is left:
. Flicker on show, likely the result of restoring window placement.
. tree flickers when dragging splitter.
. table/tree need to autoscroll when drop cursor held at bottom of view.
. prompts for deleting.
. When you move an item the table snaps to the top, this is because
I'm sending out model changed. need a better notification.
. Operations in menu to add need to change selection.
. Remember split location.
I would have preferred to split this up into a couple of reviews, but
everything is intertwined now. Sorry.
BUG=674
TEST=don't test the bookmark manager yet, but make sure bookmark bar
still works.
Review URL: http://codereview.chromium.org/8197
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4191 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
27 files changed, 2882 insertions, 849 deletions
diff --git a/chrome/browser/bookmark_bar_context_menu_controller.cc b/chrome/browser/bookmark_bar_context_menu_controller.cc deleted file mode 100644 index 975a4ec..0000000 --- a/chrome/browser/bookmark_bar_context_menu_controller.cc +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/bookmark_bar_context_menu_controller.h" - -#include "chrome/browser/bookmarks/bookmark_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/tab_contents.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/l10n_util.h" -#include "chrome/common/pref_names.h" -#include "chrome/common/pref_service.h" -#include "chrome/views/container.h" -#include "chrome/views/window.h" - -#include "chromium_strings.h" -#include "generated_resources.h" - -namespace { - -// Number of bookmarks we'll open before prompting the user to see if they -// really want to open all. -const int kNumURLsBeforePrompting = 15; - -// Returns true if the specified node is of type URL, or has a descendant -// of type URL. -bool NodeHasURLs(BookmarkNode* 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; -} - -// Implementation of OpenAll. Opens all nodes of type URL and recurses for -// groups. -void OpenAllImpl(BookmarkNode* node, - WindowOpenDisposition initial_disposition, - PageNavigator** navigator, - bool* opened_url) { - if (node->GetType() == history::StarredEntry::URL) { - WindowOpenDisposition disposition; - if (*opened_url) - disposition = NEW_BACKGROUND_TAB; - else - disposition = initial_disposition; - (*navigator)->OpenURL(node->GetURL(), GURL(), disposition, - PageTransition::AUTO_BOOKMARK); - if (!*opened_url) { - *opened_url = true; - // We opened the first URL which may have opened a new window or clobbered - // the current page, reset the navigator just to be sure. - 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) { - OpenAllImpl(node->GetChild(i), initial_disposition, navigator, - opened_url); - } - } -} - -// Returns the number of descendants of node that are of type url. -int DescendantURLCount(BookmarkNode* node) { - int result = 0; - for (int i = 0; i < node->GetChildCount(); ++i) { - BookmarkNode* child = node->GetChild(i); - if (child->is_url()) - result++; - else - result += DescendantURLCount(child); - } - return result; -} - -bool ShouldOpenAll(HWND parent, BookmarkNode* node) { - int descendant_count = DescendantURLCount(node); - if (descendant_count < kNumURLsBeforePrompting) - return true; - - std::wstring message = - l10n_util::GetStringF(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL, - IntToWString(descendant_count)); - return (MessageBox(parent, message.c_str(), - l10n_util::GetString(IDS_PRODUCT_NAME).c_str(), - MB_YESNO | MB_ICONWARNING | MB_TOPMOST) == IDYES); -} - -// 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, - BookmarkNode* 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->GetContainer()->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); - BookmarkModel* model = view_->GetProfile()->GetBookmarkModel(); - 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 views::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. - BookmarkNode* node_; - - int visual_order_; - bool is_new_; - views::Window* window_; - - DISALLOW_EVIL_CONSTRUCTORS(EditFolderController); -}; - -} // namespace - -// BookmarkBarContextMenuController ------------------------------------------- - -// static -void BookmarkBarContextMenuController::OpenAll( - HWND parent, - PageNavigator* navigator, - BookmarkNode* node, - WindowOpenDisposition initial_disposition) { - if (!ShouldOpenAll(parent, node)) - return; - - PageNavigator* nav = navigator; - bool opened_url = false; - OpenAllImpl(node, initial_disposition, &nav, &opened_url); -} - -BookmarkBarContextMenuController::BookmarkBarContextMenuController( - BookmarkBarView* view, - BookmarkNode* node) - : view_(view), - node_(node), - menu_(this) { - if (node->GetType() == history::StarredEntry::URL) { - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB)); - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW)); - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_OPEN_INCOGNITO, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_INCOGNITO)); - } else { - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_OPEN_ALL, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL)); - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO, - l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); - } - menu_.AppendSeparator(); - - if (node->GetParent() != - view->GetProfile()->GetBookmarkModel()->root_node()) { - menu_.AppendMenuItemWithLabel(IDS_BOOKMARK_BAR_EDIT, - l10n_util::GetString(IDS_BOOKMARK_BAR_EDIT)); - menu_.AppendMenuItemWithLabel( - IDS_BOOKMARK_BAR_REMOVE, - l10n_util::GetString(IDS_BOOKMARK_BAR_REMOVE)); - } - - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK, - l10n_util::GetString(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); - menu_.AppendMenuItemWithLabel( - IDS_BOOMARK_BAR_NEW_FOLDER, - l10n_util::GetString(IDS_BOOMARK_BAR_NEW_FOLDER)); - menu_.AppendSeparator(); - menu_.AppendMenuItem(IDS_BOOMARK_BAR_ALWAYS_SHOW, - l10n_util::GetString(IDS_BOOMARK_BAR_ALWAYS_SHOW), - views::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_->GetContainer()->GetHWND(), gfx::Rect(x, y, 0, 0), - views::MenuItemView::TOPLEFT, true); - - 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 IDS_BOOMARK_BAR_OPEN_INCOGNITO: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInIncognito", - profile); - - view_->GetPageNavigator()->OpenURL(node_->GetURL(), GURL(), - OFF_THE_RECORD, - PageTransition::AUTO_BOOKMARK); - break; - - case IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewWindow", - profile); - - view_->GetPageNavigator()->OpenURL(node_->GetURL(), GURL(), NEW_WINDOW, - PageTransition::AUTO_BOOKMARK); - break; - - case IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewTab", - profile); - - view_->GetPageNavigator()->OpenURL(node_->GetURL(), GURL(), - NEW_FOREGROUND_TAB, - PageTransition::AUTO_BOOKMARK); - break; - - case IDS_BOOMARK_BAR_OPEN_ALL: - case IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO: - case IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW: { - WindowOpenDisposition initial_disposition; - if (id == IDS_BOOMARK_BAR_OPEN_ALL) { - initial_disposition = CURRENT_TAB; - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenAll", - profile); - } else if (id == IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW) { - initial_disposition = NEW_WINDOW; - UserMetrics::RecordAction( - L"BookmarkBar_ContextMenu_OpenAllInNewWindow", profile); - } else { - initial_disposition = OFF_THE_RECORD; - UserMetrics::RecordAction( - L"BookmarkBar_ContextMenu_OpenAllIncognito", profile); - } - - // GetContainer is NULL during testing. - HWND parent_hwnd = view_->GetContainer() ? - view_->GetContainer()->GetHWND() : 0; - - OpenAll(parent_hwnd, view_->GetPageNavigator(), node_, - initial_disposition); - break; - } - - case IDS_BOOKMARK_BAR_EDIT: - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Edit", profile); - - if (node_->GetType() == history::StarredEntry::URL) { - BookmarkEditorView::Show(view_->GetContainer()->GetHWND(), - view_->GetProfile(), NULL, node_); - } else { - // Controller deletes itself when done. - EditFolderController* controller = new EditFolderController( - view_, node_, -1, false); - controller->Show(); - } - break; - - case IDS_BOOKMARK_BAR_REMOVE: { - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Remove", profile); - - view_->GetModel()->Remove(node_->GetParent(), - node_->GetParent()->IndexOfChild(node_)); - break; - } - - case IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK: { - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Add", profile); - - BookmarkEditorView::Show(view_->GetContainer()->GetHWND(), - view_->GetProfile(), node_, NULL); - break; - } - - case IDS_BOOMARK_BAR_NEW_FOLDER: { - UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_NewFolder", - profile); - - int visual_order; - BookmarkNode* 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 IDS_BOOMARK_BAR_ALWAYS_SHOW: - view_->ToggleWhenVisible(); - break; - - default: - NOTREACHED(); - } -} - -bool BookmarkBarContextMenuController::IsItemChecked(int id) const { - DCHECK(id == IDS_BOOMARK_BAR_ALWAYS_SHOW); - return view_->GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); -} - -bool BookmarkBarContextMenuController::IsCommandEnabled(int id) const { - switch (id) { - case IDS_BOOMARK_BAR_OPEN_INCOGNITO: - return !view_->GetProfile()->IsOffTheRecord(); - - case IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO: - return NodeHasURLs(node_) && !view_->GetProfile()->IsOffTheRecord(); - - case IDS_BOOMARK_BAR_OPEN_ALL: - case IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW: - return NodeHasURLs(node_); - } - return true; -} - -// Returns the parent node and visual_order to use when adding new -// bookmarks/folders. -BookmarkNode* 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 deleted file mode 100644 index e6227cd..0000000 --- a/chrome/browser/bookmark_bar_context_menu_controller.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_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" -#include "webkit/glue/window_open_disposition.h" - -class BookmarkNode; -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 views::MenuDelegate, - public BookmarkBarView::ModelChangedListener { - public: - // Recursively opens all bookmarks of |node|. |initial_disposition| dictates - // how the first URL is opened, all subsequent URLs are opened as background - // tabs. - static void OpenAll(HWND parent, - PageNavigator* navigator, - BookmarkNode* node, - WindowOpenDisposition initial_disposition); - - BookmarkBarContextMenuController(BookmarkBarView* view, - BookmarkNode* 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. - views::MenuItemView* menu() { return &menu_; } - - // Menu::Delegate methods. - virtual void ExecuteCommand(int id); - virtual bool IsItemChecked(int id) const; - virtual bool IsCommandEnabled(int id) const; - - private: - // Returns the parent node and visual_order to use when adding new - // bookmarks/folders. - BookmarkNode* GetParentAndVisualOrderForNewNode(int* visual_order); - - views::MenuItemView menu_; - BookmarkBarView* view_; - BookmarkNode* 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 deleted file mode 100644 index 58e59e3..0000000 --- a/chrome/browser/bookmark_bar_context_menu_controller_test.cc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/bookmark_bar_context_menu_controller.h" -#include "chrome/browser/bookmarks/bookmark_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" - -#include "generated_resources.h" - -namespace { - -// PageNavigator implementation that records the URL. -class TestingPageNavigator : public PageNavigator { - public: - virtual void OpenURL(const GURL& url, const GURL& referrer, - 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_->CreateBookmarkModel(true); - - model_ = profile_->GetBookmarkModel(); - - bb_view_.reset(new BookmarkBarView(profile_.get(), NULL)); - bb_view_->SetPageNavigator(&navigator_); - - AddTestData(); - } - - virtual void TearDown() { - BookmarkBarView::testing_ = false; - - // Flush the message loop to make Purify happy. - message_loop_.RunAllPending(); - } - - protected: - MessageLoopForUI message_loop_; - scoped_ptr<TestingProfile> profile_; - BookmarkModel* model_; - scoped_ptr<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")); - BookmarkNode* f1 = - model_->AddGroup(model_->GetBookmarkBarNode(), 1, L"F1"); - model_->AddURL(f1, 0, L"f1a", GURL(test_base + "f1a")); - BookmarkNode* f11 = model_->AddGroup(f1, 1, L"F11"); - model_->AddURL(f11, 0, L"f11a", GURL(test_base + "f11a")); - model_->AddGroup(model_->GetBookmarkBarNode(), 2, L"F2"); - } -}; - -// Tests Deleting from the menu. -TEST_F(BookmarkBarContextMenuControllerTest, DeleteURL) { - BookmarkBarContextMenuController controller( - bb_view_.get(), model_->GetBookmarkBarNode()->GetChild(0)); - GURL url = model_->GetBookmarkBarNode()->GetChild(0)->GetURL(); - ASSERT_TRUE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); - // Delete the URL. - controller.ExecuteCommand(IDS_BOOKMARK_BAR_REMOVE); - // Model shouldn't have URL anymore. - ASSERT_FALSE(model_->IsBookmarked(url)); -} - -// Tests open all on a folder with a couple of bookmarks. -TEST_F(BookmarkBarContextMenuControllerTest, OpenAll) { - BookmarkNode* folder = model_->GetBookmarkBarNode()->GetChild(1); - BookmarkBarContextMenuController controller(bb_view_.get(), folder); - ASSERT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); - ASSERT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); - ASSERT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); - // Open it. - controller.ExecuteCommand(IDS_BOOMARK_BAR_OPEN_ALL); - // 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]); - - // Make sure incognito is disabled when OTR. - profile_->set_off_the_record(true); - ASSERT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); - ASSERT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); - ASSERT_TRUE( - controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); -} - -// Tests that menus are appropriately disabled for empty folders. -TEST_F(BookmarkBarContextMenuControllerTest, DisableForEmptyFolder) { - BookmarkNode* folder = model_->GetBookmarkBarNode()->GetChild(2); - BookmarkBarContextMenuController controller(bb_view_.get(), folder); - EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); - EXPECT_FALSE( - controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); -} - -// Tests the enabled state of open incognito. -TEST_F(BookmarkBarContextMenuControllerTest, DisableIncognito) { - BookmarkBarContextMenuController controller( - bb_view_.get(), model_->GetBookmarkBarNode()->GetChild(0)); - EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_INCOGNITO)); - profile_->set_off_the_record(true); - EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_INCOGNITO)); -} diff --git a/chrome/browser/bookmarks/bookmark_context_menu.cc b/chrome/browser/bookmarks/bookmark_context_menu.cc new file mode 100644 index 0000000..4f033ac --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_context_menu.cc @@ -0,0 +1,458 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/bookmarks/bookmark_context_menu.h" + +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_utils.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/tab_contents.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/views/bookmark_bar_view.h" +#include "chrome/browser/views/bookmark_editor_view.h" +#include "chrome/browser/views/bookmark_manager_view.h" +#include "chrome/browser/views/input_window.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/container.h" +#include "chrome/views/window.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(BookmarkNode* node) { + if (node->is_url()) + return true; + + for (int i = 0; i < node->GetChildCount(); ++i) { + if (NodeHasURLs(node->GetChild(i))) + return true; + } + return false; +} + +// EditFolderController ------------------------------------------------------- + +// EditFolderController manages the editing and/or creation of a folder. If the +// user presses ok, the name change is committed to the model. +// +// EditFolderController deletes itself when the window is closed. +class EditFolderController : public InputWindowDelegate, + public BookmarkModelObserver { + public: + virtual ~EditFolderController() { + if (model_) + model_->RemoveObserver(this); + } + + static void Show(Profile* profile, + HWND hwnd, + BookmarkNode* node, + bool is_new) { + // EditFolderController deletes itself when done. + EditFolderController* controller = + new EditFolderController(profile, hwnd, node, is_new); + controller->Show(); + } + + private: + EditFolderController(Profile* profile, + HWND hwnd, + BookmarkNode* node, + bool is_new) + : profile_(profile), + model_(profile->GetBookmarkModel()), + node_(node), + is_new_(is_new) { + DCHECK(is_new_ || node); + window_ = CreateInputWindow(hwnd, this); + model_->AddObserver(this); + } + + void Show() { + window_->Show(); + } + + // InputWindowDelegate methods. + 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) { + if (is_new_) + model_->AddGroup(node_, node_->GetChildCount(), text); + else + model_->SetTitle(node_, text); + } + + virtual void InputCanceled() { + } + + virtual void WindowClosing() { + 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); + } + + // BookmarkModelObserver methods, all invoke ModelChanged and close the + // dialog. + virtual void Loaded(BookmarkModel* model) {} + virtual void BookmarkModelBeingDeleted(BookmarkModel* model) { + model_->RemoveObserver(this); + model_ = NULL; + ModelChanged(); + } + + virtual void BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index) { + ModelChanged(); + } + + virtual void BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) { + ModelChanged(); + } + + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index, + BookmarkNode* node) { + ModelChanged(); + } + + virtual void BookmarkNodeChanged(BookmarkModel* model, BookmarkNode* node) { + ModelChanged(); + } + + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + BookmarkNode* node) {} + + void ModelChanged() { + window_->Close(); + } + + Profile* profile_; + BookmarkModel* model_; + // 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. + BookmarkNode* node_; + + bool is_new_; + views::Window* window_; + + DISALLOW_COPY_AND_ASSIGN(EditFolderController); +}; + +} // namespace + +// BookmarkContextMenu ------------------------------------------- + +BookmarkContextMenu::BookmarkContextMenu( + HWND hwnd, + Profile* profile, + Browser* browser, + PageNavigator* navigator, + BookmarkNode* parent, + const std::vector<BookmarkNode*>& selection, + ConfigurationType configuration) + : hwnd_(hwnd), + profile_(profile), + browser_(browser), + navigator_(navigator), + parent_(parent), + selection_(selection), + model_(profile->GetBookmarkModel()), + configuration_(configuration) { + DCHECK(profile_); + DCHECK(model_->IsLoaded()); + menu_.reset(new views::MenuItemView(this)); + if (selection.size() == 1 && selection[0]->is_url()) { + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_OPEN_ALL, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB)); + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW)); + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_INCOGNITO)); + } else { + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_OPEN_ALL, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL)); + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); + } + menu_->AppendSeparator(); + + menu_->AppendMenuItemWithLabel(IDS_BOOKMARK_BAR_EDIT, + l10n_util::GetString(IDS_BOOKMARK_BAR_EDIT)); + menu_->AppendMenuItemWithLabel( + IDS_BOOKMARK_BAR_REMOVE, + l10n_util::GetString(IDS_BOOKMARK_BAR_REMOVE)); + + if (configuration != BOOKMARK_BAR) { + menu_->AppendMenuItemWithLabel( + IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER, + l10n_util::GetString(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); + } + + menu_->AppendSeparator(); + + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK, + l10n_util::GetString(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_BAR_NEW_FOLDER, + l10n_util::GetString(IDS_BOOMARK_BAR_NEW_FOLDER)); + + if (configuration == BOOKMARK_BAR) { + menu_->AppendSeparator(); + menu_->AppendMenuItemWithLabel(IDS_BOOKMARK_MANAGER, + l10n_util::GetString(IDS_BOOKMARK_MANAGER)); + menu_->AppendMenuItem(IDS_BOOMARK_BAR_ALWAYS_SHOW, + l10n_util::GetString(IDS_BOOMARK_BAR_ALWAYS_SHOW), + views::MenuItemView::CHECKBOX); + } + model_->AddObserver(this); +} + +BookmarkContextMenu::~BookmarkContextMenu() { + if (model_) + model_->RemoveObserver(this); +} + +void BookmarkContextMenu::RunMenuAt(int x, int y) { + if (!model_->IsLoaded()) { + NOTREACHED(); + return; + } + // width/height don't matter here. + menu_->RunMenuAt(hwnd_, gfx::Rect(x, y, 0, 0), views::MenuItemView::TOPLEFT, + true); +} + +void BookmarkContextMenu::ExecuteCommand(int id) { + switch (id) { + case IDS_BOOMARK_BAR_OPEN_ALL: + case IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO: + case IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW: { + PageNavigator* navigator = browser_ ? + browser_->GetSelectedTabContents() : navigator_; + WindowOpenDisposition initial_disposition; + if (id == IDS_BOOMARK_BAR_OPEN_ALL) { + initial_disposition = NEW_FOREGROUND_TAB; + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenAll", + profile_); + } else if (id == IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW) { + initial_disposition = NEW_WINDOW; + UserMetrics::RecordAction( + L"BookmarkBar_ContextMenu_OpenAllInNewWindow", profile_); + } else { + initial_disposition = OFF_THE_RECORD; + UserMetrics::RecordAction( + L"BookmarkBar_ContextMenu_OpenAllIncognito", profile_); + } + + bookmark_utils::OpenAll(hwnd_, profile_, navigator, selection_, + initial_disposition); + break; + } + + case IDS_BOOKMARK_BAR_EDIT: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Edit", profile_); + + if (selection_.size() != 1) { + NOTREACHED(); + return; + } + + if (selection_[0]->is_url()) { + BookmarkEditorView::Configuration editor_config; + if (configuration_ == BOOKMARK_BAR) + editor_config = BookmarkEditorView::SHOW_TREE; + else + editor_config = BookmarkEditorView::NO_TREE; + BookmarkEditorView::Show(hwnd_, profile_, NULL, selection_[0], + editor_config); + } else { + EditFolderController::Show(profile_, hwnd_, selection_[0], false); + } + break; + + case IDS_BOOKMARK_BAR_REMOVE: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Remove", profile_); + BookmarkModel* model = RemoveModelObserver(); + + for (size_t i = 0; i < selection_.size(); ++i) { + model->Remove(selection_[i]->GetParent(), + selection_[i]->GetParent()->IndexOfChild(selection_[i])); + } + selection_.clear(); + break; + } + + case IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Add", profile_); + + BookmarkEditorView::Configuration editor_config; + if (configuration_ == BOOKMARK_BAR) + editor_config = BookmarkEditorView::SHOW_TREE; + else + editor_config = BookmarkEditorView::NO_TREE; + BookmarkEditorView::Show(hwnd_, profile_, GetParentForNewNodes(), NULL, + editor_config); + break; + } + + case IDS_BOOMARK_BAR_NEW_FOLDER: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_NewFolder", + profile_); + + EditFolderController::Show(profile_, hwnd_, GetParentForNewNodes(), + true); + break; + } + + case IDS_BOOMARK_BAR_ALWAYS_SHOW: + BookmarkBarView::ToggleWhenVisible(profile_); + break; + + case IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_ShowInFolder", + profile_); + + if (selection_.size() != 1) { + NOTREACHED(); + return; + } + + if (BookmarkManagerView::current()) + BookmarkManagerView::current()->SelectInTree(selection_[0]); + break; + + default: + NOTREACHED(); + } +} + +bool BookmarkContextMenu::IsItemChecked(int id) const { + DCHECK(id == IDS_BOOMARK_BAR_ALWAYS_SHOW); + return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); +} + +bool BookmarkContextMenu::IsCommandEnabled(int id) const { + bool is_root_node = + (selection_.size() == 1 && + selection_[0]->GetParent() == model_->root_node()); + switch (id) { + case IDS_BOOMARK_BAR_OPEN_INCOGNITO: + return !profile_->IsOffTheRecord(); + + case IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO: + return HasURLs() && !profile_->IsOffTheRecord(); + + case IDS_BOOMARK_BAR_OPEN_ALL: + case IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW: + return HasURLs(); + + case IDS_BOOKMARK_BAR_EDIT: + return selection_.size() == 1 && !is_root_node; + + case IDS_BOOKMARK_BAR_REMOVE: + return !selection_.empty() && !is_root_node; + + case IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER: + return configuration_ == BOOKMARK_MANAGER_TABLE && + selection_.size() == 1; + + case IDS_BOOMARK_BAR_NEW_FOLDER: + case IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK: + return GetParentForNewNodes() != NULL; + } + return true; +} + +void BookmarkContextMenu::BookmarkModelBeingDeleted(BookmarkModel* model) { + ModelChanged(); +} + +void BookmarkContextMenu::BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index) { + ModelChanged(); +} + +void BookmarkContextMenu::BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) { + ModelChanged(); +} + +void BookmarkContextMenu::BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index, + BookmarkNode* node) { + ModelChanged(); +} + +void BookmarkContextMenu::BookmarkNodeChanged(BookmarkModel* model, + BookmarkNode* node) { + ModelChanged(); +} + +void BookmarkContextMenu::ModelChanged() { + menu_->Cancel(); +} + +BookmarkModel* BookmarkContextMenu::RemoveModelObserver() { + BookmarkModel* model = model_; + model_->RemoveObserver(this); + model_ = NULL; + return model; +} + +bool BookmarkContextMenu::HasURLs() const { + for (size_t i = 0; i < selection_.size(); ++i) { + if (NodeHasURLs(selection_[i])) + return true; + } + return false; +} + +BookmarkNode* BookmarkContextMenu::GetParentForNewNodes() const { + return (selection_.size() == 1 && selection_[0]->is_folder()) ? + selection_[0] : parent_; +} diff --git a/chrome/browser/bookmarks/bookmark_context_menu.h b/chrome/browser/bookmarks/bookmark_context_menu.h new file mode 100644 index 0000000..5b48627 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_context_menu.h @@ -0,0 +1,105 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_BOOKMARKS_BOOKMARK_CONTEXT_MENU_H_ +#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_CONTEXT_MENU_H_ + +#include <vector> + +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/views/chrome_menu.h" + +class Browser; +class PageNavigator; + +// BookmarkContextMenu manages the context menu shown for the +// bookmark bar, items on the bookmark bar, submenus of the bookmark bar and +// the bookmark manager. +class BookmarkContextMenu : public views::MenuDelegate, + public BookmarkModelObserver { + public: + // Used to configure what the context menu shows. + enum ConfigurationType { + BOOKMARK_BAR, + BOOKMARK_MANAGER_TABLE, + BOOKMARK_MANAGER_TREE + }; + + // Creates the bookmark context menu. + // |profile| is used for opening urls as well as enabling 'open incognito'. + // |browser| is used to determine the PageNavigator and may be null. + // |navigator| is used if |browser| is null, and is provided for testing. + // |parent| is the parent for newly created nodes if |selection| is empty. + // |selection| is the nodes the context menu operates on and may be empty. + // |configuration| determines which items to show. + BookmarkContextMenu(HWND hwnd, + Profile* profile, + Browser* browser, + PageNavigator* navigator, + BookmarkNode* parent, + const std::vector<BookmarkNode*>& selection, + ConfigurationType configuration); + virtual ~BookmarkContextMenu(); + + // Shows the menu at the specified place. + void RunMenuAt(int x, int y); + + // Returns the menu. + views::MenuItemView* menu() const { return menu_.get(); } + + // Menu::Delegate methods. + virtual void ExecuteCommand(int id); + virtual bool IsItemChecked(int id) const; + virtual bool IsCommandEnabled(int id) const; + + private: + // BookmarkModelObserver method. Any change to the model results in closing + // the menu. + virtual void Loaded(BookmarkModel* model) {} + virtual void BookmarkModelBeingDeleted(BookmarkModel* model); + virtual void BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index); + virtual void BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index); + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index, + BookmarkNode* node); + virtual void BookmarkNodeChanged(BookmarkModel* model, + BookmarkNode* node); + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + BookmarkNode* node) {} + + // Invoked from the various bookmark model observer methods. Closes the menu. + void ModelChanged(); + + // Removes the observer from the model and NULLs out model_. + BookmarkModel* RemoveModelObserver(); + + // Returns true if selection_ has at least one bookmark of type url. + bool HasURLs() const; + + // Returns the parent for newly created folders/bookmarks. If selection_ + // has one element and it is a folder, selection_[0] is returned, otherwise + // parent_ is returned. + BookmarkNode* GetParentForNewNodes() const; + + HWND hwnd_; + Profile* profile_; + Browser* browser_; + PageNavigator* navigator_; + BookmarkNode* parent_; + std::vector<BookmarkNode*> selection_; + scoped_ptr<views::MenuItemView> menu_; + BookmarkModel* model_; + ConfigurationType configuration_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkContextMenu); +}; + +#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_CONTEXT_MENU_H_ diff --git a/chrome/browser/bookmarks/bookmark_context_menu_test.cc b/chrome/browser/bookmarks/bookmark_context_menu_test.cc new file mode 100644 index 0000000..e665058 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_context_menu_test.cc @@ -0,0 +1,271 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/bookmarks/bookmark_context_menu.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_utils.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" + +#include "generated_resources.h" + +namespace { + +// PageNavigator implementation that records the URL. +class TestingPageNavigator : public PageNavigator { + public: + virtual void OpenURL(const GURL& url, + const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + urls_.push_back(url); + } + + std::vector<GURL> urls_; +}; + +} // namespace + +class BookmarkContextMenuTest : public testing::Test { + public: + BookmarkContextMenuTest() + : model_(NULL) { + } + + virtual void SetUp() { + BookmarkBarView::testing_ = true; + + profile_.reset(new TestingProfile()); + profile_->set_has_history_service(true); + profile_->CreateBookmarkModel(true); + profile_->BlockUntilBookmarkModelLoaded(); + + model_ = profile_->GetBookmarkModel(); + + AddTestData(); + } + + virtual void TearDown() { + BookmarkBarView::testing_ = false; + + // Flush the message loop to make Purify happy. + message_loop_.RunAllPending(); + } + + protected: + MessageLoopForUI message_loop_; + scoped_ptr<TestingProfile> profile_; + BookmarkModel* model_; + TestingPageNavigator navigator_; + + private: + // Creates the following structure: + // a + // F1 + // f1a + // F11 + // f11a + // F2 + // F3 + // F4 + // f4a + void AddTestData() { + std::string test_base = "file:///c:/tmp/"; + + model_->AddURL(model_->GetBookmarkBarNode(), 0, L"a", + GURL(test_base + "a")); + BookmarkNode* f1 = + model_->AddGroup(model_->GetBookmarkBarNode(), 1, L"F1"); + model_->AddURL(f1, 0, L"f1a", GURL(test_base + "f1a")); + BookmarkNode* f11 = model_->AddGroup(f1, 1, L"F11"); + model_->AddURL(f11, 0, L"f11a", GURL(test_base + "f11a")); + model_->AddGroup(model_->GetBookmarkBarNode(), 2, L"F2"); + model_->AddGroup(model_->GetBookmarkBarNode(), 3, L"F3"); + BookmarkNode* f4 = + model_->AddGroup(model_->GetBookmarkBarNode(), 4, L"F4"); + model_->AddURL(f4, 0, L"f4a", GURL(test_base + "f4a")); + } +}; + +// Tests Deleting from the menu. +TEST_F(BookmarkContextMenuTest, DeleteURL) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(0)); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0]->GetParent(), nodes, + BookmarkContextMenu::BOOKMARK_BAR); + GURL url = model_->GetBookmarkBarNode()->GetChild(0)->GetURL(); + ASSERT_TRUE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); + // Delete the URL. + controller.ExecuteCommand(IDS_BOOKMARK_BAR_REMOVE); + // Model shouldn't have URL anymore. + ASSERT_FALSE(model_->IsBookmarked(url)); +} + +// Tests open all on a folder with a couple of bookmarks. +TEST_F(BookmarkContextMenuTest, OpenAll) { + BookmarkNode* folder = model_->GetBookmarkBarNode()->GetChild(1); + bookmark_utils::OpenAll( + NULL, profile_.get(), &navigator_, folder, NEW_FOREGROUND_TAB); + + // 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 the enabled state of the menus when supplied an empty vector. +TEST_F(BookmarkContextMenuTest, EmptyNodes) { + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, model_->other_node(), + std::vector<BookmarkNode*>(), BookmarkContextMenu::BOOKMARK_BAR); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_NEW_FOLDER)); +} + +// Tests the enabled state of the menus when supplied a vector with a single +// url. +TEST_F(BookmarkContextMenuTest, SingleURL) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(0)); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0]->GetParent(), + nodes, BookmarkContextMenu::BOOKMARK_BAR); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_NEW_FOLDER)); +} + +// Tests the enabled state of the menus when supplied a vector with multiple +// urls. +TEST_F(BookmarkContextMenuTest, MultipleURLs) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(0)); + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(1)->GetChild(0)); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0]->GetParent(), + nodes, BookmarkContextMenu::BOOKMARK_BAR); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_NEW_FOLDER)); +} + +// Tests the enabled state of the menus when supplied an vector with a single +// folder. +TEST_F(BookmarkContextMenuTest, SingleFolder) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(2)); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0]->GetParent(), + nodes, BookmarkContextMenu::BOOKMARK_BAR); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_NEW_FOLDER)); +} + +// Tests the enabled state of the menus when supplied a vector with multiple +// folders, all of which are empty. +TEST_F(BookmarkContextMenuTest, MultipleEmptyFolders) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(2)); + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(3)); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0]->GetParent(), + nodes, BookmarkContextMenu::BOOKMARK_BAR); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_NEW_FOLDER)); +} + +// Tests the enabled state of the menus when supplied a vector with multiple +// folders, some of which contain URLs. +TEST_F(BookmarkContextMenuTest, MultipleFoldersWithURLs) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(3)); + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(4)); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0]->GetParent(), + nodes, BookmarkContextMenu::BOOKMARK_BAR); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); + EXPECT_TRUE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); + EXPECT_FALSE( + controller.IsCommandEnabled(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + EXPECT_TRUE( + controller.IsCommandEnabled(IDS_BOOMARK_BAR_NEW_FOLDER)); +} + +// Tests the enabled state of open incognito. +TEST_F(BookmarkContextMenuTest, DisableIncognito) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->GetBookmarkBarNode()->GetChild(0)); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0]->GetParent(), + nodes, BookmarkContextMenu::BOOKMARK_BAR); + profile_->set_off_the_record(true); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_INCOGNITO)); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); +} + +// Tests that you can't remove/edit when showing the other node. +TEST_F(BookmarkContextMenuTest, DisabledItemsWithOtherNode) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(model_->other_node()); + BookmarkContextMenu controller( + NULL, profile_.get(), NULL, NULL, nodes[0], nodes, + BookmarkContextMenu::BOOKMARK_BAR); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_EDIT)); + EXPECT_FALSE(controller.IsCommandEnabled(IDS_BOOKMARK_BAR_REMOVE)); +} diff --git a/chrome/browser/bookmarks/bookmark_drag_utils.cc b/chrome/browser/bookmarks/bookmark_drag_utils.cc deleted file mode 100644 index 863369c..0000000 --- a/chrome/browser/bookmarks/bookmark_drag_utils.cc +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/bookmarks/bookmark_drag_utils.h" - -#include "chrome/browser/bookmarks/bookmark_drag_data.h" -#include "chrome/browser/bookmarks/bookmark_model.h" -#include "chrome/common/drag_drop_types.h" -#include "chrome/views/event.h" - -namespace { - -void CloneDragDataImpl(BookmarkModel* model, - const BookmarkDragData::Element& element, - BookmarkNode* parent, - int index_to_add_at) { - if (element.is_url) { - model->AddURL(parent, index_to_add_at, element.title, element.url); - } else { - BookmarkNode* new_folder = model->AddGroup(parent, index_to_add_at, - element.title); - for (int i = 0; i < static_cast<int>(element.children.size()); ++i) - CloneDragDataImpl(model, element.children[i], new_folder, i); - } -} - -} // namespace - -namespace bookmark_drag_utils { - -int PreferredDropOperation(const views::DropTargetEvent& event, - int operation) { - int common_ops = (event.GetSourceOperations() & operation); - if (!common_ops) - return 0; - if (DragDropTypes::DRAG_COPY & common_ops) - return DragDropTypes::DRAG_COPY; - if (DragDropTypes::DRAG_LINK & common_ops) - return DragDropTypes::DRAG_LINK; - if (DragDropTypes::DRAG_MOVE & common_ops) - return DragDropTypes::DRAG_MOVE; - return DragDropTypes::DRAG_NONE; -} - -bool IsValidDropLocation(Profile* profile, - const BookmarkDragData& data, - BookmarkNode* drop_parent, - int index) { - if (!drop_parent->is_folder()) { - NOTREACHED(); - return false; - } - - if (!data.is_valid()) - return false; - - if (data.IsFromProfile(profile)) { - std::vector<BookmarkNode*> nodes = data.GetNodes(profile); - for (size_t i = 0; i < nodes.size(); ++i) { - // Don't allow the drop if the user is attempting to drop on one of the - // nodes being dragged. - BookmarkNode* node = nodes[i]; - int node_index = (drop_parent == node->GetParent()) ? - drop_parent->IndexOfChild(nodes[i]) : -1; - if (node_index != -1 && (index == node_index || index == node_index + 1)) - return false; - - // drop_parent can't accept a child that is an ancestor. - if (drop_parent->HasAncestor(node)) - return false; - } - return true; - } - // From the same profile, always accept. - return true; -} - -void CloneDragData(BookmarkModel* model, - const std::vector<BookmarkDragData::Element>& elements, - BookmarkNode* parent, - int index_to_add_at) { - if (!parent->is_folder() || !model) { - NOTREACHED(); - return; - } - for (size_t i = 0; i < elements.size(); ++i) - CloneDragDataImpl(model, elements[i], parent, index_to_add_at + i); -} - -} // namespace bookmark_drag_utils diff --git a/chrome/browser/bookmarks/bookmark_folder_tree_model.cc b/chrome/browser/bookmarks/bookmark_folder_tree_model.cc index f365e76..b1932a4 100644 --- a/chrome/browser/bookmarks/bookmark_folder_tree_model.cc +++ b/chrome/browser/bookmarks/bookmark_folder_tree_model.cc @@ -33,6 +33,8 @@ BookmarkFolderTreeModel::NodeType BookmarkFolderTreeModel::GetNodeType( return RECENTLY_BOOKMARKED; if (node == search_node_) return SEARCH; + if (node == GetRoot()) + return NONE; return BOOKMARK; } diff --git a/chrome/browser/bookmarks/bookmark_utils.cc b/chrome/browser/bookmarks/bookmark_utils.cc new file mode 100644 index 0000000..d9b0be1 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_utils.cc @@ -0,0 +1,241 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/bookmarks/bookmark_utils.h" + +#include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/page_navigator.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/event.h" + +#include "chromium_strings.h" +#include "generated_resources.h" + +namespace { + +// Number of bookmarks we'll open before prompting the user to see if they +// really want to open all. +const int kNumURLsBeforePrompting = 15; + +// A PageNavigator implementation that creates a new Browser. This is used when +// opening a url and there is no Browser open. The Browser is created the first +// time the PageNavigator method is invoked. +class NewBrowserPageNavigator : public PageNavigator { + public: + explicit NewBrowserPageNavigator(Profile* profile) + : profile_(profile), + browser_(NULL) {} + + virtual ~NewBrowserPageNavigator() { + if (browser_) + browser_->Show(); + } + + Browser* browser() const { return browser_; } + + virtual void OpenURL(const GURL& url, + const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + if (!browser_) { + Profile* profile = (disposition == OFF_THE_RECORD) ? + profile_->GetOffTheRecordProfile() : profile_; + browser_ = new Browser(gfx::Rect(), SW_SHOW, profile, + BrowserType::TABBED_BROWSER, std::wstring()); + // Always open the first tab in the foreground. + disposition = NEW_FOREGROUND_TAB; + } + browser_->OpenURLFromTab(NULL, url, referrer, NEW_FOREGROUND_TAB, transition); + } + + private: + Profile* profile_; + Browser* browser_; + + DISALLOW_COPY_AND_ASSIGN(NewBrowserPageNavigator); +}; + +void CloneDragDataImpl(BookmarkModel* model, + const BookmarkDragData::Element& element, + BookmarkNode* parent, + int index_to_add_at) { + if (element.is_url) { + model->AddURL(parent, index_to_add_at, element.title, element.url); + } else { + BookmarkNode* new_folder = model->AddGroup(parent, index_to_add_at, + element.title); + for (int i = 0; i < static_cast<int>(element.children.size()); ++i) + CloneDragDataImpl(model, element.children[i], new_folder, i); + } +} + +// Returns the number of descendants of node that are of type url. +int DescendantURLCount(BookmarkNode* node) { + int result = 0; + for (int i = 0; i < node->GetChildCount(); ++i) { + BookmarkNode* child = node->GetChild(i); + if (child->is_url()) + result++; + else + result += DescendantURLCount(child); + } + return result; +} + +// Implementation of OpenAll. Opens all nodes of type URL and recurses for +// groups. |navigator| is the PageNavigator used to open URLs. After the first +// url is opened |opened_url| is set to true and |navigator| is set to the +// PageNavigator of the last active tab. This is done to handle a window +// disposition of new window, in which case we want subsequent tabs to open in +// that window. +void OpenAllImpl(BookmarkNode* node, + WindowOpenDisposition initial_disposition, + PageNavigator** navigator, + bool* opened_url) { + if (node->is_url()) { + WindowOpenDisposition disposition; + if (*opened_url) + disposition = NEW_BACKGROUND_TAB; + else + disposition = initial_disposition; + (*navigator)->OpenURL(node->GetURL(), GURL(), disposition, + PageTransition::AUTO_BOOKMARK); + if (!*opened_url) { + *opened_url = true; + // We opened the first URL which may have opened a new window or clobbered + // the current page, reset the navigator just to be sure. + 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) { + OpenAllImpl(node->GetChild(i), initial_disposition, navigator, + opened_url); + } + } +} + +bool ShouldOpenAll(HWND parent, const std::vector<BookmarkNode*>& nodes) { + int descendant_count = 0; + for (size_t i = 0; i < nodes.size(); ++i) + descendant_count += DescendantURLCount(nodes[i]); + if (descendant_count < kNumURLsBeforePrompting) + return true; + + std::wstring message = + l10n_util::GetStringF(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL, + IntToWString(descendant_count)); + return MessageBox(parent, message.c_str(), + l10n_util::GetString(IDS_PRODUCT_NAME).c_str(), + MB_YESNO | MB_ICONWARNING | MB_TOPMOST) == IDYES; +} + +} // namespace + +namespace bookmark_utils { + +int PreferredDropOperation(const views::DropTargetEvent& event, + int operation) { + int common_ops = (event.GetSourceOperations() & operation); + if (!common_ops) + return 0; + if (DragDropTypes::DRAG_COPY & common_ops) + return DragDropTypes::DRAG_COPY; + if (DragDropTypes::DRAG_LINK & common_ops) + return DragDropTypes::DRAG_LINK; + if (DragDropTypes::DRAG_MOVE & common_ops) + return DragDropTypes::DRAG_MOVE; + return DragDropTypes::DRAG_NONE; +} + +bool IsValidDropLocation(Profile* profile, + const BookmarkDragData& data, + BookmarkNode* drop_parent, + int index) { + if (!drop_parent->is_folder()) { + NOTREACHED(); + return false; + } + + if (!data.is_valid()) + return false; + + if (data.IsFromProfile(profile)) { + std::vector<BookmarkNode*> nodes = data.GetNodes(profile); + for (size_t i = 0; i < nodes.size(); ++i) { + // Don't allow the drop if the user is attempting to drop on one of the + // nodes being dragged. + BookmarkNode* node = nodes[i]; + int node_index = (drop_parent == node->GetParent()) ? + drop_parent->IndexOfChild(nodes[i]) : -1; + if (node_index != -1 && (index == node_index || index == node_index + 1)) + return false; + + // drop_parent can't accept a child that is an ancestor. + if (drop_parent->HasAncestor(node)) + return false; + } + return true; + } + // From the same profile, always accept. + return true; +} + +void CloneDragData(BookmarkModel* model, + const std::vector<BookmarkDragData::Element>& elements, + BookmarkNode* parent, + int index_to_add_at) { + if (!parent->is_folder() || !model) { + NOTREACHED(); + return; + } + for (size_t i = 0; i < elements.size(); ++i) + CloneDragDataImpl(model, elements[i], parent, index_to_add_at + i); +} + +void OpenAll(HWND parent, + Profile* profile, + PageNavigator* navigator, + const std::vector<BookmarkNode*>& nodes, + WindowOpenDisposition initial_disposition) { + if (!ShouldOpenAll(parent, nodes)) + return; + + NewBrowserPageNavigator navigator_impl(profile); + if (!navigator) { + Browser* browser = + BrowserList::FindBrowserWithType(profile, BrowserType::TABBED_BROWSER); + if (!browser || !browser->GetSelectedTabContents()) + navigator = &navigator_impl; + else + navigator = browser->GetSelectedTabContents(); + } + + bool opened_url = false; + for (size_t i = 0; i < nodes.size(); ++i) + OpenAllImpl(nodes[i], initial_disposition, &navigator, &opened_url); +} + +void OpenAll(HWND parent, + Profile* profile, + PageNavigator* navigator, + BookmarkNode* node, + WindowOpenDisposition initial_disposition) { + std::vector<BookmarkNode*> nodes; + nodes.push_back(node); + OpenAll(parent, profile, navigator, nodes, initial_disposition); +} + +} // namespace bookmark_utils diff --git a/chrome/browser/bookmarks/bookmark_drag_utils.h b/chrome/browser/bookmarks/bookmark_utils.h index 2805311..0711638 100644 --- a/chrome/browser/bookmarks/bookmark_drag_utils.h +++ b/chrome/browser/bookmarks/bookmark_utils.h @@ -2,21 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_BOOKMARKS_BOOKMARK_DRAG_UTILS_H_ -#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_DRAG_UTILS_H_ +#ifndef CHROME_BROWSER_BOOKMARKS_BOOKMARK_UTILS_H_ +#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_UTILS_H_ + +#include <vector> #include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "webkit/glue/window_open_disposition.h" class BookmarkNode; +class PageNavigator; class Profile; namespace views { class DropTargetEvent; } -// Functions used in managing bookmark drag and drop. These functions are -// used by both the bookmark bar and bookmark manager. -namespace bookmark_drag_utils { +// A collection of bookmark utility functions used by various parts of the UI +// that show bookmarks: bookmark manager, bookmark bar view ... +namespace bookmark_utils { // Calculates the drop operation given the event and supported set of // operations. This prefers the following ordering: COPY, LINK then MOVE. @@ -40,6 +44,24 @@ void CloneDragData(BookmarkModel* model, BookmarkNode* parent, int index_to_add_at); -} +// Recursively opens all bookmarks. |initial_disposition| dictates how the +// first URL is opened, all subsequent URLs are opened as background tabs. +// |navigator| is used to open the URLs. If |navigator| is NULL the last +// tabbed browser with the profile |profile| is used. If there is no browser +// with the specified profile a new one is created. +void OpenAll(HWND parent, + Profile* profile, + PageNavigator* navigator, + const std::vector<BookmarkNode*>& nodes, + WindowOpenDisposition initial_disposition); + +// Convenience for opening a single BookmarkNode. +void OpenAll(HWND parent, + Profile* profile, + PageNavigator* navigator, + BookmarkNode* node, + WindowOpenDisposition initial_disposition); + +} // namespace bookmark_utils -#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_DRAG_UTILS_H_ +#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_UTILS_H_ diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj index 75c604e..d9daed9 100644 --- a/chrome/browser/browser.vcproj +++ b/chrome/browser/browser.vcproj @@ -722,11 +722,11 @@ Name="Bookmarks" > <File - RelativePath=".\bookmark_bar_context_menu_controller.cc" + RelativePath=".\bookmarks\bookmark_context_menu.cc" > </File> <File - RelativePath=".\bookmark_bar_context_menu_controller.h" + RelativePath=".\bookmarks\bookmark_context_menu.h" > </File> <File @@ -746,14 +746,6 @@ > </File> <File - RelativePath=".\bookmarks\bookmark_drag_utils.cc" - > - </File> - <File - RelativePath=".\bookmarks\bookmark_drag_utils.h" - > - </File> - <File RelativePath=".\bookmarks\bookmark_folder_tree_model.cc" > </File> @@ -789,6 +781,14 @@ RelativePath=".\bookmarks\bookmark_table_model.h" > </File> + <File + RelativePath=".\bookmarks\bookmark_utils.cc" + > + </File> + <File + RelativePath=".\bookmarks\bookmark_utils.h" + > + </File> </Filter> <Filter Name="Tab Contents" diff --git a/chrome/browser/browser_commands.cc b/chrome/browser/browser_commands.cc index 0f01548..a4df1c1 100644 --- a/chrome/browser/browser_commands.cc +++ b/chrome/browser/browser_commands.cc @@ -25,6 +25,8 @@ #include "chrome/browser/task_manager.h" #include "chrome/browser/user_metrics.h" #include "chrome/browser/views/about_chrome_view.h" +#include "chrome/browser/views/bookmark_bar_view.h" +#include "chrome/browser/views/bookmark_manager_view.h" #include "chrome/browser/views/bug_report_view.h" #include "chrome/browser/views/clear_browsing_data.h" #include "chrome/browser/views/importer_view.h" @@ -104,6 +106,7 @@ void Browser::InitCommandState() { controller_.UpdateCommandEnabled(IDC_ABOUT, true); controller_.UpdateCommandEnabled(IDC_SHOW_HISTORY, true); controller_.UpdateCommandEnabled(IDC_SHOW_BOOKMARKS_BAR, true); + controller_.UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true); controller_.UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true); controller_.UpdateCommandEnabled(IDC_ENCODING, true); controller_.UpdateCommandEnabled(IDC_ENCODING_AUTO_DETECT, true); @@ -650,22 +653,15 @@ void Browser::ExecuteCommand(int id) { DuplicateContentsAt(selected_index()); break; - case IDC_SHOW_BOOKMARKS_BAR: { + case IDC_SHOW_BOOKMARKS_BAR: UserMetrics::RecordAction(L"ShowBookmarksBar", profile_); + BookmarkBarView::ToggleWhenVisible(profile_); + break; - // Invert the current pref. - PrefService* prefs = profile_->GetPrefs(); - prefs->SetBoolean(prefs::kShowBookmarkBar, - !prefs->GetBoolean(prefs::kShowBookmarkBar)); - prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread()); - - // And notify the notification service. - Source<Profile> source(profile_); - NotificationService::current()->Notify( - NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source, - NotificationService::NoDetails()); + case IDC_SHOW_BOOKMARK_MANAGER: + UserMetrics::RecordAction(L"ShowBookmarkManager", profile_); + BookmarkManagerView::Show(profile_); break; - } case IDC_SHOW_HISTORY: UserMetrics::RecordAction(L"ShowHistory", profile_); diff --git a/chrome/browser/browser_prefs.cc b/chrome/browser/browser_prefs.cc index 9f43083..46deec6 100644 --- a/chrome/browser/browser_prefs.cc +++ b/chrome/browser/browser_prefs.cc @@ -21,6 +21,8 @@ #include "chrome/browser/task_manager.h" #include "chrome/browser/template_url_prepopulate_data.h" #include "chrome/browser/views/bookmark_bar_view.h" +#include "chrome/browser/views/bookmark_manager_view.h" +#include "chrome/browser/views/bookmark_table_view.h" #include "chrome/browser/views/keyword_editor_view.h" #include "chrome/browser/views/page_info_window.h" #include "chrome/browser/web_contents.h" @@ -29,6 +31,7 @@ namespace browser { void RegisterAllPrefs(PrefService* user_prefs, PrefService* local_state) { // Prefs in Local State + BookmarkManagerView::RegisterPrefs(local_state); Browser::RegisterPrefs(local_state); CacheManagerHost::RegisterPrefs(local_state); chrome_browser_net::RegisterPrefs(local_state); @@ -44,6 +47,7 @@ void RegisterAllPrefs(PrefService* user_prefs, PrefService* local_state) { // User prefs BookmarkBarView::RegisterUserPrefs(user_prefs); + BookmarkTableView::RegisterUserPrefs(user_prefs); Browser::RegisterUserPrefs(user_prefs); chrome_browser_net::RegisterUserPrefs(user_prefs); DownloadManager::RegisterUserPrefs(user_prefs); diff --git a/chrome/browser/views/bookmark_bar_view.cc b/chrome/browser/views/bookmark_bar_view.cc index 70be891..8c08453 100644 --- a/chrome/browser/views/bookmark_bar_view.cc +++ b/chrome/browser/views/bookmark_bar_view.cc @@ -9,8 +9,8 @@ #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/bookmarks/bookmark_drag_utils.h" +#include "chrome/browser/bookmarks/bookmark_context_menu.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" @@ -395,8 +395,6 @@ class MenuRunner : public views::MenuDelegate, } virtual void ModelChanged() { - if (context_menu_.get()) - context_menu_->ModelChanged(); menu_.Cancel(); } @@ -531,8 +529,16 @@ class MenuRunner : public views::MenuDelegate, int y, bool is_mouse_gesture) { DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); + std::vector<BookmarkNode*> nodes; + nodes.push_back(menu_id_to_node_map_[id]); context_menu_.reset( - new BookmarkBarContextMenuController(view_, menu_id_to_node_map_[id])); + new BookmarkContextMenu(view_->GetContainer()->GetHWND(), + view_->GetProfile(), + view_->browser(), + view_->GetPageNavigator(), + nodes[0]->GetParent(), + nodes, + BookmarkContextMenu::BOOKMARK_BAR)); context_menu_->RunMenuAt(x, y); context_menu_.reset(NULL); return true; @@ -581,7 +587,7 @@ class MenuRunner : public views::MenuDelegate, // Data for the drop. BookmarkDragData drop_data_; - scoped_ptr<BookmarkBarContextMenuController> context_menu_; + scoped_ptr<BookmarkContextMenu> context_menu_; DISALLOW_COPY_AND_ASSIGN(MenuRunner); }; @@ -646,6 +652,22 @@ static const SkBitmap& GetGroupIcon() { } // static +void BookmarkBarView::ToggleWhenVisible(Profile* profile) { + PrefService* prefs = profile->GetPrefs(); + const bool always_show = !prefs->GetBoolean(prefs::kShowBookmarkBar); + + // The user changed when the bookmark bar is shown, update the preferences. + prefs->SetBoolean(prefs::kShowBookmarkBar, always_show); + prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread()); + + // And notify the notification service. + Source<Profile> source(profile); + NotificationService::current()->Notify( + NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source, + NotificationService::NoDetails()); +} + +// static void BookmarkBarView::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterBooleanPref(prefs::kShowBookmarkBar, false); } @@ -1079,24 +1101,6 @@ int BookmarkBarView::OnPerformDrop(const DropTargetEvent& event) { return PerformDropImpl(data, parent_node, index); } -void BookmarkBarView::ToggleWhenVisible() { - PrefService* prefs = profile_->GetPrefs(); - const bool always_show = !prefs->GetBoolean(prefs::kShowBookmarkBar); - - // The user changed when the bookmark bar is shown, update the preferences. - prefs->SetBoolean(prefs::kShowBookmarkBar, always_show); - prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread()); - - // And notify the notification service. - Source<Profile> source(profile_); - NotificationService::current()->Notify( - NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source, - NotificationService::NoDetails()); - - // May need to redraw the bar with a new style. - SchedulePaint(); -} - bool BookmarkBarView::IsAlwaysShown() { return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); } @@ -1422,8 +1426,8 @@ void BookmarkBarView::ButtonPressed(views::BaseButton* sender) { event_utils::DispositionFromEventFlags(sender->mouse_event_flags()), PageTransition::AUTO_BOOKMARK); } else { - BookmarkBarContextMenuController::OpenAll( - GetContainer()->GetHWND(), GetPageNavigator(), node, + bookmark_utils::OpenAll( + GetContainer()->GetHWND(), profile_, GetPageNavigator(), node, event_utils::DispositionFromEventFlags(sender->mouse_event_flags())); } UserMetrics::RecordAction(L"ClickedBookmarkBarURLButton", profile_); @@ -1438,18 +1442,30 @@ void BookmarkBarView::ShowContextMenu(View* source, return; } - BookmarkNode* node = model_->GetBookmarkBarNode(); + BookmarkNode* parent = NULL; + std::vector<BookmarkNode*> nodes; if (source == other_bookmarked_button_) { - node = model_->other_node(); + parent = model_->other_node(); + // Do this so the user can open all bookmarks. BookmarkContextMenu makes + // sure the user can edit/delete the node in this case. + nodes.push_back(parent); } else if (source != this) { // User clicked on one of the bookmark buttons, find which one they // clicked on. int bookmark_button_index = GetChildIndex(source); DCHECK(bookmark_button_index != -1 && bookmark_button_index < GetBookmarkButtonCount()); - node = model_->GetBookmarkBarNode()->GetChild(bookmark_button_index); + BookmarkNode* node = + model_->GetBookmarkBarNode()->GetChild(bookmark_button_index); + nodes.push_back(node); + parent = node->GetParent(); + } else { + parent = model_->GetBookmarkBarNode(); } - BookmarkBarContextMenuController controller(this, node); + BookmarkContextMenu controller(GetContainer()->GetHWND(), + GetProfile(), browser(), GetPageNavigator(), + parent, nodes, + BookmarkContextMenu::BOOKMARK_BAR); controller.RunMenuAt(x, y); } @@ -1492,7 +1508,7 @@ bool BookmarkBarView::IsItemChecked(int id) const { } void BookmarkBarView::ExecuteCommand(int id) { - ToggleWhenVisible(); + ToggleWhenVisible(profile_); } void BookmarkBarView::Observe(NotificationType type, @@ -1652,7 +1668,7 @@ int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event, int ops = data.GetFirstNode(profile_) ? DragDropTypes::DRAG_MOVE : DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK; - return bookmark_drag_utils::PreferredDropOperation(event, ops); + return bookmark_utils::PreferredDropOperation(event, ops); } for (int i = 0; i < GetBookmarkButtonCount() && @@ -1733,7 +1749,7 @@ int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event, // Currently only accept one dragged node at a time. return DragDropTypes::DRAG_NONE; - if (!bookmark_drag_utils::IsValidDropLocation(profile_, data, parent, index)) + if (!bookmark_utils::IsValidDropLocation(profile_, data, parent, index)) return DragDropTypes::DRAG_NONE; if (data.GetFirstNode(profile_)) { @@ -1741,7 +1757,7 @@ int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event, return DragDropTypes::DRAG_MOVE; } else { // User is dragging from another app, copy. - return bookmark_drag_utils::PreferredDropOperation( + return bookmark_utils::PreferredDropOperation( event, DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); } } @@ -1767,8 +1783,7 @@ int BookmarkBarView::PerformDropImpl(const BookmarkDragData& data, return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK; } else { // Dropping a group from different profile. Always accept. - bookmark_drag_utils::CloneDragData(model_, data.elements, parent_node, - index); + bookmark_utils::CloneDragData(model_, data.elements, parent_node, index); return DragDropTypes::DRAG_COPY; } } diff --git a/chrome/browser/views/bookmark_bar_view.h b/chrome/browser/views/bookmark_bar_view.h index 242a7f2..754d6b6 100644 --- a/chrome/browser/views/bookmark_bar_view.h +++ b/chrome/browser/views/bookmark_bar_view.h @@ -72,6 +72,10 @@ class BookmarkBarView : public views::View, explicit BookmarkBarView(Profile* profile, Browser* browser); virtual ~BookmarkBarView(); + // Toggles whether the bookmark bar is shown only on the new tab page or on + // all tabs. + static void ToggleWhenVisible(Profile* profile); + static void RegisterUserPrefs(PrefService* prefs); // Resets the profile. This removes any buttons for the current profile and @@ -81,6 +85,9 @@ class BookmarkBarView : public views::View, // Returns the current profile. Profile* GetProfile() { return profile_; } + // Returns the current browser. + Browser* browser() const { return browser_; } + // Sets the PageNavigator that is used when the user selects an entry on // the bookmark bar. void SetPageNavigator(PageNavigator* navigator); @@ -122,10 +129,6 @@ class BookmarkBarView : public views::View, // Returns the model. BookmarkModel* GetModel() { return model_; } - // Toggles whether the bookmark bar is shown only on the new tab page or on - // all tabs. - void ToggleWhenVisible(); - // Returns true if the bookmarks bar preference is set to 'always show', we // use this as a shorthand way of knowing what style of bar to draw (if the // pref is set to false but we're painting, then we must be on the new tab diff --git a/chrome/browser/views/bookmark_bubble_view.cc b/chrome/browser/views/bookmark_bubble_view.cc index 103df07..9136014 100644 --- a/chrome/browser/views/bookmark_bubble_view.cc +++ b/chrome/browser/views/bookmark_bubble_view.cc @@ -358,7 +358,8 @@ void BookmarkBubbleView::ShowEditor() { Close(); if (node) - BookmarkEditorView::Show(parent, profile_, NULL, node); + BookmarkEditorView::Show(parent, profile_, NULL, node, + BookmarkEditorView::SHOW_TREE); } void BookmarkBubbleView::SetNodeTitleFromTextField() { diff --git a/chrome/browser/views/bookmark_editor_view.cc b/chrome/browser/views/bookmark_editor_view.cc index 8eb7979..1179037 100644 --- a/chrome/browser/views/bookmark_editor_view.cc +++ b/chrome/browser/views/bookmark_editor_view.cc @@ -44,27 +44,34 @@ static const int kNewGroupButtonID = 1002; void BookmarkEditorView::Show(HWND parent_hwnd, Profile* profile, BookmarkNode* parent, - BookmarkNode* node) { + BookmarkNode* node, + Configuration configuration) { DCHECK(profile); - BookmarkEditorView* editor = new BookmarkEditorView(profile, parent, node); + BookmarkEditorView* editor = + new BookmarkEditorView(profile, parent, node, configuration); editor->Show(parent_hwnd); } BookmarkEditorView::BookmarkEditorView(Profile* profile, BookmarkNode* parent, - BookmarkNode* node) + BookmarkNode* node, + Configuration configuration) : profile_(profile), -#pragma warning(suppress: 4355) // Okay to pass "this" here. - new_group_button_( - l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_BUTTON)), + tree_view_(NULL), + new_group_button_(NULL), parent_(parent), node_(node), - running_menu_for_root_(false) { + running_menu_for_root_(false), + show_tree_(configuration == SHOW_TREE) { DCHECK(profile); Init(); } BookmarkEditorView::~BookmarkEditorView() { + // The tree model is deleted before the view. Reset the model otherwise the + // tree will reference a deleted model. + if (tree_view_) + tree_view_->SetModel(NULL); bb_model_->RemoveObserver(this); } @@ -96,7 +103,7 @@ bool BookmarkEditorView::Accept() { } bool BookmarkEditorView::AreAcceleratorsEnabled(DialogButton button) { - return !tree_view_.GetEditingNode(); + return !show_tree_ || !tree_view_->GetEditingNode(); } views::View* BookmarkEditorView::GetContentsView() { @@ -107,16 +114,23 @@ void BookmarkEditorView::Layout() { // Let the grid layout manager lay out most of the dialog... GetLayoutManager()->Layout(this); + if (!show_tree_) + return; + // Manually lay out the New Folder button in the same row as the OK/Cancel // buttons... gfx::Rect parent_bounds = GetParent()->GetLocalBounds(false); - gfx::Size prefsize = new_group_button_.GetPreferredSize(); - int button_y = parent_bounds.bottom() - prefsize.height() - kButtonVEdgeMargin; - new_group_button_.SetBounds(kPanelHorizMargin, button_y, prefsize.width(), + gfx::Size prefsize = new_group_button_->GetPreferredSize(); + int button_y = + parent_bounds.bottom() - prefsize.height() - kButtonVEdgeMargin; + new_group_button_->SetBounds(kPanelHorizMargin, button_y, prefsize.width(), prefsize.height()); } gfx::Size BookmarkEditorView::GetPreferredSize() { + if (!show_tree_) + return views::View::GetPreferredSize(); + return gfx::Size(views::Window::GetLocalizedContentsSize( IDS_EDITBOOKMARK_DIALOG_WIDTH_CHARS, IDS_EDITBOOKMARK_DIALOG_HEIGHT_LINES)); @@ -125,12 +139,12 @@ gfx::Size BookmarkEditorView::GetPreferredSize() { void BookmarkEditorView::ViewHierarchyChanged(bool is_add, views::View* parent, views::View* child) { - if (child == this) { + if (show_tree_ && child == this) { // Add and remove the New Folder button from the ClientView's hierarchy. if (is_add) { - parent->AddChildView(&new_group_button_); + parent->AddChildView(new_group_button_.get()); } else { - parent->RemoveChildView(&new_group_button_); + parent->RemoveChildView(new_group_button_.get()); } } } @@ -164,9 +178,9 @@ void BookmarkEditorView::ButtonPressed(NativeButton* sender) { } void BookmarkEditorView::ExecuteCommand(int id) { - DCHECK(tree_view_.GetSelectedNode()); + DCHECK(tree_view_->GetSelectedNode()); if (id == IDS_EDIT) { - tree_view_.StartEditing(tree_view_.GetSelectedNode()); + tree_view_->StartEditing(tree_view_->GetSelectedNode()); } else { DCHECK(id == IDS_BOOMARK_EDITOR_NEW_FOLDER_MENU_ITEM); NewGroup(); @@ -180,7 +194,7 @@ bool BookmarkEditorView::IsCommandEnabled(int id) const { void BookmarkEditorView::Show(HWND parent_hwnd) { views::Window::CreateChromeWindow(parent_hwnd, gfx::Rect(), this); UserInputChanged(); - if (bb_model_->IsLoaded()) + if (show_tree_ && bb_model_->IsLoaded()) ExpandAndSelect(); window()->Show(); // Select all the text in the name textfield. @@ -198,11 +212,11 @@ void BookmarkEditorView::ShowContextMenu(View* source, int x, int y, bool is_mouse_gesture) { - DCHECK(source == &tree_view_); - if (!tree_view_.GetSelectedNode()) + DCHECK(source == tree_view_); + if (!tree_view_->GetSelectedNode()) return; running_menu_for_root_ = - (tree_model_->GetParent(tree_view_.GetSelectedNode()) == + (tree_model_->GetParent(tree_view_->GetSelectedNode()) == tree_model_->GetRoot()); context_menu_.reset(new Menu(this, Menu::TOPLEFT, GetContainer()->GetHWND())); @@ -215,28 +229,32 @@ void BookmarkEditorView::ShowContextMenu(View* source, } void BookmarkEditorView::Init() { - tree_view_.SetContextMenuController(this); bb_model_ = profile_->GetBookmarkModel(); DCHECK(bb_model_); bb_model_->AddObserver(this); - tree_view_.SetRootShown(false); - // Tell View not to delete all Views declared by value. - tree_view_.SetParentOwned(false); - new_group_button_.SetParentOwned(false); url_tf_.SetParentOwned(false); title_tf_.SetParentOwned(false); - new_group_button_.SetEnabled(false); - new_group_button_.SetListener(this); - new_group_button_.SetID(kNewGroupButtonID); - title_tf_.SetText(node_ ? node_->GetTitle() : std::wstring()); title_tf_.SetController(this); url_tf_.SetText(node_ ? UTF8ToWide(node_->GetURL().spec()) : std::wstring()); url_tf_.SetController(this); + if (show_tree_) { + tree_view_ = new views::TreeView(); + new_group_button_.reset(new views::NativeButton( + l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_BUTTON))); + new_group_button_->SetParentOwned(false); + tree_view_->SetContextMenuController(this); + + tree_view_->SetRootShown(false); + new_group_button_->SetEnabled(false); + new_group_button_->SetListener(this); + new_group_button_->SetID(kNewGroupButtonID); + } + // Yummy layout code. const int labels_column_set_id = 0; const int single_column_view_set_id = 1; @@ -277,14 +295,15 @@ void BookmarkEditorView::Init() { new Label(l10n_util::GetString(IDS_BOOMARK_EDITOR_URL_LABEL))); layout->AddView(&url_tf_); - layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); - - layout->StartRow(1, single_column_view_set_id); - layout->AddView(&tree_view_); + if (show_tree_) { + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(1, single_column_view_set_id); + layout->AddView(tree_view_); + } layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); - if (bb_model_->IsLoaded()) + if (!show_tree_ || bb_model_->IsLoaded()) Reset(); } @@ -304,8 +323,10 @@ void BookmarkEditorView::BookmarkNodeAdded(BookmarkModel* model, void BookmarkEditorView::BookmarkNodeRemoved(BookmarkModel* model, BookmarkNode* parent, - int index) { - if ((node_ && !node_->GetParent()) || (parent_ && !parent_->GetParent())) { + int index, + BookmarkNode* node) { + if ((node_ && node_->HasAncestor(node)) || + (parent_ && parent_->HasAncestor(node))) { // The node, or its parent was removed. Close the dialog. window()->Close(); } else { @@ -314,25 +335,30 @@ void BookmarkEditorView::BookmarkNodeRemoved(BookmarkModel* model, } void BookmarkEditorView::Reset() { + if (!show_tree_) { + if (GetParent()) + UserInputChanged(); + return; + } + + new_group_button_->SetEnabled(true); + // Do this first, otherwise when we invoke SetModel with the real one // tree_view will try to invoke something on the model we just deleted. - tree_view_.SetModel(NULL); + tree_view_->SetModel(NULL); EditorNode* root_node = CreateRootNode(); tree_model_.reset(new EditorTreeModel(root_node)); - tree_view_.SetModel(tree_model_.get()); - tree_view_.SetController(this); - - new_group_button_.SetEnabled(true); + tree_view_->SetModel(tree_model_.get()); + tree_view_->SetController(this); context_menu_.reset(); if (GetParent()) { ExpandAndSelect(); - UserInputChanged(); } else if (GetParent()) { - tree_view_.ExpandAll(); + tree_view_->ExpandAll(); } } GURL BookmarkEditorView::GetInputURL() const { @@ -356,13 +382,13 @@ void BookmarkEditorView::UserInputChanged() { void BookmarkEditorView::NewGroup() { // Create a new entry parented to the selected item, or the bookmark // bar if nothing is selected. - EditorNode* parent = tree_model_->AsNode(tree_view_.GetSelectedNode()); + EditorNode* parent = tree_model_->AsNode(tree_view_->GetSelectedNode()); if (!parent) { NOTREACHED(); return; } - tree_view_.StartEditing(AddNewGroup(parent)); + tree_view_->StartEditing(AddNewGroup(parent)); } BookmarkEditorView::EditorNode* BookmarkEditorView::AddNewGroup( @@ -376,7 +402,7 @@ BookmarkEditorView::EditorNode* BookmarkEditorView::AddNewGroup( } void BookmarkEditorView::ExpandAndSelect() { - tree_view_.ExpandAll(); + tree_view_->ExpandAll(); BookmarkNode* to_select = node_ ? node_->GetParent() : parent_; int group_id_to_select = to_select->id(); @@ -386,7 +412,7 @@ void BookmarkEditorView::ExpandAndSelect() { if (!b_node) b_node = tree_model_->GetRoot()->GetChild(0); // Bookmark bar node. - tree_view_.SetSelectedNode(b_node); + tree_view_->SetSelectedNode(b_node); } BookmarkEditorView::EditorNode* BookmarkEditorView::CreateRootNode() { @@ -429,15 +455,17 @@ BookmarkEditorView::EditorNode* BookmarkEditorView::FindNodeWithID( void BookmarkEditorView::ApplyEdits() { DCHECK(bb_model_->IsLoaded()); - if (!tree_view_.GetSelectedNode()) { + EditorNode* parent = show_tree_ ? + tree_model_->AsNode(tree_view_->GetSelectedNode()) : NULL; + if (show_tree_ && !parent) { NOTREACHED(); return; } - ApplyEdits(tree_model_->AsNode(tree_view_.GetSelectedNode())); + ApplyEdits(parent); } void BookmarkEditorView::ApplyEdits(EditorNode* parent) { - DCHECK(parent); + DCHECK(!show_tree_ || parent); // We're going to apply edits to the bookmark bar model, which will call us // back. Normally when a structural edit occurs we reset the tree model. @@ -450,6 +478,26 @@ void BookmarkEditorView::ApplyEdits(EditorNode* parent) { BookmarkNode* old_parent = node_ ? node_->GetParent() : NULL; const int old_index = old_parent ? old_parent->IndexOfChild(node_) : -1; + if (!show_tree_ ) { + if (!node_) { + bb_model_->AddURL(parent_, parent_->GetChildCount(), new_title, new_url); + return; + } + // If we're not showing the tree we only need to modify the node. + if (old_index == -1) { + NOTREACHED(); + return; + } + if (new_url != node_->GetURL()) { + bb_model_->AddURLWithCreationTime(old_parent, old_index, new_title, + new_url, node_->date_added()); + bb_model_->Remove(old_parent, old_index + 1); + } else { + bb_model_->SetTitle(node_, new_title); + } + return; + } + // Create the new groups and update the titles. BookmarkNode* new_parent = NULL; ApplyNameChangesAndCreateNewGroups( @@ -523,4 +571,3 @@ void BookmarkEditorView::ApplyNameChangesAndCreateNewGroups( parent_b_node, parent_bb_node); } } - diff --git a/chrome/browser/views/bookmark_editor_view.h b/chrome/browser/views/bookmark_editor_view.h index 796f041..4e6e317 100644 --- a/chrome/browser/views/bookmark_editor_view.h +++ b/chrome/browser/views/bookmark_editor_view.h @@ -47,17 +47,28 @@ class BookmarkEditorView : public views::View, FRIEND_TEST(BookmarkEditorViewTest, ModelsMatch); FRIEND_TEST(BookmarkEditorViewTest, MoveToNewParent); FRIEND_TEST(BookmarkEditorViewTest, NewURL); + FRIEND_TEST(BookmarkEditorViewTest, ChangeURLNoTree); + FRIEND_TEST(BookmarkEditorViewTest, ChangeTitleNoTree); public: + // An enumeration of the possible configurations offered. + enum Configuration { + SHOW_TREE, + NO_TREE + }; + // Shows the BookmarkEditorView editing |node|. If |node| is NULL a new entry - // is created initially parented to |parent|. + // is created initially parented to |parent|. If |show_tree| is false the + // tree is not shown. static void Show(HWND parent_window, Profile* profile, BookmarkNode* parent, - BookmarkNode* node); + BookmarkNode* node, + Configuration configuration); BookmarkEditorView(Profile* profile, BookmarkNode* parent, - BookmarkNode* node); + BookmarkNode* node, + Configuration configuration); virtual ~BookmarkEditorView(); @@ -148,7 +159,8 @@ class BookmarkEditorView : public views::View, int index); virtual void BookmarkNodeRemoved(BookmarkModel* model, BookmarkNode* parent, - int index); + int index, + BookmarkNode* node); virtual void BookmarkNodeChanged(BookmarkModel* model, BookmarkNode* node) {} virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, @@ -222,10 +234,10 @@ class BookmarkEditorView : public views::View, scoped_ptr<EditorTreeModel> tree_model_; // Displays star groups. - views::TreeView tree_view_; + views::TreeView* tree_view_; // Used to create a new group. - views::NativeButton new_group_button_; + scoped_ptr<views::NativeButton> new_group_button_; // Used for editing the URL. views::TextField url_tf_; @@ -249,6 +261,9 @@ class BookmarkEditorView : public views::View, // nodes. bool running_menu_for_root_; + // Is the tree shown? + bool show_tree_; + DISALLOW_COPY_AND_ASSIGN(BookmarkEditorView); }; diff --git a/chrome/browser/views/bookmark_editor_view_unittest.cc b/chrome/browser/views/bookmark_editor_view_unittest.cc index 0a5ed68..24fb05e 100644 --- a/chrome/browser/views/bookmark_editor_view_unittest.cc +++ b/chrome/browser/views/bookmark_editor_view_unittest.cc @@ -79,7 +79,8 @@ class BookmarkEditorViewTest : public testing::Test { // Makes sure the tree model matches that of the bookmark bar model. TEST_F(BookmarkEditorViewTest, ModelsMatch) { - BookmarkEditorView editor(profile_.get(), NULL, NULL); + BookmarkEditorView editor(profile_.get(), NULL, NULL, + BookmarkEditorView::SHOW_TREE); BookmarkEditorView::EditorNode* editor_root = editor.tree_model_->GetRoot(); // The root should have two children, one for the bookmark bar node, // the other for the 'other bookmarks' folder. @@ -103,7 +104,8 @@ TEST_F(BookmarkEditorViewTest, ModelsMatch) { // Changes the title and makes sure parent/visual order doesn't change. TEST_F(BookmarkEditorViewTest, EditTitleKeepsPosition) { - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.title_tf_.SetText(L"new_a"); editor.ApplyEdits(editor.tree_model_->GetRoot()->GetChild(0)); @@ -118,7 +120,8 @@ TEST_F(BookmarkEditorViewTest, EditTitleKeepsPosition) { TEST_F(BookmarkEditorViewTest, EditURLKeepsPosition) { Time node_time = Time::Now() + TimeDelta::FromDays(2); GetNode("a")->date_added_ = node_time; - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "new_a").spec())); @@ -133,7 +136,8 @@ TEST_F(BookmarkEditorViewTest, EditURLKeepsPosition) { // Moves 'a' to be a child of the other node. TEST_F(BookmarkEditorViewTest, ChangeParent) { - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.ApplyEdits(editor.tree_model_->GetRoot()->GetChild(1)); @@ -146,7 +150,8 @@ TEST_F(BookmarkEditorViewTest, ChangeParent) { TEST_F(BookmarkEditorViewTest, ChangeParentAndURL) { Time node_time = Time::Now() + TimeDelta::FromDays(2); GetNode("a")->date_added_ = node_time; - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "new_a").spec())); @@ -160,7 +165,8 @@ TEST_F(BookmarkEditorViewTest, ChangeParentAndURL) { // Creates a new folder and moves a node to it. TEST_F(BookmarkEditorViewTest, MoveToNewParent) { - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); // Create two nodes: "F21" as a child of "F2" and "F211" as a child of "F21". BookmarkEditorView::EditorNode* f2 = @@ -191,7 +197,8 @@ TEST_F(BookmarkEditorViewTest, MoveToNewParent) { // Brings up the editor, creating a new URL on the bookmark bar. TEST_F(BookmarkEditorViewTest, NewURL) { - BookmarkEditorView editor(profile_.get(), NULL, NULL); + BookmarkEditorView editor(profile_.get(), NULL, NULL, + BookmarkEditorView::SHOW_TREE); editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "a").spec())); editor.title_tf_.SetText(L"new_a"); @@ -206,3 +213,41 @@ TEST_F(BookmarkEditorViewTest, NewURL) { EXPECT_EQ(L"new_a", new_node->GetTitle()); EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL()); } + +// Brings up the editor with no tree and modifies the url. +TEST_F(BookmarkEditorViewTest, ChangeURLNoTree) { + BookmarkEditorView editor(profile_.get(), NULL, + model_->other_node()->GetChild(0), + BookmarkEditorView::NO_TREE); + + editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "a").spec())); + editor.title_tf_.SetText(L"new_a"); + + editor.ApplyEdits(NULL); + + BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node(); + ASSERT_EQ(2, other_node->GetChildCount()); + + BookmarkNode* new_node = other_node->GetChild(0); + + EXPECT_EQ(L"new_a", new_node->GetTitle()); + EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL()); +} + +// Brings up the editor with no tree and modifies only the title. +TEST_F(BookmarkEditorViewTest, ChangeTitleNoTree) { + BookmarkEditorView editor(profile_.get(), NULL, + model_->other_node()->GetChild(0), + BookmarkEditorView::NO_TREE); + + editor.title_tf_.SetText(L"new_a"); + + editor.ApplyEdits(NULL); + + BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node(); + ASSERT_EQ(2, other_node->GetChildCount()); + + BookmarkNode* new_node = other_node->GetChild(0); + + EXPECT_EQ(L"new_a", new_node->GetTitle()); +} diff --git a/chrome/browser/views/bookmark_folder_tree_view.cc b/chrome/browser/views/bookmark_folder_tree_view.cc new file mode 100644 index 0000000..c068103 --- /dev/null +++ b/chrome/browser/views/bookmark_folder_tree_view.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/bookmark_folder_tree_view.h" + +#include "base/base_drag_source.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_folder_tree_model.h" +#include "chrome/browser/profile.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/views/chrome_menu.h" + +#include "generated_resources.h" + +BookmarkFolderTreeView::BookmarkFolderTreeView(Profile* profile, + BookmarkFolderTreeModel* model) + : views::TreeView(), + profile_(profile), + is_dragging_(false) { + SetModel(model); + SetEditable(false); + SetRootShown(false); + set_drag_enabled(true); +} + +bool BookmarkFolderTreeView::CanDrop(const OSExchangeData& data) { + if (!profile_->GetBookmarkModel()->IsLoaded()) + return false; + + drop_info_.reset(new DropInfo()); + if (!drop_info_->drag_data.Read(data)) + return false; + + // See if there are any urls being dropped. + for (size_t i = 0; i < drop_info_->drag_data.size(); ++i) { + if (drop_info_->drag_data.elements[0].is_url) { + drop_info_->only_folders = false; + break; + } + } + + return true; +} + +void BookmarkFolderTreeView::OnDragEntered( + const views::DropTargetEvent& event) { +} + +int BookmarkFolderTreeView::OnDragUpdated(const views::DropTargetEvent& event) { + int drop_index; + bool drop_on; + FolderNode* drop_parent = + CalculateDropParent(event.y(), drop_info_->only_folders, &drop_index, + &drop_on); + drop_info_->drop_operation = + CalculateDropOperation(event, drop_parent, drop_index, drop_on); + + if (drop_info_->drop_operation == DragDropTypes::DRAG_NONE) { + drop_parent = NULL; + drop_index = -1; + drop_on = false; + } + + SetDropParent(drop_parent, drop_index, drop_on); + + return drop_info_->drop_operation; +} + +void BookmarkFolderTreeView::OnDragExited() { + SetDropParent(NULL, -1, false); + + drop_info_.reset(); +} + +int BookmarkFolderTreeView::OnPerformDrop(const views::DropTargetEvent& event) { + OnPerformDropImpl(); + + int drop_operation = drop_info_->drop_operation; + SetDropParent(NULL, -1, false); + drop_info_.reset(); + return drop_operation; +} + +BookmarkNode* BookmarkFolderTreeView::GetSelectedBookmarkNode() { + views::TreeModelNode* selected_node = GetSelectedNode(); + if (!selected_node) + return NULL; + return TreeNodeAsBookmarkNode(folder_model()->AsNode(selected_node)); +} + +LRESULT BookmarkFolderTreeView::OnNotify(int w_param, LPNMHDR l_param) { + switch (l_param->code) { + case TVN_BEGINDRAG: { + HTREEITEM tree_item = + reinterpret_cast<LPNMTREEVIEW>(l_param)->itemNew.hItem; + FolderNode* folder_node = + folder_model()->AsNode(GetNodeForTreeItem(tree_item)); + BeginDrag(TreeNodeAsBookmarkNode(folder_node)); + return 0; // Return value ignored. + } + } + + return TreeView::OnNotify(w_param, l_param); +} + +void BookmarkFolderTreeView::BeginDrag(BookmarkNode* node) { + BookmarkModel* model = profile_->GetBookmarkModel(); + // Only allow the drag if the user has selected a node of type bookmark and it + // isn't the bookmark bar or other bookmarks folders. + if (!node || node == model->other_node() || + node == model->GetBookmarkBarNode()) { + return; + } + + std::vector<BookmarkNode*> nodes_to_drag; + nodes_to_drag.push_back(node); + + scoped_refptr<OSExchangeData> data = new OSExchangeData; + BookmarkDragData(nodes_to_drag).Write(profile_, data); + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + DWORD effects; + is_dragging_ = true; + DoDragDrop(data, drag_source, + DROPEFFECT_LINK | DROPEFFECT_COPY | DROPEFFECT_MOVE, &effects); + is_dragging_ = false; +} + +FolderNode* BookmarkFolderTreeView::CalculateDropParent(int y, + bool only_folders, + int* drop_index, + bool* drop_on) { + *drop_on = false; + *drop_index = -1; + HWND hwnd = GetNativeControlHWND(); + HTREEITEM item = TreeView_GetFirstVisible(hwnd); + while (item) { + RECT bounds; + TreeView_GetItemRect(hwnd, item, &bounds, TRUE); + if (y < bounds.bottom) { + views::TreeModelNode* model_node = GetNodeForTreeItem(item); + if (folder_model()->GetNodeType(model_node) != + BookmarkFolderTreeModel::BOOKMARK) { + // Only allow drops on bookmark nodes. + return NULL; + } + + FolderNode* node = folder_model()->AsNode(model_node); + if (!only_folders || !node->GetParent() || + !node->GetParent()->GetParent()) { + // If some of the elements being dropped are urls, then we only allow + // dropping on a folder. Similarly you can't drop between the + // bookmark bar and other folder nodes. + *drop_on = true; + *drop_index = node->GetChildCount(); + return node; + } + + // Drop contains all folders, allow them to be dropped between + // folders. + if (y < bounds.top + views::MenuItemView::kDropBetweenPixels) { + *drop_index = node->GetParent()->IndexOfChild(node); + return node->GetParent(); + } + if (y >= bounds.bottom - views::MenuItemView::kDropBetweenPixels) { + if (IsExpanded(node) && folder_model()->GetChildCount(node) > 0) { + // The node is expanded and has children, treat the drop as occurring + // as the first child. This is done to avoid the selection highlight + // dancing around when dragging over expanded folders. Without this + // the highlight jumps past the last expanded child of node. + *drop_index = 0; + return node; + } + *drop_index = node->GetParent()->IndexOfChild(node) + 1; + return node->GetParent(); + } + *drop_on = true; + *drop_index = node->GetChildCount(); + return node; + } + item = TreeView_GetNextVisible(hwnd, item); + } + return NULL; +} + +int BookmarkFolderTreeView::CalculateDropOperation( + const views::DropTargetEvent& event, + FolderNode* drop_parent, + int drop_index, + bool drop_on) { + if (!drop_parent) + return DragDropTypes::DRAG_NONE; + + if (drop_info_->drag_data.IsFromProfile(profile_)) { + int bookmark_model_drop_index = + FolderIndexToBookmarkIndex(drop_parent, drop_index, drop_on); + if (!bookmark_utils::IsValidDropLocation( + profile_, drop_info_->drag_data, + TreeNodeAsBookmarkNode(drop_parent), + bookmark_model_drop_index)) { + return DragDropTypes::DRAG_NONE; + } + + // Data from the same profile. Prefer move, but do copy if the user wants + // that. + if (event.IsControlDown()) + return DragDropTypes::DRAG_COPY; + + return DragDropTypes::DRAG_MOVE; + } + // We're going to copy, but return an operation compatible with the source + // operations so that the user can drop. + return bookmark_utils::PreferredDropOperation( + event, DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); +} + +void BookmarkFolderTreeView::OnPerformDropImpl() { + BookmarkNode* parent_node = TreeNodeAsBookmarkNode(drop_info_->drop_parent); + int drop_index = FolderIndexToBookmarkIndex( + drop_info_->drop_parent, drop_info_->drop_index, drop_info_->drop_on); + BookmarkModel* model = profile_->GetBookmarkModel(); + // If the data is not from this profile we return an operation compatible + // with the source. As such, we need to need to check the data here too. + if (!drop_info_->drag_data.IsFromProfile(profile_) || + drop_info_->drop_operation == DragDropTypes::DRAG_COPY) { + bookmark_utils::CloneDragData(model, drop_info_->drag_data.elements, + parent_node, drop_index); + return; + } + + // else, move. + std::vector<BookmarkNode*> nodes = drop_info_->drag_data.GetNodes(profile_); + if (nodes.empty()) + return; + + for (size_t i = 0; i < nodes.size(); ++i) { + model->Move(nodes[i], parent_node, drop_index); + // Reset the drop_index, just in case the index didn't really change. + drop_index = parent_node->IndexOfChild(nodes[i]) + 1; + if (nodes[i]->is_folder()) { + Expand(folder_model()->GetFolderNodeForBookmarkNode(nodes[i])); + } + } + + if (is_dragging_ && nodes[0]->is_folder()) { + // We're the drag source. Select the node that was moved. + SetSelectedNode(folder_model()->GetFolderNodeForBookmarkNode(nodes[0])); + } +} + +void BookmarkFolderTreeView::SetDropParent(FolderNode* node, + int drop_index, + bool drop_on) { + if (drop_info_->drop_parent == node && + drop_info_->drop_index == drop_index && + drop_info_->drop_on == drop_on) { + return; + } + // Remove the indicator over the previous location. + if (drop_info_->drop_on) { + HTREEITEM item = GetTreeItemForNode(drop_info_->drop_parent); + if (item) + TreeView_SetItemState(GetNativeControlHWND(), item, 0, TVIS_DROPHILITED); + } else if (drop_info_->drop_index != -1) { + TreeView_SetInsertMark(GetNativeControlHWND(), NULL, FALSE); + } + + drop_info_->drop_parent = node; + drop_info_->drop_index = drop_index; + drop_info_->drop_on = drop_on; + + // And show the new indicator. + if (drop_info_->drop_on) { + HTREEITEM item = GetTreeItemForNode(drop_info_->drop_parent); + if (item) { + TreeView_SetItemState(GetNativeControlHWND(), item, TVIS_DROPHILITED, + TVIS_DROPHILITED); + } + } else if (drop_info_->drop_index != -1) { + BOOL after = FALSE; + if (folder_model()->GetChildCount(drop_info_->drop_parent) == + drop_info_->drop_index) { + after = TRUE; + node = + folder_model()->GetChild(drop_info_->drop_parent, + drop_info_->drop_index - 1); + } else { + node = + folder_model()->GetChild(drop_info_->drop_parent, + drop_info_->drop_index); + } + HTREEITEM item = GetTreeItemForNode(node); + if (item) + TreeView_SetInsertMark(GetNativeControlHWND(), item, after); + } +} + +BookmarkFolderTreeModel* BookmarkFolderTreeView::folder_model() const { + return static_cast<BookmarkFolderTreeModel*>(model()); +} + +BookmarkNode* BookmarkFolderTreeView::TreeNodeAsBookmarkNode(FolderNode* node) { + return folder_model()->TreeNodeAsBookmarkNode(node); +} + +int BookmarkFolderTreeView::FolderIndexToBookmarkIndex(FolderNode* node, + int index, + bool drop_on) { + BookmarkNode* parent_node = TreeNodeAsBookmarkNode(node); + if (drop_on || index == node->GetChildCount()) + return parent_node->GetChildCount(); + + if (index != 0) { + return parent_node->IndexOfChild( + TreeNodeAsBookmarkNode(node->GetChild(index))); + } + + return 0; +} diff --git a/chrome/browser/views/bookmark_folder_tree_view.h b/chrome/browser/views/bookmark_folder_tree_view.h new file mode 100644 index 0000000..ae12b2c --- /dev/null +++ b/chrome/browser/views/bookmark_folder_tree_view.h @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_FOLDER_TREE_VIEW_H_ +#define CHROME_BROWSER_VIEWS_BOOKMARK_FOLDER_TREE_VIEW_H_ + +#include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "chrome/browser/bookmarks/bookmark_folder_tree_model.h" +#include "chrome/views/tree_view.h" + +class BookmarkModel; +class BookmarkNode; +class OSExchangeData; +class Profile; + +// BookmarkFolderTreeView is used to show the contents of a +// BookmarkFolderTreeModel and provides drag and drop support. +class BookmarkFolderTreeView : public views::TreeView { + public: + BookmarkFolderTreeView(Profile* profile, BookmarkFolderTreeModel* model); + + // Drag and drop methods. + virtual bool CanDrop(const OSExchangeData& data); + virtual void OnDragEntered(const views::DropTargetEvent& event); + virtual int OnDragUpdated(const views::DropTargetEvent& event); + virtual void OnDragExited(); + virtual int OnPerformDrop(const views::DropTargetEvent& event); + + // Returns the selected node as a BookmarkNode. This returns NULL if the + // selected node is not of type BookmarkFolderTreeModel::BOOKMARK or + // nothing is selected. + BookmarkNode* GetSelectedBookmarkNode(); + + protected: + // Overriden to start a drag. + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + private: + // Provides information used during a drop. + struct DropInfo { + DropInfo() + : drop_parent(NULL), + only_folders(true), + drop_index(-1), + drop_operation(0), + drop_on(false) {} + + // Parent the mouse is over. + FolderNode* drop_parent; + + // Drag data. + BookmarkDragData drag_data; + + // Does drag_data consists of folders only. + bool only_folders; + + // If drop_on is false, this is the index to add the child. + // WARNING: this index is in terms of the BookmarkFolderTreeModel, which is + // not the same as the BookmarkModel. + int drop_index; + + // Operation for the drop. + int drop_operation; + + // Is the user dropping on drop_parent? If false, the mouse is positioned + // such that the drop should insert the data at position drop_index in + // drop_parent. + bool drop_on; + }; + + // Starts a drag operation for the specified node. + void BeginDrag(BookmarkNode* node); + + // Calculates the drop parent. Returns NULL if not over a valid drop + // location. See DropInfos documentation for a description of |drop_index| + // and |drop_on|. + FolderNode* CalculateDropParent(int y, + bool only_folders, + int* drop_index, + bool* drop_on); + + // Determines the appropriate drop operation. This returns DRAG_NONE + // if the location is not valid. + int CalculateDropOperation(const views::DropTargetEvent& event, + FolderNode* drop_parent, + int drop_index, + bool drop_on); + + // Performs the drop operation. + void OnPerformDropImpl(); + + // Sets the parent of the drop operation. + void SetDropParent(FolderNode* node, int drop_index, bool drop_on); + + // Returns the model as a BookmarkFolderTreeModel. + BookmarkFolderTreeModel* folder_model() const; + + // Converts FolderNode into a BookmarkNode. + BookmarkNode* TreeNodeAsBookmarkNode(FolderNode* node); + + // Converts an index in terms of the BookmarkFolderTreeModel to an index + // in terms of the BookmarkModel. + int FolderIndexToBookmarkIndex(FolderNode* node, int index, bool drop_on); + + Profile* profile_; + + // Non-null during a drop. + scoped_ptr<DropInfo> drop_info_; + + // Did we originate the drag? + bool is_dragging_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkFolderTreeView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_FOLDER_TREE_VIEW_H_ diff --git a/chrome/browser/views/bookmark_manager_view.cc b/chrome/browser/views/bookmark_manager_view.cc new file mode 100644 index 0000000..050323a --- /dev/null +++ b/chrome/browser/views/bookmark_manager_view.cc @@ -0,0 +1,383 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/bookmark_manager_view.h" + +#include <algorithm> + +#include "base/gfx/skia_utils.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/bookmarks/bookmark_context_menu.h" +#include "chrome/browser/bookmarks/bookmark_folder_tree_model.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_table_model.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/views/bookmark_editor_view.h" +#include "chrome/browser/views/bookmark_folder_tree_view.h" +#include "chrome/browser/views/bookmark_table_view.h" +#include "chrome/browser/views/standard_layout.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/color_utils.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/container_win.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/single_split_view.h" +#include "chrome/views/window.h" + +#include "generated_resources.h" + +// If non-null, there is an open editor and this is the window it is contained +// in it. +static views::Window* open_window = NULL; +// And this is the manager contained in it. +static BookmarkManagerView* manager = NULL; + +// Delay, in ms, between when the user types and when we run the search. +static const int kSearchDelayMS = 200; + +BookmarkManagerView::BookmarkManagerView(Profile* profile) + : profile_(profile->GetOriginalProfile()), + table_view_(NULL), + tree_view_(NULL), + search_factory_(this) { + search_tf_ = new views::TextField(); + search_tf_->set_default_width_in_chars(40); + + table_view_ = new BookmarkTableView(profile_, NULL); + table_view_->SetObserver(this); + table_view_->SetContextMenuController(this); + + tree_view_ = new BookmarkFolderTreeView(profile_, NULL); + tree_view_->SetController(this); + tree_view_->SetContextMenuController(this); + + views::SingleSplitView* split_view = + new views::SingleSplitView(tree_view_, table_view_); + + views::GridLayout* layout = new views::GridLayout(this); + SetLayoutManager(layout); + const int search_cs_id = 1; + const int split_cs_id = 2; + layout->SetInsets(kPanelVertMargin, 0, 0, 0); + views::ColumnSet* column_set = layout->AddColumnSet(search_cs_id); + column_set->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, + 1, views::GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kButtonHEdgeMargin); + + column_set = layout->AddColumnSet(split_cs_id); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, + views::GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, search_cs_id); + layout->AddView(search_tf_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(1, split_cs_id); + layout->AddView(split_view); + + BookmarkModel* bookmark_model = profile_->GetBookmarkModel(); + if (!bookmark_model->IsLoaded()) + bookmark_model->AddObserver(this); +} + +BookmarkManagerView::~BookmarkManagerView() { + if (!GetBookmarkModel()->IsLoaded()) { + GetBookmarkModel()->RemoveObserver(this); + } else { + // The models are deleted before the views. Make sure we set the models of + // the views to NULL so that they aren't left holding a reference to a + // deleted model. + table_view_->SetModel(NULL); + tree_view_->SetModel(NULL); + } + manager = NULL; + open_window = NULL; +} + +// static +void BookmarkManagerView::RegisterPrefs(PrefService* prefs) { + prefs->RegisterDictionaryPref(prefs::kBookmarkManagerPlacement); +} + +// static +void BookmarkManagerView::Show(Profile* profile) { + if (!profile->GetBookmarkModel()) + return; + + if (open_window != NULL) { + open_window->MoveToFront(true); + return; + } + + // Both of these will be deleted when the dialog closes. + manager = new BookmarkManagerView(profile); + + // Create the window. + open_window = views::Window::CreateChromeWindow(NULL, gfx::Rect(), manager); + // Let the manager know it's parented. + manager->PrepareForShow(); + // And show it. + open_window->Show(); +} + +// static +BookmarkManagerView* BookmarkManagerView::current() { + return manager; +} + +void BookmarkManagerView::SelectInTree(BookmarkNode* node) { + if (!node) + return; + + BookmarkNode* parent = node->is_url() ? node->GetParent() : node; + FolderNode* folder_node = tree_model_->GetFolderNodeForBookmarkNode(parent); + if (!folder_node) { + NOTREACHED(); + return; + } + + tree_view_->SetSelectedNode(folder_node); + + if (node->is_url()) { + int index = table_model_->IndexOfNode(node); + if (index != -1) + table_view_->Select(index); + } +} + + +std::vector<BookmarkNode*> BookmarkManagerView::GetSelectedTableNodes() { + std::vector<BookmarkNode*> nodes; + for (views::TableView::iterator i = table_view_->SelectionBegin(); + i != table_view_->SelectionEnd(); ++i) { + nodes.push_back(table_model_->GetNodeForRow(*i)); + } + // TableViews iterator iterates in reverse order. Reverse the nodes so they + // are opened in visual order. + std::reverse(nodes.begin(), nodes.end()); + return nodes; +} + +void BookmarkManagerView::PaintBackground(ChromeCanvas* canvas) { + canvas->drawColor(color_utils::GetSysSkColor(COLOR_3DFACE), + SkPorterDuff::kSrc_Mode); +} + +gfx::Size BookmarkManagerView::GetPreferredSize() { + return gfx::Size(views::Window::GetLocalizedContentsSize( + IDS_BOOKMARK_MANAGER_DIALOG_WIDTH_CHARS, + IDS_BOOKMARK_MANAGER_DIALOG_HEIGHT_LINES)); +} + +std::wstring BookmarkManagerView::GetWindowTitle() const { + return l10n_util::GetString(IDS_BOOKMARK_MANAGER_TITLE); +} + +void BookmarkManagerView::SaveWindowPosition(const CRect& bounds, + bool maximized, + bool always_on_top) { + window()->SaveWindowPositionToPrefService(g_browser_process->local_state(), + prefs::kBookmarkManagerPlacement, + bounds, maximized, always_on_top); +} + +bool BookmarkManagerView::RestoreWindowPosition(CRect* bounds, + bool* maximized, + bool* always_on_top) { + return window()->RestoreWindowPositionFromPrefService( + g_browser_process->local_state(), + prefs::kBookmarkManagerPlacement, + bounds, maximized, always_on_top); +} + +void BookmarkManagerView::OnDoubleClick() { + std::vector<BookmarkNode*> nodes = GetSelectedTableNodes(); + if (nodes.empty()) + return; + if (nodes.size() == 1 && nodes[0]->is_folder()) { + // Double click on a folder descends into the folder. + SelectInTree(nodes[0]); + return; + } + // TODO(sky): OnDoubleClick needs a handle to the current mouse event so that + // we can use + // event_utils::DispositionFromEventFlags(sender->mouse_event_flags()) . + bookmark_utils::OpenAll( + GetContainer()->GetHWND(), profile_, NULL, nodes, CURRENT_TAB); +} + +void BookmarkManagerView::OnTableViewDelete(views::TableView* table) { + std::vector<BookmarkNode*> nodes = GetSelectedTableNodes(); + if (nodes.empty()) + return; + for (size_t i = 0; i < nodes.size(); ++i) { + GetBookmarkModel()->Remove(nodes[i]->GetParent(), + nodes[i]->GetParent()->IndexOfChild(nodes[i])); + } +} + +void BookmarkManagerView::OnTreeViewSelectionChanged( + views::TreeView* tree_view) { + views::TreeModelNode* node = tree_view_->GetSelectedNode(); + + BookmarkTableModel* new_table_model = NULL; + BookmarkNode* table_parent_node = NULL; + + if (node) { + switch (tree_model_->GetNodeType(node)) { + case BookmarkFolderTreeModel::BOOKMARK: + table_parent_node = tree_model_->TreeNodeAsBookmarkNode(node); + new_table_model = + BookmarkTableModel::CreateBookmarkTableModelForFolder( + profile_->GetBookmarkModel(), + table_parent_node); + break; + + case BookmarkFolderTreeModel::RECENTLY_BOOKMARKED: + new_table_model = BookmarkTableModel::CreateRecentlyBookmarkedModel( + profile_->GetBookmarkModel()); + break; + + case BookmarkFolderTreeModel::SEARCH: + search_factory_.RevokeAll(); + new_table_model = CreateSearchTableModel(); + break; + + default: + NOTREACHED(); + break; + } + } + + SetTableModel(new_table_model, table_parent_node); +} + +void BookmarkManagerView::Loaded(BookmarkModel* model) { + model->RemoveObserver(this); + LoadedImpl(); +} + +void BookmarkManagerView::ContentsChanged(views::TextField* sender, + const std::wstring& new_contents) { + search_factory_.RevokeAll(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + search_factory_.NewRunnableMethod(&BookmarkManagerView::PerformSearch), + kSearchDelayMS); +} + +void BookmarkManagerView::HandleKeystroke(views::TextField* sender, + UINT message, TCHAR key, + UINT repeat_count, + UINT flags) { + if (key == VK_RETURN) { + PerformSearch(); + search_tf_->SelectAll(); + } +} + +void BookmarkManagerView::ShowContextMenu(views::View* source, + int x, + int y, + bool is_mouse_gesture) { + if (!GetBookmarkModel()->IsLoaded()) + return; + + if (source == table_view_) { + std::vector<BookmarkNode*> nodes = GetSelectedTableNodes(); + if (nodes.empty()) + return; + + BookmarkNode* parent = tree_view_->GetSelectedBookmarkNode(); + BookmarkContextMenu menu(GetContainer()->GetHWND(), profile_, NULL, NULL, + parent, nodes, + BookmarkContextMenu::BOOKMARK_MANAGER_TABLE); + menu.RunMenuAt(x, y); + } else if (source == tree_view_) { + BookmarkNode* node = tree_view_->GetSelectedBookmarkNode(); + if (!node) + return; + std::vector<BookmarkNode*> nodes; + nodes.push_back(node); + BookmarkContextMenu menu(GetContainer()->GetHWND(), profile_, NULL, NULL, + node, nodes, + BookmarkContextMenu::BOOKMARK_MANAGER_TREE); + menu.RunMenuAt(x, y); + } +} + +BookmarkTableModel* BookmarkManagerView::CreateSearchTableModel() { + std::wstring search_text = search_tf_->GetText(); + if (search_text.empty()) + return NULL; + return BookmarkTableModel::CreateSearchTableModel(GetBookmarkModel(), + search_text); +} + +void BookmarkManagerView::SetTableModel(BookmarkTableModel* new_table_model, + BookmarkNode* parent_node) { + // Be sure and reset the model on the view before updating table_model_. + // Otherwise the view will attempt to use the deleted model when we set the + // new one. + table_view_->SetModel(NULL); + table_view_->SetShowPathColumn(!parent_node); + table_view_->SetModel(new_table_model); + table_view_->set_parent_node(parent_node); + table_model_.reset(new_table_model); +} + +void BookmarkManagerView::PerformSearch() { + search_factory_.RevokeAll(); + // Reset the controller, otherwise when we change the selection we'll get + // notified and update the model twice. + tree_view_->SetController(NULL); + tree_view_->SetSelectedNode(tree_model_->search_node()); + tree_view_->SetController(this); + SetTableModel(CreateSearchTableModel(), NULL); +} + +void BookmarkManagerView::PrepareForShow() { + views::SingleSplitView* split_view = + static_cast<views::SingleSplitView*>(table_view_->GetParent()); + // Give a third of the space to the tree. + split_view->set_divider_x(split_view->width() / 3); + if (!GetBookmarkModel()->IsLoaded()) { + search_tf_->SetReadOnly(true); + return; + } + + LoadedImpl(); +} + +void BookmarkManagerView::LoadedImpl() { + BookmarkModel* bookmark_model = GetBookmarkModel(); + BookmarkNode* bookmark_bar_node = bookmark_model->GetBookmarkBarNode(); + table_model_.reset( + BookmarkTableModel::CreateBookmarkTableModelForFolder(bookmark_model, + bookmark_bar_node)); + table_view_->SetModel(table_model_.get()); + table_view_->set_parent_node(bookmark_bar_node); + + tree_model_.reset(new BookmarkFolderTreeModel(bookmark_model)); + tree_view_->SetModel(tree_model_.get()); + + tree_view_->ExpandAll(); + + tree_view_->SetSelectedNode( + tree_model_->GetFolderNodeForBookmarkNode(bookmark_bar_node)); + + search_tf_->SetReadOnly(false); + search_tf_->SetController(this); + + Layout(); + SchedulePaint(); +} + +BookmarkModel* BookmarkManagerView::GetBookmarkModel() const { + return profile_->GetBookmarkModel(); +} diff --git a/chrome/browser/views/bookmark_manager_view.h b/chrome/browser/views/bookmark_manager_view.h new file mode 100644 index 0000000..c397b33 --- /dev/null +++ b/chrome/browser/views/bookmark_manager_view.h @@ -0,0 +1,160 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_MANAGER_VIEW_H_ +#define CHROME_BROWSER_VIEWS_BOOKMARK_MANAGER_VIEW_H_ + +#include "base/task.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/views/table_view.h" +#include "chrome/views/text_field.h" +#include "chrome/views/tree_view.h" +#include "chrome/views/view.h" +#include "chrome/views/window_delegate.h" +#include "webkit/glue/window_open_disposition.h" + +class BookmarkFolderTreeModel; +class BookmarkFolderTreeView; +class BookmarkTableModel; +class BookmarkTableView; +class PrefService; +class Profile; + +// A view that lets the user manage their bookmarks. The bookmark manager +// shows a tree on the left with a table on the right. The tree shows the +// folder nodes and the table the contents of the selected tree node. The +// tree is a BookmarkFolderTreeView and the table a BookmarkTableView. A +// text field is also provided that allows the user to search the contents +// of the bookmarks. +class BookmarkManagerView : public views::View, + public views::WindowDelegate, + public views::TreeViewController, + public views::TableViewObserver, + public views::TextField::Controller, + public BookmarkModelObserver, + public views::ContextMenuController { + public: + explicit BookmarkManagerView(Profile* profile); + virtual ~BookmarkManagerView(); + + static void RegisterPrefs(PrefService* prefs); + + // Shows the bookmark manager. Only one bookmark manager exists. + static void Show(Profile* profile); + + // Returns the current manager, or NULL if the manager is not showing. + static BookmarkManagerView* current(); + + // Selects the specified node in the tree. If node is a URL it's parent is + // selected and node is selected in the table. + void SelectInTree(BookmarkNode* node); + + // Returns the selection of the table. + std::vector<BookmarkNode*> GetSelectedTableNodes(); + + virtual void PaintBackground(ChromeCanvas* canvas); + + virtual gfx::Size GetPreferredSize(); + + // WindowDelegate. + virtual bool CanResize() const { return true; } + virtual bool CanMaximize() const { return true; } + virtual std::wstring GetWindowTitle() const; + virtual void SaveWindowPosition(const CRect& bounds, + bool maximized, + bool always_on_top); + virtual bool RestoreWindowPosition(CRect* bounds, + bool* maximized, + bool* always_on_top); + virtual View* GetContentsView() { return this; } + // TODO(sky): implement these when we have an icon. + //virtual SkBitmap GetWindowIcon(); + //virtual bool ShouldShowWindowIcon() const { return true; } + + private: + // TableViewObserver methods. + virtual void OnSelectionChanged() {} + // Overriden to open the selected table nodes in the current browser. + virtual void OnDoubleClick(); + virtual void OnTableViewDelete(views::TableView* table); + + // TreeViewController method. + virtual void OnTreeViewSelectionChanged(views::TreeView* tree_view); + + // BookmarkModelObserver. We're only installed as an observer until the + // bookmarks are loaded. + virtual void Loaded(BookmarkModel* model); + virtual void BookmarkModelBeingDeleted(BookmarkModel* model) {} + virtual void BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index) {} + virtual void BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) {} + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index) {} + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int old_index, + BookmarkNode* node) {} + virtual void BookmarkNodeChanged(BookmarkModel* model, + BookmarkNode* node) {} + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + BookmarkNode* node) {} + + // TextField::Controller methods. + // Starts a timer to search for the search text. + virtual void ContentsChanged(views::TextField* sender, + const std::wstring& new_contents); + // If return has been pressed this performs an immediate search. + virtual void HandleKeystroke(views::TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags); + + // ContextMenuController. + virtual void ShowContextMenu(views::View* source, + int x, + int y, + bool is_mouse_gesture); + + // Creates the table model to use when searching. This returns NULL if there + // is no search text. + BookmarkTableModel* CreateSearchTableModel(); + + // Sets the model of the table and its parent node. + void SetTableModel(BookmarkTableModel* new_table_model, + BookmarkNode* parent_node); + + // Sets the table's model to the results of CreateSearchTableModel and selects + // the search node in the tree. + void PerformSearch(); + + // Invoked prior to showing. If the BookmarkModel is loaded this invokes + // LoadedImpl. + void PrepareForShow(); + + // Invoked when we're parented and the BookmarkModel is loaded. Sets the + // models of the tree/table appropriately and expands the necessary nodes. + void LoadedImpl(); + + // Returns the BookmarkModel. + BookmarkModel* GetBookmarkModel() const; + + Profile* profile_; + BookmarkTableView* table_view_; + BookmarkFolderTreeView* tree_view_; + scoped_ptr<BookmarkTableModel> table_model_; + scoped_ptr<BookmarkFolderTreeModel> tree_model_; + views::TextField* search_tf_; + + // Factory used for delaying search. + ScopedRunnableMethodFactory<BookmarkManagerView> search_factory_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkManagerView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_MANAGER_VIEW_H_ diff --git a/chrome/browser/views/bookmark_table_view.cc b/chrome/browser/views/bookmark_table_view.cc new file mode 100644 index 0000000..ffda218 --- /dev/null +++ b/chrome/browser/views/bookmark_table_view.cc @@ -0,0 +1,394 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/bookmark_table_view.h" + +#include "base/base_drag_source.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_table_model.h" +#include "chrome/browser/profile.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/chrome_menu.h" + +#include "generated_resources.h" + +namespace { + +// Height of the drop indicator used when dropping between rows. +const int kDropHighlightHeight = 2; + +int GetWidthOfColumn(const std::vector<views::TableColumn>& columns, + const std::vector<int> widths, + int column_id) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i].id == column_id) + return widths[i]; + } + NOTREACHED(); + return -1; +} + +} // namespace + +BookmarkTableView::BookmarkTableView(Profile* profile, + BookmarkTableModel* model) + : views::TableView(model, std::vector<views::TableColumn>(), + views::ICON_AND_TEXT, false, true, true), + profile_(profile), + show_path_column_(false) { + UpdateColumns(); +} + +// static +void BookmarkTableView::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterIntegerPref(prefs::kBookmarkTableNameWidth1, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTableURLWidth1, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTableNameWidth2, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTableURLWidth2, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTablePathWidth, -1); +} + +bool BookmarkTableView::CanDrop(const OSExchangeData& data) { + if (!parent_node_ || !profile_->GetBookmarkModel()->IsLoaded()) + return false; + + drop_info_.reset(new DropInfo()); + if (!drop_info_->drag_data.Read(data)) + return false; + + // Don't allow the user to drop an ancestor of the parent node onto the + // parent node. This would create a cycle, which is definitely a no-no. + std::vector<BookmarkNode*> nodes = drop_info_->drag_data.GetNodes(profile_); + for (size_t i = 0; i < nodes.size(); ++i) { + if (parent_node_->HasAncestor(nodes[i])) + return false; + } + return true; +} + +void BookmarkTableView::OnDragEntered(const views::DropTargetEvent& event) { +} + +int BookmarkTableView::OnDragUpdated(const views::DropTargetEvent& event) { + if (!parent_node_) + return false; + + LVHITTESTINFO hit_info = {0}; + hit_info.pt.x = event.x(); + hit_info.pt.y = event.y(); + // TODO(sky): need to support auto-scroll and all that good stuff. + + int drop_index; + bool drop_on; + drop_index = CalculateDropIndex(event.y(), &drop_on); + + drop_info_->drop_operation = + CalculateDropOperation(event, drop_index, drop_on); + + if (drop_info_->drop_operation == DragDropTypes::DRAG_NONE) { + drop_index = -1; + drop_on = false; + } + + SetDropIndex(drop_index, drop_on); + + return drop_info_->drop_operation; +} + +void BookmarkTableView::OnDragExited() { + SetDropIndex(-1, false); + drop_info_.reset(); +} + +int BookmarkTableView::OnPerformDrop(const views::DropTargetEvent& event) { + OnPerformDropImpl(); + int drop_operation = drop_info_->drop_operation; + SetDropIndex(-1, false); + drop_info_.reset(); + return drop_operation; +} + +BookmarkTableModel* BookmarkTableView::bookmark_table_model() const { + return static_cast<BookmarkTableModel*>(model()); +} + +void BookmarkTableView::SaveColumnConfiguration() { + PrefService* prefs = profile_->GetPrefs(); + if (!prefs) + return; + + if (show_path_column_) { + prefs->SetInteger(prefs::kBookmarkTableNameWidth2, + GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); + prefs->SetInteger(prefs::kBookmarkTableURLWidth2, + GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); + prefs->SetInteger(prefs::kBookmarkTablePathWidth, + GetColumnWidth(IDS_BOOKMARK_TABLE_PATH)); + } else { + prefs->SetInteger(prefs::kBookmarkTableNameWidth1, + GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); + prefs->SetInteger(prefs::kBookmarkTableURLWidth1, + GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); + } +} + +void BookmarkTableView::SetShowPathColumn(bool show_path_column) { + if (show_path_column == show_path_column_) + return; + + SaveColumnConfiguration(); + + show_path_column_ = show_path_column; + UpdateColumns(); +} + +void BookmarkTableView::PostPaint() { + if (!drop_info_.get() || drop_info_->drop_index == -1 || + drop_info_->drop_on) { + return; + } + + RECT bounds = GetDropBetweenHighlightRect(drop_info_->drop_index); + HDC dc = GetDC(GetNativeControlHWND()); + HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_WINDOWTEXT)); + FillRect(dc, &bounds, brush); + DeleteObject(brush); + ReleaseDC(GetNativeControlHWND(), dc); +} + +LRESULT BookmarkTableView::OnNotify(int w_param, LPNMHDR l_param) { + switch(l_param->code) { + case LVN_BEGINDRAG: + BeginDrag(); + return 0; // Return value doesn't matter for this message. + } + + return TableView::OnNotify(w_param, l_param); +} + +void BookmarkTableView::BeginDrag() { + std::vector<BookmarkNode*> nodes_to_drag; + for (TableView::iterator i = SelectionBegin(); i != SelectionEnd(); ++i) + nodes_to_drag.push_back(bookmark_table_model()->GetNodeForRow(*i)); + if (nodes_to_drag.empty()) + return; // Nothing to drag. + + scoped_refptr<OSExchangeData> data = new OSExchangeData; + BookmarkDragData(nodes_to_drag).Write(profile_, data); + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + DWORD effects; + DoDragDrop(data, drag_source, + DROPEFFECT_LINK | DROPEFFECT_COPY | DROPEFFECT_MOVE, &effects); +} + +int BookmarkTableView::CalculateDropOperation( + const views::DropTargetEvent& event, + int drop_index, + bool drop_on) { + if (drop_info_->drag_data.IsFromProfile(profile_)) { + // Data from the same profile. Prefer move, but do copy if the user wants + // that. + if (event.IsControlDown()) + return DragDropTypes::DRAG_COPY; + + int real_drop_index; + BookmarkNode* drop_parent = GetDropParentAndIndex(drop_index, drop_on, + &real_drop_index); + if (!bookmark_utils::IsValidDropLocation( + profile_, drop_info_->drag_data, drop_parent, real_drop_index)) { + return DragDropTypes::DRAG_NONE; + } + return DragDropTypes::DRAG_MOVE; + } + // We're going to copy, but return an operation compatible with the source + // operations so that the user can drop. + return bookmark_utils::PreferredDropOperation( + event, DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); +} + +void BookmarkTableView::OnPerformDropImpl() { + int drop_index; + BookmarkNode* drop_parent = GetDropParentAndIndex( + drop_info_->drop_index, drop_info_->drop_on, &drop_index); + BookmarkModel* model = profile_->GetBookmarkModel(); + int min_selection; + int max_selection; + // If the data is not from this profile we return an operation compatible + // with the source. As such, we need to need to check the data here too. + if (!drop_info_->drag_data.IsFromProfile(profile_) || + drop_info_->drop_operation == DragDropTypes::DRAG_COPY) { + bookmark_utils::CloneDragData(model, drop_info_->drag_data.elements, + drop_parent, drop_index); + min_selection = drop_index; + max_selection = drop_index + + static_cast<int>(drop_info_->drag_data.elements.size()); + } else { + // else, move. + std::vector<BookmarkNode*> nodes = drop_info_->drag_data.GetNodes(profile_); + if (nodes.empty()) + return; + + for (size_t i = 0; i < nodes.size(); ++i) { + model->Move(nodes[i], drop_parent, drop_index); + // Reset the drop_index, just in case the index didn't really change. + drop_index = drop_parent->IndexOfChild(nodes[i]) + 1; + } + min_selection = drop_parent->IndexOfChild(nodes[0]); + max_selection = min_selection + static_cast<int>(nodes.size()); + } + if (min_selection < RowCount() && max_selection < RowCount()) { + // Select the moved/copied rows. + Select(min_selection); + if (min_selection + 1 < max_selection) { + // SetSelectedState doesn't send notification, so we manually do it. + for (int i = min_selection + 1; i < max_selection; ++i) + SetSelectedState(i, true); + if (observer()) + observer()->OnSelectionChanged(); + } + } +} + +void BookmarkTableView::SetDropIndex(int index, bool drop_on) { + if (drop_info_->drop_index == index && drop_info_->drop_on == drop_on) + return; + + UpdateDropIndex(drop_info_->drop_index, drop_info_->drop_on, false); + + drop_info_->drop_index = index; + drop_info_->drop_on = drop_on; + + UpdateDropIndex(drop_info_->drop_index, drop_info_->drop_on, true); +} + +void BookmarkTableView::UpdateDropIndex(int index, bool drop_on, bool turn_on) { + if (index == -1) + return; + + if (drop_on) { + ListView_SetItemState(GetNativeControlHWND(), index, + turn_on ? LVIS_DROPHILITED : 0, LVIS_DROPHILITED); + } else { + RECT bounds = GetDropBetweenHighlightRect(index); + InvalidateRect(GetNativeControlHWND(), &bounds, FALSE); + } +} + +int BookmarkTableView::CalculateDropIndex(int y, bool* drop_on) { + *drop_on = false; + HWND hwnd = GetNativeControlHWND(); + int row_count = RowCount(); + int top_index = ListView_GetTopIndex(hwnd); + if (row_count == 0 || top_index < 0) + return 0; + + for (int i = top_index; i < row_count; ++i) { + RECT bounds; + ListView_GetItemRect(hwnd, i, &bounds, LVIR_BOUNDS); + if (y < bounds.top) + return i; + if (y < bounds.bottom) { + if (bookmark_table_model()->GetNodeForRow(i)->is_folder()) { + if (y < bounds.top + views::MenuItemView::kDropBetweenPixels) + return i; + if (y >= bounds.bottom - views::MenuItemView::kDropBetweenPixels) + return i + 1; + *drop_on = true; + return i; + } + if (y < (bounds.bottom - bounds.top) / 2 + bounds.top) + return i; + return i + 1; + } + } + return row_count; +} + +BookmarkNode* BookmarkTableView::GetDropParentAndIndex(int visual_drop_index, + bool drop_on, + int* index) { + if (drop_on) { + BookmarkNode* parent = parent_node_->GetChild(visual_drop_index); + *index = parent->GetChildCount(); + return parent; + } + *index = visual_drop_index; + return parent_node_; +} + +RECT BookmarkTableView::GetDropBetweenHighlightRect(int index) { + RECT bounds = { 0 }; + if (RowCount() == 0) { + bounds.top = content_offset(); + bounds.left = 0; + bounds.right = width(); + } else if (index >= RowCount()) { + ListView_GetItemRect(GetNativeControlHWND(), index - 1, &bounds, + LVIR_BOUNDS); + bounds.top = bounds.bottom - kDropHighlightHeight / 2; + } else { + ListView_GetItemRect(GetNativeControlHWND(), index, &bounds, LVIR_BOUNDS); + bounds.top -= kDropHighlightHeight / 2; + } + bounds.bottom = bounds.top + kDropHighlightHeight; + return bounds; +} +void BookmarkTableView::UpdateColumns() { + PrefService* prefs = profile_->GetPrefs(); + views::TableColumn name_column = + views::TableColumn(IDS_BOOKMARK_TABLE_TITLE, views::TableColumn::LEFT, + -1); + views::TableColumn url_column = + views::TableColumn(IDS_BOOKMARK_TABLE_URL, views::TableColumn::LEFT, -1); + views::TableColumn path_column = + views::TableColumn(IDS_BOOKMARK_TABLE_PATH, views::TableColumn::LEFT, -1); + + std::vector<views::TableColumn> columns; + if (show_path_column_) { + int name_width = -1; + int url_width = -1; + int path_width = -1; + if (prefs) { + name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth2); + url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth2); + path_width = prefs->GetInteger(prefs::kBookmarkTablePathWidth); + } + if (name_width != -1 && url_width != -1 && path_width != -1) { + name_column.width = name_width; + url_column.width = url_width; + path_column.width = path_width; + } else { + name_column.percent = .5; + url_column.percent = .25; + path_column.percent= .25; + } + columns.push_back(name_column); + columns.push_back(url_column); + columns.push_back(path_column); + } else { + int name_width = -1; + int url_width = -1; + if (prefs) { + name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth1); + url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth1); + } + if (name_width != -1 && url_width != -1) { + name_column.width = name_width; + url_column.width = url_width; + } else { + name_column.percent = .5; + url_column.percent = .5; + } + columns.push_back(name_column); + columns.push_back(url_column); + } + SetColumns(columns); + for (size_t i = 0; i < columns.size(); ++i) + SetColumnVisibility(columns[i].id, true); + OnModelChanged(); +} diff --git a/chrome/browser/views/bookmark_table_view.h b/chrome/browser/views/bookmark_table_view.h new file mode 100644 index 0000000..a01f1af --- /dev/null +++ b/chrome/browser/views/bookmark_table_view.h @@ -0,0 +1,121 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_TABLE_VIEW_H_ +#define CHROME_BROWSER_VIEWS_BOOKMARK_TABLE_VIEW_H_ + +#include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "chrome/views/menu.h" +#include "chrome/views/table_view.h" + +class BookmarkModel; +class BookmarkNode; +class BookmarkTableModel; +class OSExchangeData; +class PrefService; +class Profile; + +// A TableView implementation that shows a BookmarkTableModel. +// BookmarkTableView provides drag and drop support as well as showing a +// separate set of columns when showing search results. +class BookmarkTableView : public views::TableView { + public: + BookmarkTableView(Profile* profile, BookmarkTableModel* model); + + static void RegisterUserPrefs(PrefService* prefs); + + // Drag and drop methods. + virtual bool CanDrop(const OSExchangeData& data); + virtual void OnDragEntered(const views::DropTargetEvent& event); + virtual int OnDragUpdated(const views::DropTargetEvent& event); + virtual void OnDragExited(); + virtual int OnPerformDrop(const views::DropTargetEvent& event); + + // Sets the parent of the nodes being displayed. For search and recently + // found results |parent| is NULL. + void set_parent_node(BookmarkNode* parent) { parent_node_ = parent; } + + // Sets whether the path column should be shown. The path column is shown + // for search results and recently bookmarked. + void SetShowPathColumn(bool show_path_column); + + // The model as a BookmarkTableModel. + BookmarkTableModel* bookmark_table_model() const; + + // Saves the widths of the table columns. + void SaveColumnConfiguration(); + + protected: + // Overriden to draw a drop indicator when dropping between rows. + virtual void PostPaint(); + + // Overriden to start a drag. + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + private: + // Information used when we're the drop target of a drag and drop operation. + struct DropInfo { + DropInfo() : drop_index(-1), drop_operation(0), drop_on(false) {} + + BookmarkDragData drag_data; + + // Index into the table model of where the drop should occur. + int drop_index; + + // The drop operation that should occur. + int drop_operation; + + // Whether the drop is on drop_index or before it. + bool drop_on; + }; + + // Starts a drop operation. + void BeginDrag(); + + // Returns the drop operation for the specified index. + int CalculateDropOperation(const views::DropTargetEvent& event, + int drop_index, + bool drop_on); + + // Performs the drop operation. + void OnPerformDropImpl(); + + // Sets the drop index. If index differs from the current drop index + // UpdateDropIndex is invoked for the old and new values. + void SetDropIndex(int index, bool drop_on); + + // Invoked from SetDropIndex to update the state for the specified index + // and schedule a paint. If |turn_on| is true the highlight is being turned + // on for the specified index, otherwise it is being turned off. + void UpdateDropIndex(int index, bool drop_on, bool turn_on); + + // Determines the drop index for the specified location. + int CalculateDropIndex(int y, bool* drop_on); + + // Returns the BookmarkNode the drop should occur on, or NULL if not over + // a valid location. + BookmarkNode* GetDropParentAndIndex(int visual_drop_index, + bool drop_on, + int* index); + + // Returns the bounds of drop indicator shown when the drop is to occur + // between rows (drop_on is false). + RECT GetDropBetweenHighlightRect(int index); + + // Resets the columns. BookmarkTableView shows different sets of columns. + // See ShowPathColumn for details. + void UpdateColumns(); + + Profile* profile_; + + BookmarkNode* parent_node_; + + scoped_ptr<DropInfo> drop_info_; + + bool show_path_column_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkTableView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_TABLE_VIEW_H_ diff --git a/chrome/browser/views/browser_views.vcproj b/chrome/browser/views/browser_views.vcproj index cfce335..4a47e9a 100644 --- a/chrome/browser/views/browser_views.vcproj +++ b/chrome/browser/views/browser_views.vcproj @@ -470,6 +470,30 @@ > </File> <File + RelativePath=".\bookmark_folder_tree_view.cc" + > + </File> + <File + RelativePath=".\bookmark_folder_tree_view.h" + > + </File> + <File + RelativePath=".\bookmark_manager_view.cc" + > + </File> + <File + RelativePath=".\bookmark_manager_view.h" + > + </File> + <File + RelativePath=".\bookmark_table_view.cc" + > + </File> + <File + RelativePath=".\bookmark_table_view.h" + > + </File> + <File RelativePath=".\bookmark_bubble_view.cc" > </File> diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc index a7f26e3..d87e205 100644 --- a/chrome/browser/views/toolbar_view.cc +++ b/chrome/browser/views/toolbar_view.cc @@ -546,6 +546,8 @@ void BrowserToolbarView::RunAppMenu(const CPoint& pt, HWND hwnd) { menu.AppendSeparator(); menu.AppendMenuItemWithLabel(IDC_SHOW_HISTORY, l10n_util::GetString(IDS_SHOW_HISTORY)); + menu.AppendMenuItemWithLabel(IDC_SHOW_BOOKMARK_MANAGER, + l10n_util::GetString(IDS_BOOKMARK_MANAGER)); menu.AppendMenuItemWithLabel(IDC_SHOW_DOWNLOADS, l10n_util::GetString(IDS_SHOW_DOWNLOADS)); menu.AppendSeparator(); |