From 776e749879084abf997bed1838737fd0f8bd5a20 Mon Sep 17 00:00:00 2001 From: "sky@google.com" Date: Thu, 23 Oct 2008 16:47:41 +0000 Subject: Adds models needed by the bookmark manager. Specifically a BookmarkTableModel, which will be used to show one of the following: the children of a folder, recently bookmarked or the results of a search. And the tree model implementation that shows the folders. BUG=674 TEST=covered by unit tests Review URL: http://codereview.chromium.org/8063 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3814 0039d316-1c4b-4281-b951-d872f2087c98 --- .../bookmarks/bookmark_folder_tree_model.cc | 178 +++++++++++ .../browser/bookmarks/bookmark_folder_tree_model.h | 102 +++++++ .../bookmark_folder_tree_model_unittest.cc | 186 ++++++++++++ chrome/browser/bookmarks/bookmark_model.cc | 27 +- chrome/browser/bookmarks/bookmark_model.h | 13 +- chrome/browser/bookmarks/bookmark_table_model.cc | 325 +++++++++++++++++++++ chrome/browser/bookmarks/bookmark_table_model.h | 66 +++++ .../bookmarks/bookmark_table_model_unittest.cc | 296 +++++++++++++++++++ 8 files changed, 1188 insertions(+), 5 deletions(-) create mode 100644 chrome/browser/bookmarks/bookmark_folder_tree_model.cc create mode 100644 chrome/browser/bookmarks/bookmark_folder_tree_model.h create mode 100644 chrome/browser/bookmarks/bookmark_folder_tree_model_unittest.cc create mode 100644 chrome/browser/bookmarks/bookmark_table_model.cc create mode 100644 chrome/browser/bookmarks/bookmark_table_model.h create mode 100644 chrome/browser/bookmarks/bookmark_table_model_unittest.cc (limited to 'chrome/browser/bookmarks') diff --git a/chrome/browser/bookmarks/bookmark_folder_tree_model.cc b/chrome/browser/bookmarks/bookmark_folder_tree_model.cc new file mode 100644 index 0000000..f365e76 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_folder_tree_model.cc @@ -0,0 +1,178 @@ +// 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_folder_tree_model.h" + +#include "chrome/common/l10n_util.h" + +#include "generated_resources.h" + +BookmarkFolderTreeModel::BookmarkFolderTreeModel(BookmarkModel* model) + : views::TreeNodeModel(new FolderNode(NULL)), + model_(model), + recently_bookmarked_node_(new FolderNode(NULL)), + search_node_(new FolderNode(NULL)){ + recently_bookmarked_node_->SetTitle( + l10n_util::GetString(IDS_BOOKMARK_TREE_RECENTLY_BOOKMARKED_NODE_TITLE)); + search_node_->SetTitle( + l10n_util::GetString(IDS_BOOKMARK_TREE_SEARCH_NODE_TITLE)); + if (model_->IsLoaded()) + AddRootChildren(); + model_->AddObserver(this); +} + +BookmarkFolderTreeModel::~BookmarkFolderTreeModel() { + if (model_) + model_->RemoveObserver(this); +} + +BookmarkFolderTreeModel::NodeType BookmarkFolderTreeModel::GetNodeType( + views::TreeModelNode* node) { + if (node == recently_bookmarked_node_) + return RECENTLY_BOOKMARKED; + if (node == search_node_) + return SEARCH; + return BOOKMARK; +} + +FolderNode* BookmarkFolderTreeModel::GetFolderNodeForBookmarkNode( + BookmarkNode* node) { + if (!node->is_folder()) + return NULL; + + return GetFolderNodeForBookmarkNode(AsNode(GetRoot()), node); +} + +BookmarkNode* BookmarkFolderTreeModel::TreeNodeAsBookmarkNode( + views::TreeModelNode* node) { + if (GetNodeType(node) != BOOKMARK) + return NULL; + return AsNode(node)->value; +} + +void BookmarkFolderTreeModel::Loaded(BookmarkModel* model) { + AddRootChildren(); +} + +void BookmarkFolderTreeModel::BookmarkModelBeingDeleted(BookmarkModel* model) { + DCHECK(model_); + model_->RemoveObserver(this); + model_ = NULL; +} + +void BookmarkFolderTreeModel::BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index) { + BookmarkNode* moved_node = new_parent->GetChild(new_index); + if (!moved_node->is_folder()) + return; // We're only showing folders, so we can ignore this. + + FolderNode* moved_folder_node = GetFolderNodeForBookmarkNode(moved_node); + DCHECK(moved_folder_node); + int old_folder_index = + moved_folder_node->GetParent()->IndexOfChild(moved_folder_node); + Remove(moved_folder_node->GetParent(), old_folder_index); + int new_folder_index = CalculateIndexForChild(moved_node); + Add(GetFolderNodeForBookmarkNode(new_parent), new_folder_index, + moved_folder_node); +} + +void BookmarkFolderTreeModel::BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) { + BookmarkNode* new_node = parent->GetChild(index); + if (!new_node->is_folder()) + return; // We're only showing folders, so we can ignore this. + + int folder_index = CalculateIndexForChild(new_node); + Add(GetFolderNodeForBookmarkNode(parent), folder_index, + CreateFolderNode(new_node)); +} + +void BookmarkFolderTreeModel::BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index, + BookmarkNode* node) { + if (!node->is_folder()) + return; // We're only showing folders. + + FolderNode* folder_node = GetFolderNodeForBookmarkNode(parent); + DCHECK(folder_node); + for (int i = 0; i < folder_node->GetChildCount(); ++i) { + if (folder_node->GetChild(i)->value == node) { + scoped_ptr removed_node(Remove(folder_node, i)); + return; + } + } + + // If we get here it means a folder was removed that we didn't know about, + // which shouldn't happen. + NOTREACHED(); +} + +void BookmarkFolderTreeModel::BookmarkNodeChanged(BookmarkModel* model, + BookmarkNode* node) { + if (!node->is_folder()) + return; + + FolderNode* folder_node = GetFolderNodeForBookmarkNode(node); + if (!folder_node) + return; + + folder_node->SetTitle(node->GetTitle()); + if (GetObserver()) + GetObserver()->TreeNodeChanged(this, folder_node); +} + +void BookmarkFolderTreeModel::AddRootChildren() { + Add(AsNode(GetRoot()), 0, CreateFolderNode(model_->GetBookmarkBarNode())); + Add(AsNode(GetRoot()), 1, CreateFolderNode(model_->other_node())); + Add(AsNode(GetRoot()), 2, recently_bookmarked_node_); + Add(AsNode(GetRoot()), 3, search_node_); +} + +FolderNode* BookmarkFolderTreeModel::GetFolderNodeForBookmarkNode( + FolderNode* folder_node, + BookmarkNode* node) { + DCHECK(node->is_folder()); + if (folder_node->value == node) + return folder_node; + for (int i = 0; i < folder_node->GetChildCount(); ++i) { + FolderNode* result = + GetFolderNodeForBookmarkNode(folder_node->GetChild(i), node); + if (result) + return result; + } + return NULL; +} + +FolderNode* BookmarkFolderTreeModel::CreateFolderNode(BookmarkNode* node) { + DCHECK(node->is_folder()); + FolderNode* folder_node = new FolderNode(node); + folder_node->SetTitle(node->GetTitle()); + + // And clone the children folders. + for (int i = 0; i < node->GetChildCount(); ++i) { + BookmarkNode* child = node->GetChild(i); + if (child->is_folder()) + folder_node->Add(folder_node->GetChildCount(), CreateFolderNode(child)); + } + return folder_node; +} + +int BookmarkFolderTreeModel::CalculateIndexForChild(BookmarkNode* node) { + BookmarkNode* parent = node->GetParent(); + DCHECK(parent); + for (int i = 0, folder_count = 0; i < parent->GetChildCount(); ++i) { + BookmarkNode* child = parent->GetChild(i); + if (child == node) + return folder_count; + if (child->is_folder()) + folder_count++; + } + NOTREACHED(); + return 0; +} diff --git a/chrome/browser/bookmarks/bookmark_folder_tree_model.h b/chrome/browser/bookmarks/bookmark_folder_tree_model.h new file mode 100644 index 0000000..c2f7093 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_folder_tree_model.h @@ -0,0 +1,102 @@ +// 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_FOLDER_TREE_MODEL_H_ +#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_FOLDER_TREE_MODEL_H_ + +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/views/tree_node_model.h" + +// The type of nodes created by BookmarkFolderTreeModel. +typedef views::TreeNodeWithValue FolderNode; + +// TreeModel implementation that shows the folders from the BookmarkModel. +// The root node contains the following nodes: +// bookmark bar, other folders, recently bookmarked and search. +class BookmarkFolderTreeModel : public views::TreeNodeModel, + public BookmarkModelObserver { + public: + // Type of the node. + enum NodeType { + // Represents an entry from the BookmarkModel. + BOOKMARK, + RECENTLY_BOOKMARKED, + SEARCH, + NONE // Used for no selection. + }; + + explicit BookmarkFolderTreeModel(BookmarkModel* model); + ~BookmarkFolderTreeModel(); + + // The tree is not editable. + virtual void SetTitle(views::TreeModelNode* node, const std::wstring& title) { + NOTREACHED(); + } + + // Returns the type of the specified node. + NodeType GetNodeType(views::TreeModelNode* node); + + // Returns the FolderNode for the specified BookmarkNode. + FolderNode* GetFolderNodeForBookmarkNode(BookmarkNode* node); + + // Converts the tree node into a BookmarkNode. Returns NULL if |node| is NULL + // or not of NodeType::BOOKMARK. + BookmarkNode* TreeNodeAsBookmarkNode(views::TreeModelNode* node); + + // Returns the search node. + FolderNode* search_node() const { return search_node_; } + + // BookmarkModelObserver implementation. + 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); + // Folders don't have favicons, so we ignore this. + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + BookmarkNode* node) {} + + private: + // Invoked from the constructor to create the children of the root node. + void AddRootChildren(); + + // Implementation of GetFolderNodeForBookmarkNode. If the |folder_node| + // represents |node|, |folder_node| is returned, otherwise this recurses + // through the children. + FolderNode* GetFolderNodeForBookmarkNode(FolderNode* folder_node, + BookmarkNode* node); + + // Creates a new folder node for |node| and all its children. + FolderNode* CreateFolderNode(BookmarkNode* node); + + // Returns the number of folders that precede |node| in |node|s parent. + // The returned value is the index of the folder node representing |node| + // in its parent. + // This is used when new bookmarks are created to determine where the + // corresponding folder node should be created. + int CalculateIndexForChild(BookmarkNode* node); + + // The model we're getting data from. Owned by the Profile. + BookmarkModel* model_; + + // The two special nodes. These are owned by the root tree node owned by + // TreeNodeModel. + FolderNode* recently_bookmarked_node_; + FolderNode* search_node_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkFolderTreeModel); +}; + +#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_FOLDER_TREE_MODEL_H_ diff --git a/chrome/browser/bookmarks/bookmark_folder_tree_model_unittest.cc b/chrome/browser/bookmarks/bookmark_folder_tree_model_unittest.cc new file mode 100644 index 0000000..c6f0a00 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_folder_tree_model_unittest.cc @@ -0,0 +1,186 @@ +// 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 "base/string_util.h" +#include "base/time.h" +#include "chrome/browser/bookmarks/bookmark_folder_tree_model.h" +#include "chrome/test/testing_profile.h" +#include "chrome/views/tree_view.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "generated_resources.h" + +// Base class for bookmark model tests. +// Initial state of the bookmark model is as follows: +// bb +// url1 +// f1 +// f11 +// o +// url2 +// f2 +// url3 +class BookmarkFolderTreeModelTest : public testing::Test, + public views::TreeModelObserver { + public: + BookmarkFolderTreeModelTest() + : url1_("http://1"), + url2_("http://2"), + url3_("http://3"), + added_count_(0), + removed_count_(0), + changed_count_(0) { + } + + virtual void SetUp() { + profile_.reset(new TestingProfile()); + profile_->CreateBookmarkModel(true); + // Populate with some default data. + BookmarkNode* bb = bookmark_model()->GetBookmarkBarNode(); + bookmark_model()->AddURL(bb, 0, L"url1", url1_); + BookmarkNode* f1 = bookmark_model()->AddGroup(bb, 1, L"f1"); + bookmark_model()->AddGroup(f1, 0, L"f11"); + + BookmarkNode* other = bookmark_model()->other_node(); + bookmark_model()->AddURL(other, 0, L"url2", url2_); + bookmark_model()->AddGroup(other, 1, L"f2"); + bookmark_model()->AddURL(other, 2, L"url3", url3_); + + model_.reset(new BookmarkFolderTreeModel(bookmark_model())); + model_->SetObserver(this); + } + + virtual void TearDown() { + model_.reset(NULL); + profile_.reset(NULL); + } + + BookmarkModel* bookmark_model() const { + return profile_->GetBookmarkModel(); + } + + virtual void TreeNodesAdded(views::TreeModel* model, + views::TreeModelNode* parent, + int start, + int count) { + added_count_++; + } + + virtual void TreeNodesRemoved(views::TreeModel* model, + views::TreeModelNode* parent, + int start, + int count) { + removed_count_++; + } + + virtual void TreeNodeChanged(views::TreeModel* model, + views::TreeModelNode* node) { + changed_count_++; + } + + void VerifyAndClearObserverCounts(int changed_count, int added_count, + int removed_count) { + EXPECT_EQ(changed_count, changed_count_); + EXPECT_EQ(added_count, added_count_); + EXPECT_EQ(removed_count, removed_count_); + ResetCounts(); + } + + void ResetCounts() { + changed_count_ = removed_count_ = added_count_ = 0; + } + + scoped_ptr model_; + + const GURL url1_; + const GURL url2_; + const GURL url3_; + + private: + int changed_count_; + int added_count_; + int removed_count_; + scoped_ptr profile_; +}; + +// Verifies the root node has 4 nodes, and the contents of the bookmark bar +// and other folders matches the initial state. +TEST_F(BookmarkFolderTreeModelTest, InitialState) { + // Verify the first 4 nodes. + views::TreeModelNode* root = model_->GetRoot(); + ASSERT_EQ(4, model_->GetChildCount(root)); + EXPECT_EQ(BookmarkFolderTreeModel::BOOKMARK, + model_->GetNodeType(model_->GetChild(root, 0))); + EXPECT_EQ(BookmarkFolderTreeModel::BOOKMARK, + model_->GetNodeType(model_->GetChild(root, 1))); + EXPECT_EQ(BookmarkFolderTreeModel::RECENTLY_BOOKMARKED, + model_->GetNodeType(model_->GetChild(root, 2))); + EXPECT_EQ(BookmarkFolderTreeModel::SEARCH, + model_->GetNodeType(model_->GetChild(root, 3))); + + // Verify the contents of the bookmark bar node. + views::TreeModelNode* bb_node = model_->GetChild(root, 0); + EXPECT_TRUE(model_->TreeNodeAsBookmarkNode(bb_node) == + bookmark_model()->GetBookmarkBarNode()); + ASSERT_EQ(1, model_->GetChildCount(bb_node)); + EXPECT_TRUE(model_->TreeNodeAsBookmarkNode(model_->GetChild(bb_node, 0)) == + bookmark_model()->GetBookmarkBarNode()->GetChild(1)); + + // Verify the contents of the other folders node. + views::TreeModelNode* other_node = model_->GetChild(root, 1); + EXPECT_TRUE(model_->TreeNodeAsBookmarkNode(other_node) == + bookmark_model()->other_node()); + ASSERT_EQ(1, model_->GetChildCount(other_node)); + EXPECT_TRUE(model_->TreeNodeAsBookmarkNode(model_->GetChild(other_node, 0)) == + bookmark_model()->other_node()->GetChild(1)); +} + +// Removes a URL node and makes sure we don't get any notification. +TEST_F(BookmarkFolderTreeModelTest, RemoveURL) { + bookmark_model()->Remove(bookmark_model()->GetBookmarkBarNode(), 0); + VerifyAndClearObserverCounts(0, 0, 0); +} + +// Changes the title of a URL and makes sure we don't get any notification. +TEST_F(BookmarkFolderTreeModelTest, ChangeURL) { + bookmark_model()->SetTitle( + bookmark_model()->GetBookmarkBarNode()->GetChild(0), L"BLAH"); + VerifyAndClearObserverCounts(0, 0, 0); +} + +// Adds a URL and make sure we don't get notification. +TEST_F(BookmarkFolderTreeModelTest, AddURL) { + bookmark_model()->AddURL( + bookmark_model()->other_node(), 0, L"url1", url1_); + VerifyAndClearObserverCounts(0, 0, 0); +} + +// Removes a folder and makes sure we get the right notification. +TEST_F(BookmarkFolderTreeModelTest, RemoveFolder) { + bookmark_model()->Remove(bookmark_model()->GetBookmarkBarNode(), 1); + VerifyAndClearObserverCounts(0, 0, 1); + // Make sure the node was removed. + EXPECT_EQ(0, model_->GetRoot()->GetChild(0)->GetChildCount()); +} + +// Adds a folder and makes sure we get the right notification. +TEST_F(BookmarkFolderTreeModelTest, AddFolder) { + BookmarkNode* new_group = + bookmark_model()->AddGroup( + bookmark_model()->GetBookmarkBarNode(), 0, L"fa"); + VerifyAndClearObserverCounts(0, 1, 0); + // Make sure the node was added at the right place. + // Make sure the node was removed. + ASSERT_EQ(2, model_->GetRoot()->GetChild(0)->GetChildCount()); + EXPECT_TRUE(new_group == model_->TreeNodeAsBookmarkNode( + model_->GetRoot()->GetChild(0)->GetChild(0))); +} + +// Changes the title of a folder and makes sure we don't get any notification. +TEST_F(BookmarkFolderTreeModelTest, ChangeFolder) { + bookmark_model()->SetTitle( + bookmark_model()->GetBookmarkBarNode()->GetChild(1)->GetChild(0), + L"BLAH"); + VerifyAndClearObserverCounts(1, 0, 0); +} diff --git a/chrome/browser/bookmarks/bookmark_model.cc b/chrome/browser/bookmarks/bookmark_model.cc index fba0629..14430a3 100644 --- a/chrome/browser/bookmarks/bookmark_model.cc +++ b/chrome/browser/bookmarks/bookmark_model.cc @@ -201,6 +201,22 @@ void BookmarkModel::GetBookmarksMatchingText( } } +bool BookmarkModel::DoesBookmarkMatchText(const std::wstring& text, + BookmarkNode* node) { + if (!node->is_url()) + return false; + + QueryParser parser; + ScopedVector query_nodes; + parser.ParseQuery(text, &query_nodes.get()); + if (query_nodes.empty()) + return false; + + Snippet::MatchPositions match_position; + return parser.DoesQueryMatch(node->GetTitle(), query_nodes.get(), + &match_position); +} + void BookmarkModel::Remove(BookmarkNode* parent, int index) { if (!loaded_ || !IsValidIndex(parent, index, false) || parent == &root_) { NOTREACHED(); @@ -355,8 +371,11 @@ BookmarkNode* BookmarkModel::AddURLWithCreationTime( new_node->date_added_ = creation_time; new_node->type_ = history::StarredEntry::URL; - AutoLock url_lock(url_lock_); - nodes_ordered_by_url_set_.insert(new_node); + { + // Only hold the lock for the duration of the insert. + AutoLock url_lock(url_lock_); + nodes_ordered_by_url_set_.insert(new_node); + } return AddNode(parent, index, new_node, was_bookmarked); } @@ -535,7 +554,7 @@ void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) { store_->ScheduleSave(); FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, - BookmarkNodeRemoved(this, parent, index)); + BookmarkNodeRemoved(this, parent, index, node.get())); if (details.changed_urls.empty()) { // No point in sending out notification if the starred state didn't change. @@ -596,7 +615,7 @@ BookmarkNode* BookmarkModel::GetNodeByID(BookmarkNode* node, int id) { bool BookmarkModel::IsValidIndex(BookmarkNode* parent, int index, bool allow_end) { - return (parent && + return (parent && parent->is_folder() && (index >= 0 && (index < parent->GetChildCount() || (allow_end && index == parent->GetChildCount())))); } diff --git a/chrome/browser/bookmarks/bookmark_model.h b/chrome/browser/bookmarks/bookmark_model.h index d9f47ac..2f3f32d 100644 --- a/chrome/browser/bookmarks/bookmark_model.h +++ b/chrome/browser/bookmarks/bookmark_model.h @@ -147,9 +147,16 @@ class BookmarkModelObserver { int index) = 0; // Invoked when a node has been removed, the item may still be starred though. + // TODO(sky): merge these two into one. virtual void BookmarkNodeRemoved(BookmarkModel* model, BookmarkNode* parent, - int index) = 0; + int index) {} + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int old_index, + BookmarkNode* node) { + BookmarkNodeRemoved(model, parent, old_index); + } // Invoked when the title or favicon of a node has changed. virtual void BookmarkNodeChanged(BookmarkModel* model, @@ -222,6 +229,10 @@ class BookmarkModel : public NotificationObserver, public BookmarkService { size_t max_count, std::vector* matches); + // Returns true if the specified bookmark's title matches the specified + // text. + bool DoesBookmarkMatchText(const std::wstring& text, BookmarkNode* node); + void AddObserver(BookmarkModelObserver* observer) { observers_.AddObserver(observer); } diff --git a/chrome/browser/bookmarks/bookmark_table_model.cc b/chrome/browser/bookmarks/bookmark_table_model.cc new file mode 100644 index 0000000..c55f6ff --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_table_model.cc @@ -0,0 +1,325 @@ +// 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_table_model.h" + +#include + +#include "base/string_util.h" +#include "base/time.h" +#include "base/time_format.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/resource_bundle.h" +#include "googleurl/src/gurl.h" + +#include "generated_resources.h" + +namespace { + +// Number of bookmarks shown in recently bookmarked. +const int kRecentlyBookmarkedCount = 50; + +// FolderBookmarkTableModel ---------------------------------------------------- + +class FolderBookmarkTableModel : public BookmarkTableModel { + public: + FolderBookmarkTableModel(BookmarkModel* model, BookmarkNode* root_node) + : BookmarkTableModel(model), + root_node_(root_node) { + } + + virtual int RowCount() { + return root_node_ ? root_node_->GetChildCount() : 0; + } + + virtual BookmarkNode* GetNodeForRow(int row) { + DCHECK(root_node_); + return root_node_->GetChild(row); + } + + virtual void BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index) { + if (observer() && (old_parent == root_node_ || new_parent == root_node_)) + observer()->OnModelChanged(); + } + + virtual void BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) { + if (root_node_ == parent && observer()) + observer()->OnItemsAdded(index, 1); + } + + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index, + BookmarkNode* node) { + if (root_node_->HasAncestor(node)) { + // We, our one of our ancestors was removed. + root_node_ = NULL; + if (observer()) + observer()->OnModelChanged(); + return; + } + if (root_node_ == parent && observer()) + observer()->OnItemsRemoved(index, 1); + } + + virtual void BookmarkNodeChanged(BookmarkModel* model, + BookmarkNode* node) { + NotifyChanged(node); + } + + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + BookmarkNode* node) { + NotifyChanged(node); + } + + private: + void NotifyChanged(BookmarkNode* node) { + if (node->GetParent() == root_node_ && observer()) + observer()->OnItemsChanged(node->GetParent()->IndexOfChild(node), 1); + } + + // The node we're showing the children of. This is set to NULL if the node + // (or one of its ancestors) is removed from the model. + BookmarkNode* root_node_; + + DISALLOW_COPY_AND_ASSIGN(FolderBookmarkTableModel); +}; + +// VectorBackedBookmarkTableModel ---------------------------------------------- + +class VectorBackedBookmarkTableModel : public BookmarkTableModel { + public: + explicit VectorBackedBookmarkTableModel(BookmarkModel* model) + : BookmarkTableModel(model) { + } + + virtual BookmarkNode* GetNodeForRow(int row) { + return nodes_[row]; + } + + virtual int RowCount() { + return static_cast(nodes_.size()); + } + + virtual void BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index) { + NotifyObserverOfChange(new_parent->GetChild(new_index)); + } + + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + BookmarkNode* node) { + NotifyObserverOfChange(node); + } + + virtual void BookmarkNodeChanged(BookmarkModel* model, + BookmarkNode* node) { + NotifyObserverOfChange(node); + } + + protected: + void NotifyObserverOfChange(BookmarkNode* node) { + if (!observer()) + return; + + int index = IndexOfNode(node); + if (index != -1) + observer()->OnItemsChanged(index, 1); + } + + typedef std::vector Nodes; + Nodes nodes_; + + private: + DISALLOW_COPY_AND_ASSIGN(VectorBackedBookmarkTableModel); +}; + +// RecentlyBookmarkedTableModel ------------------------------------------------ + +class RecentlyBookmarkedTableModel : public VectorBackedBookmarkTableModel { + public: + explicit RecentlyBookmarkedTableModel(BookmarkModel* model) + : VectorBackedBookmarkTableModel(model) { + UpdateRecentlyBookmarked(); + } + + virtual void BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) { + if (parent->GetChild(index)->is_url()) + UpdateRecentlyBookmarked(); + } + + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int old_index, + BookmarkNode* old_node) { + if (old_node->is_url()) + UpdateRecentlyBookmarked(); + } + + private: + void UpdateRecentlyBookmarked() { + nodes_.clear(); + model()->GetMostRecentlyAddedEntries(kRecentlyBookmarkedCount, &nodes_); + if (observer()) + observer()->OnModelChanged(); + } + + DISALLOW_COPY_AND_ASSIGN(RecentlyBookmarkedTableModel); +}; + +// BookmarkSearchTableModel ---------------------------------------------------- + +class BookmarkSearchTableModel : public VectorBackedBookmarkTableModel { + public: + BookmarkSearchTableModel(BookmarkModel* model, + const std::wstring& search_text) + : VectorBackedBookmarkTableModel(model), + search_text_(search_text) { + std::vector matches; + model->GetBookmarksMatchingText(search_text, + std::numeric_limits::max(), &matches); + for (size_t i = 0; i < matches.size(); ++i) + nodes_.push_back(matches[i].node); + } + + virtual void BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) { + BookmarkNode* node = parent->GetChild(index); + if (model->DoesBookmarkMatchText(search_text_, node)) { + nodes_.push_back(node); + if (observer()) + observer()->OnItemsAdded(static_cast(nodes_.size() - 1), 1); + } + } + + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index, + BookmarkNode* node) { + int internal_index = IndexOfNode(node); + if (internal_index == -1) + return; + + nodes_.erase(nodes_.begin() + static_cast(internal_index)); + if (observer()) + observer()->OnItemsRemoved(internal_index, 1); + } + + private: + const std::wstring search_text_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkSearchTableModel); +}; + +} // namespace + +// BookmarkTableModel ---------------------------------------------------------- + +// static +BookmarkTableModel* BookmarkTableModel::CreateRecentlyBookmarkedModel( + BookmarkModel* model) { + return new RecentlyBookmarkedTableModel(model); +} + +// static +BookmarkTableModel* BookmarkTableModel::CreateBookmarkTableModelForFolder( + BookmarkModel* model, BookmarkNode* node) { + return new FolderBookmarkTableModel(model, node); +} + +// static +BookmarkTableModel* BookmarkTableModel::CreateSearchTableModel( + BookmarkModel* model, + const std::wstring& text) { + return new BookmarkSearchTableModel(model, text); +} + +BookmarkTableModel::BookmarkTableModel(BookmarkModel* model) + : model_(model), + observer_(NULL) { + model_->AddObserver(this); +} + +BookmarkTableModel::~BookmarkTableModel() { + if (model_) + model_->RemoveObserver(this); +} + +std::wstring BookmarkTableModel::GetText(int row, int column_id) { + BookmarkNode* node = GetNodeForRow(row); + switch (column_id) { + case IDS_BOOKMARK_TABLE_TITLE: + return node->GetTitle(); + + case IDS_BOOKMARK_TABLE_URL: + return node->is_url() ? UTF8ToWide(node->GetURL().spec()) : + std::wstring(); + + case IDS_BOOKMARK_TABLE_PATH: { + std::wstring path; + BuildPath(node->GetParent(), &path); + return path; + } + } + NOTREACHED(); + return std::wstring(); +} + +SkBitmap BookmarkTableModel::GetIcon(int row) { + static SkBitmap* folder_icon = ResourceBundle::GetSharedInstance(). + GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER); + static SkBitmap* default_icon = ResourceBundle::GetSharedInstance(). + GetBitmapNamed(IDR_DEFAULT_FAVICON); + + BookmarkNode* node = GetNodeForRow(row); + if (node->is_folder()) + return *folder_icon; + + if (node->GetFavIcon().empty()) + return *default_icon; + + return node->GetFavIcon(); +} + +void BookmarkTableModel::BookmarkModelBeingDeleted(BookmarkModel* model) { + model_->RemoveObserver(this); + model_ = NULL; +} + +int BookmarkTableModel::IndexOfNode(BookmarkNode* node) { + for (int i = RowCount() - 1; i >= 0; --i) { + if (GetNodeForRow(i) == node) + return i; + } + return -1; +} + +void BookmarkTableModel::BuildPath(BookmarkNode* node, std::wstring* path) { + if (!node) { + NOTREACHED(); + return; + } + if (node == model()->GetBookmarkBarNode()) { + *path = l10n_util::GetString(IDS_BOOKMARK_TABLE_BOOKMARK_BAR_PATH); + return; + } + if (node == model()->other_node()) { + *path = l10n_util::GetString(IDS_BOOKMARK_TABLE_OTHER_BOOKMARKS_PATH); + return; + } + BuildPath(node->GetParent(), path); + path->append(l10n_util::GetString(IDS_BOOKMARK_TABLE_PATH_SEPARATOR)); + path->append(node->GetTitle()); +} diff --git a/chrome/browser/bookmarks/bookmark_table_model.h b/chrome/browser/bookmarks/bookmark_table_model.h new file mode 100644 index 0000000..b7f295e --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_table_model.h @@ -0,0 +1,66 @@ +// 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_TABLE_MODEL_H_ +#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_TABLE_MODEL_H_ + +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/views/table_view.h" + +// BookmarkTableModel provides a view of the BookmarkModel as a TableModel. +// Three variations are provided: +// . Recently created bookmarks. +// . The children of a particular folder. +// . All bookmarks matching the specified text. +class BookmarkTableModel : public views::TableModel, + public BookmarkModelObserver { + public: + // Methods for creating the various BookmarkTableModels. Ownership passes + // to the caller. + static BookmarkTableModel* CreateRecentlyBookmarkedModel( + BookmarkModel* model); + static BookmarkTableModel* CreateBookmarkTableModelForFolder( + BookmarkModel* model, BookmarkNode* node); + static BookmarkTableModel* CreateSearchTableModel( + BookmarkModel* model, + const std::wstring& text); + + explicit BookmarkTableModel(BookmarkModel* model); + virtual ~BookmarkTableModel(); + + // TableModel methods. + virtual std::wstring GetText(int row, int column_id); + virtual SkBitmap GetIcon(int row); + virtual void SetObserver(views::TableModelObserver* observer) { + observer_ = observer; + } + + // BookmarkModelObserver methods. + virtual void Loaded(BookmarkModel* model) {} + virtual void BookmarkModelBeingDeleted(BookmarkModel* model); + + // Returns the index of the specified node, or -1 if the node isn't in the + // model. + virtual int IndexOfNode(BookmarkNode* node); + + // Returns the BookmarkNode at the specified index. + virtual BookmarkNode* GetNodeForRow(int row) = 0; + + // Returns the underlying BookmarkModel. + BookmarkModel* model() const { return model_; } + + protected: + views::TableModelObserver* observer() const { return observer_; } + + private: + // Builds the path shown in the path column for the specified node. + void BuildPath(BookmarkNode* node, std::wstring* path); + + BookmarkModel* model_; + views::TableModelObserver* observer_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkTableModel); +}; + +#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_TABLE_MODEL_H_ diff --git a/chrome/browser/bookmarks/bookmark_table_model_unittest.cc b/chrome/browser/bookmarks/bookmark_table_model_unittest.cc new file mode 100644 index 0000000..ed2b215 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_table_model_unittest.cc @@ -0,0 +1,296 @@ +// 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 "base/string_util.h" +#include "base/time.h" +#include "chrome/browser/bookmarks/bookmark_table_model.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "generated_resources.h" + +// Base class for bookmark model tests. +// Initial state of the bookmark model is as follows: +// bb +// url1 (t0) +// f1 +// o +// url2 (t0 + 2) +// f2 +// url3 (t0 + 1) +class BookmarkTableModelTest : public testing::Test, + public views::TableModelObserver { + public: + BookmarkTableModelTest() + : url1_("http://1"), + url2_("http://2"), + url3_("http://3"), + changed_count_(0), + item_changed_count_(0), + added_count_(0), + removed_count_(0) { + } + + virtual void SetUp() { + profile_.reset(new TestingProfile()); + profile_->CreateBookmarkModel(true); + // Populate with some default data. + Time t0 = Time::Now(); + BookmarkNode* bb = bookmark_model()->GetBookmarkBarNode(); + bookmark_model()->AddURLWithCreationTime(bb, 0, L"a", url1_, t0); + bookmark_model()->AddGroup(bb, 1, L"f1"); + + BookmarkNode* other = bookmark_model()->other_node(); + bookmark_model()->AddURLWithCreationTime(other, 0, L"b", + url2_, t0 + TimeDelta::FromDays(2)); + bookmark_model()->AddGroup(other, 1, L"f2"); + bookmark_model()->AddURLWithCreationTime(other, 2, L"c", url3_, + t0 + TimeDelta::FromDays(1)); + } + + virtual void TearDown() { + model_.reset(NULL); + profile_.reset(NULL); + } + + BookmarkModel* bookmark_model() const { + return profile_->GetBookmarkModel(); + } + + virtual void OnModelChanged() { + changed_count_++; + } + + virtual void OnItemsChanged(int start, int length) { + item_changed_count_++; + } + + virtual void OnItemsAdded(int start, int length) { + added_count_++; + } + + virtual void OnItemsRemoved(int start, int length) { + removed_count_++; + } + + void VerifyAndClearOberserverCounts(int changed_count, int item_changed_count, + int added_count, int removed_count) { + EXPECT_EQ(changed_count, changed_count_); + EXPECT_EQ(item_changed_count, item_changed_count_); + EXPECT_EQ(added_count, added_count_); + EXPECT_EQ(removed_count, removed_count_); + ResetCounts(); + } + + void ResetCounts() { + changed_count_ = item_changed_count_ = removed_count_ = added_count_ = 0; + } + + void SetModel(BookmarkTableModel* model) { + if (model_.get()) + model_->SetObserver(NULL); + model_.reset(model); + if (model_.get()) + model_->SetObserver(this); + } + + scoped_ptr model_; + + const GURL url1_; + const GURL url2_; + const GURL url3_; + + private: + int changed_count_; + int item_changed_count_; + int added_count_; + int removed_count_; + scoped_ptr profile_; +}; + +// Verifies the count when showing various nodes. +TEST_F(BookmarkTableModelTest, FolderInitialState) { + SetModel(BookmarkTableModel::CreateBookmarkTableModelForFolder( + bookmark_model(), bookmark_model()->GetBookmarkBarNode())); + ASSERT_EQ(2, model_->RowCount()); + EXPECT_TRUE(bookmark_model()->GetBookmarkBarNode()->GetChild(0) == + model_->GetNodeForRow(0)); + EXPECT_TRUE(bookmark_model()->GetBookmarkBarNode()->GetChild(1) == + model_->GetNodeForRow(1)); + + SetModel(BookmarkTableModel::CreateBookmarkTableModelForFolder( + bookmark_model(), bookmark_model()->other_node())); + EXPECT_EQ(3, model_->RowCount()); +} + +// Verifies adding an item to folder model generates the correct event. +TEST_F(BookmarkTableModelTest, AddToFolder) { + BookmarkNode* other = bookmark_model()->other_node(); + SetModel(BookmarkTableModel::CreateBookmarkTableModelForFolder( + bookmark_model(), other)); + BookmarkNode* new_node = bookmark_model()->AddURL(other, 0, L"new", url1_); + // Should have gotten notification of the add. + VerifyAndClearOberserverCounts(0, 0, 1, 0); + ASSERT_EQ(4, model_->RowCount()); + EXPECT_TRUE(new_node == model_->GetNodeForRow(0)); + + // Add to the bookmark bar, this shouldn't generate an event. + bookmark_model()->AddURL(bookmark_model()->GetBookmarkBarNode(), 0, L"new", + url1_); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} + +// Verifies removing an item from folder model generates the correct event. +TEST_F(BookmarkTableModelTest, RemoveFromFolder) { + BookmarkNode* other = bookmark_model()->other_node(); + SetModel(BookmarkTableModel::CreateBookmarkTableModelForFolder( + bookmark_model(), other)); + bookmark_model()->Remove(other, 0); + + // Should have gotten notification of the remove. + VerifyAndClearOberserverCounts(0, 0, 0, 1); + EXPECT_EQ(2, model_->RowCount()); + + // Remove from the bookmark bar, this shouldn't generate an event. + bookmark_model()->Remove(bookmark_model()->GetBookmarkBarNode(), 0); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} + +// Verifies changing an item in the folder model generates the correct event. +TEST_F(BookmarkTableModelTest, ChangeFolder) { + BookmarkNode* other = bookmark_model()->other_node(); + SetModel(BookmarkTableModel::CreateBookmarkTableModelForFolder( + bookmark_model(), other)); + bookmark_model()->SetTitle(other->GetChild(0), L"new"); + + // Should have gotten notification of the change. + VerifyAndClearOberserverCounts(0, 1, 0, 0); + EXPECT_EQ(3, model_->RowCount()); + + // Change a node in the bookmark bar, this shouldn't generate an event. + bookmark_model()->SetTitle( + bookmark_model()->GetBookmarkBarNode()->GetChild(0), L"new2"); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} + +// Verifies show recently added shows the recently added, in order. +TEST_F(BookmarkTableModelTest, RecentlyBookmarkedOrder) { + SetModel(BookmarkTableModel::CreateRecentlyBookmarkedModel(bookmark_model())); + EXPECT_EQ(3, model_->RowCount()); + + BookmarkNode* bb = bookmark_model()->GetBookmarkBarNode(); + BookmarkNode* other = bookmark_model()->other_node(); + EXPECT_TRUE(other->GetChild(0) == model_->GetNodeForRow(0)); + EXPECT_TRUE(other->GetChild(2) == model_->GetNodeForRow(1)); + EXPECT_TRUE(bb->GetChild(0) == model_->GetNodeForRow(2)); +} + +// Verifies adding an item to recently added notifies observer. +TEST_F(BookmarkTableModelTest, AddToRecentlyBookmarked) { + SetModel(BookmarkTableModel::CreateRecentlyBookmarkedModel(bookmark_model())); + bookmark_model()->AddURL(bookmark_model()->other_node(), 0, L"new", url1_); + // Should have gotten notification of the add. + VerifyAndClearOberserverCounts(1, 0, 0, 0); + EXPECT_EQ(4, model_->RowCount()); + + // Add a folder, this shouldn't change the model. + bookmark_model()->AddGroup(bookmark_model()->GetBookmarkBarNode(), 0, L"new"); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} + +// Verifies removing an item from recently added notifies observer. +TEST_F(BookmarkTableModelTest, RemoveFromRecentlyBookmarked) { + SetModel(BookmarkTableModel::CreateRecentlyBookmarkedModel(bookmark_model())); + bookmark_model()->Remove(bookmark_model()->other_node(), 0); + // Should have gotten notification of the remove. + VerifyAndClearOberserverCounts(1, 0, 0, 0); + EXPECT_EQ(2, model_->RowCount()); + + // Remove a folder, this shouldn't change the model. + bookmark_model()->Remove(bookmark_model()->other_node(), 0); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} + +// Verifies changing an item in recently added notifies observer. +TEST_F(BookmarkTableModelTest, ChangeRecentlyBookmarked) { + SetModel(BookmarkTableModel::CreateRecentlyBookmarkedModel(bookmark_model())); + bookmark_model()->SetTitle(bookmark_model()->other_node()->GetChild(0), + L"new"); + // Should have gotten notification of the change. + VerifyAndClearOberserverCounts(0, 1, 0, 0); + + // Change a folder, this shouldn't change the model. + bookmark_model()->SetTitle(bookmark_model()->other_node()->GetChild(1), + L"new"); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} + +// Verifies search finds the correct bookmarks. +TEST_F(BookmarkTableModelTest, Search) { + SetModel(BookmarkTableModel::CreateSearchTableModel(bookmark_model(), L"c")); + ASSERT_EQ(1, model_->RowCount()); + EXPECT_TRUE(bookmark_model()->other_node()->GetChild(2) == + model_->GetNodeForRow(0)); + // Make sure IndexOfNode works. + EXPECT_EQ(0, + model_->IndexOfNode(bookmark_model()->other_node()->GetChild(2))); +} + +// Verifies adding an item to search notifies observers. +TEST_F(BookmarkTableModelTest, AddToSearch) { + SetModel(BookmarkTableModel::CreateSearchTableModel(bookmark_model(), L"c")); + BookmarkNode* new_node = + bookmark_model()->AddURL(bookmark_model()->other_node(), 0, L"c", url1_); + // Should have gotten notification of the add. + VerifyAndClearOberserverCounts(0, 0, 1, 0); + ASSERT_EQ(2, model_->RowCount()); + // New node should have gone to end. + EXPECT_TRUE(model_->GetNodeForRow(1) == new_node); + + // Add a folder, this shouldn't change the model. + bookmark_model()->AddGroup(bookmark_model()->GetBookmarkBarNode(), 0, L"new"); + VerifyAndClearOberserverCounts(0, 0, 0, 0); + EXPECT_EQ(2, model_->RowCount()); + + // Add a url that doesn't match search, this shouldn't change the model. + bookmark_model()->AddGroup(bookmark_model()->GetBookmarkBarNode(), 0, L"new"); + VerifyAndClearOberserverCounts(0, 0, 0, 0); + EXPECT_EQ(2, model_->RowCount()); +} + +// Verifies removing an item updates search. +TEST_F(BookmarkTableModelTest, RemoveFromSearch) { + SetModel(BookmarkTableModel::CreateSearchTableModel(bookmark_model(), L"c")); + bookmark_model()->Remove(bookmark_model()->other_node(), 2); + // Should have gotten notification of the remove. + VerifyAndClearOberserverCounts(0, 0, 0, 1); + EXPECT_EQ(0, model_->RowCount()); + + // Remove a folder, this shouldn't change the model. + bookmark_model()->Remove(bookmark_model()->other_node(), 1); + VerifyAndClearOberserverCounts(0, 0, 0, 0); + + // Remove another url that isn't in the model, this shouldn't change anything. + bookmark_model()->Remove(bookmark_model()->other_node(), 0); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} + +// Verifies changing an item in search notifies observer. +TEST_F(BookmarkTableModelTest, ChangeSearch) { + SetModel(BookmarkTableModel::CreateSearchTableModel(bookmark_model(), L"c")); + bookmark_model()->SetTitle(bookmark_model()->other_node()->GetChild(2), + L"new"); + // Should have gotten notification of the change. + VerifyAndClearOberserverCounts(0, 1, 0, 0); + + // Change a folder, this shouldn't change the model. + bookmark_model()->SetTitle(bookmark_model()->other_node()->GetChild(1), + L"new"); + VerifyAndClearOberserverCounts(0, 0, 0, 0); + + // Change a url that isn't in the model, this shouldn't send change. + bookmark_model()->SetTitle(bookmark_model()->other_node()->GetChild(0), + L"new"); + VerifyAndClearOberserverCounts(0, 0, 0, 0); +} -- cgit v1.1