diff options
author | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-05 00:35:09 +0000 |
---|---|---|
committer | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-05 00:35:09 +0000 |
commit | 140aea057fa118579380f685f362f2533862b03f (patch) | |
tree | 627c15d8adda34f2c602589e0bac815bbdc7d4d9 | |
parent | 57e3074163dc21bc3bc8cd37dc05f191532d836a (diff) | |
download | chromium_src-140aea057fa118579380f685f362f2533862b03f.zip chromium_src-140aea057fa118579380f685f362f2533862b03f.tar.gz chromium_src-140aea057fa118579380f685f362f2533862b03f.tar.bz2 |
Port the folder selector portion of the BookmarkEditor to GTK.
Mirrors the BookmarkEditorView method, where the contents of BookmarkModel
are copied to a temporary model so changes can be discarded if the user hits
Cancel. In the GTK version, we copy not into another BookmarkModel, but into
a GtkTreeStore, which serves as a model to the GtkTreeView on screen.
Also ports the unit tests.
http://crbug.com/11250
Review URL: http://codereview.chromium.org/99361
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15257 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/bookmarks/bookmark_model.h | 6 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_utils.cc | 78 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_utils.h | 19 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_editor_gtk.cc | 154 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_editor_gtk.h | 35 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_editor_gtk_unittest.cc | 304 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_tree_model.cc | 161 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_tree_model.h | 43 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_editor_view.cc | 67 | ||||
-rw-r--r-- | chrome/chrome.gyp | 3 |
10 files changed, 773 insertions, 97 deletions
diff --git a/chrome/browser/bookmarks/bookmark_model.h b/chrome/browser/bookmarks/bookmark_model.h index b5ad92a..7afaee5 100644 --- a/chrome/browser/bookmarks/bookmark_model.h +++ b/chrome/browser/bookmarks/bookmark_model.h @@ -39,13 +39,14 @@ class StarredURLDatabase; // star id and type. BookmarkNodes are returned from a BookmarkModel. // class BookmarkNode : public views::TreeNode<BookmarkNode> { - friend class BookmarkEditorView; friend class BookmarkModel; friend class BookmarkCodec; friend class history::StarredURLDatabase; FRIEND_TEST(BookmarkCodecTest, PersistIDsTest); FRIEND_TEST(BookmarkEditorViewTest, ChangeParentAndURL); FRIEND_TEST(BookmarkEditorViewTest, EditURLKeepsPosition); + FRIEND_TEST(BookmarkEditorGtkTest, ChangeParentAndURL); + FRIEND_TEST(BookmarkEditorGtkTest, EditURLKeepsPosition); FRIEND_TEST(BookmarkModelTest, MostRecentlyAddedEntries); FRIEND_TEST(BookmarkModelTest, GetMostRecentlyAddedNodeForURL); @@ -81,6 +82,9 @@ class BookmarkNode : public views::TreeNode<BookmarkNode> { // Returns the time the bookmark/group was added. base::Time date_added() const { return date_added_; } + // Sets the time the bookmark/group was added. + void set_date_added(const base::Time& date) { date_added_ = date; } + // Returns the last time the group was modified. This is only maintained // for folders (including the bookmark and other folder). base::Time date_group_modified() const { return date_group_modified_; } diff --git a/chrome/browser/bookmarks/bookmark_utils.cc b/chrome/browser/bookmarks/bookmark_utils.cc index e20e545..6832778 100644 --- a/chrome/browser/bookmarks/bookmark_utils.cc +++ b/chrome/browser/bookmarks/bookmark_utils.cc @@ -32,6 +32,8 @@ #include "chrome/common/temp_scaffolding_stubs.h" #endif +using base::Time; + namespace { // A PageNavigator implementation that creates a new Browser. This is used when @@ -531,6 +533,82 @@ bool DoesBookmarkContainText(BookmarkNode* node, const std::wstring& text) { return (node->is_url() && DoesBookmarkContainWords(node, words)); } +void ApplyEditsWithNoGroupChange(BookmarkModel* model, + BookmarkNode* parent, + BookmarkNode* node, + const std::wstring& new_title, + const GURL& new_url, + BookmarkEditor::Handler* handler) { + BookmarkNode* old_parent = node ? node->GetParent() : NULL; + const int old_index = old_parent ? old_parent->IndexOfChild(node) : -1; + + if (!node) { + node = + model->AddURL(parent, parent->GetChildCount(), new_title, new_url); + + if (handler) + handler->NodeCreated(node); + 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()) { + model->AddURLWithCreationTime(old_parent, old_index, new_title, + new_url, node->date_added()); + model->Remove(old_parent, old_index + 1); + } else { + model->SetTitle(node, new_title); + } +} + +void ApplyEditsWithPossibleGroupChange(BookmarkModel* model, + BookmarkNode* new_parent, + BookmarkNode* node, + const std::wstring& new_title, + const GURL& new_url, + BookmarkEditor::Handler* handler) { + BookmarkNode* old_parent = node ? node->GetParent() : NULL; + const int old_index = old_parent ? old_parent->IndexOfChild(node) : -1; + + if (node) { + Time date_added = node->date_added(); + if (new_parent == node->GetParent()) { + // The parent is the same. + if (new_url != node->GetURL()) { + model->Remove(old_parent, old_index); + BookmarkNode* new_node = + model->AddURL(old_parent, old_index, new_title, new_url); + new_node->set_date_added(date_added); + } else { + model->SetTitle(node, new_title); + } + } else if (new_url != node->GetURL()) { + // The parent and URL changed. + model->Remove(old_parent, old_index); + BookmarkNode* new_node = + model->AddURL(new_parent, new_parent->GetChildCount(), new_title, + new_url); + new_node->set_date_added(date_added); + } else { + // The parent and title changed. Move the node and change the title. + model->Move(node, new_parent, new_parent->GetChildCount()); + model->SetTitle(node, new_title); + } + } else { + // We're adding a new URL. + node = + model->AddURL(new_parent, new_parent->GetChildCount(), new_title, + new_url); + if (handler) + handler->NodeCreated(node); + } +} + // Formerly in BookmarkBarView void ToggleWhenVisible(Profile* profile) { PrefService* prefs = profile->GetPrefs(); diff --git a/chrome/browser/bookmarks/bookmark_utils.h b/chrome/browser/bookmarks/bookmark_utils.h index 43966c0..81bdb84 100644 --- a/chrome/browser/bookmarks/bookmark_utils.h +++ b/chrome/browser/bookmarks/bookmark_utils.h @@ -9,6 +9,7 @@ #include "base/gfx/native_widget_types.h" #include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "chrome/browser/bookmarks/bookmark_editor.h" #include "chrome/browser/history/snippet.h" #include "webkit/glue/window_open_disposition.h" @@ -142,6 +143,24 @@ void GetBookmarksContainingText(BookmarkModel* model, // Returns true if |node|'s url or title contains the string |text|. bool DoesBookmarkContainText(BookmarkNode* node, const std::wstring& text); +// Modifies a bookmark node (assuming that there's no magic that needs to be +// done regarding moving from one folder to another). +void ApplyEditsWithNoGroupChange(BookmarkModel* model, + BookmarkNode* parent, + BookmarkNode* node, + const std::wstring& new_title, + const GURL& new_url, + BookmarkEditor::Handler* handler); + +// Modifies a bookmark node assuming that the parent of the node may have +// changed and the node will need to be removed and reinserted. +void ApplyEditsWithPossibleGroupChange(BookmarkModel* model, + BookmarkNode* new_parent, + BookmarkNode* node, + const std::wstring& new_title, + const GURL& new_url, + BookmarkEditor::Handler* handler); + // Toggles whether the bookmark bar is shown only on the new tab page or on // all tabs. This is a preference modifier, not a visual modifier. void ToggleWhenVisible(Profile* profile); diff --git a/chrome/browser/gtk/bookmark_editor_gtk.cc b/chrome/browser/gtk/bookmark_editor_gtk.cc index dae1b8f..72f2522 100644 --- a/chrome/browser/gtk/bookmark_editor_gtk.cc +++ b/chrome/browser/gtk/bookmark_editor_gtk.cc @@ -10,6 +10,8 @@ #include "base/gfx/gtk_util.h" #include "base/logging.h" #include "base/string_util.h" +#include "chrome/browser/gtk/bookmark_tree_model.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/history/history.h" #include "chrome/browser/profile.h" #include "chrome/browser/net/url_fixer_upper.h" @@ -26,6 +28,7 @@ const GdkColor kErrorColor = GDK_COLOR_RGB(0xFF, 0xBC, 0xBC); // Preferred width of the tree. static const int kTreeWidth = 300; +static const int kTreeHeight = 150; } // namespace @@ -65,9 +68,6 @@ BookmarkEditorGtk::~BookmarkEditorGtk() { // The tree model is deleted before the view. Reset the model otherwise the // tree will reference a deleted model. - // TODO(erg): Enable this when we have a |tree_view_|. - // if (tree_view_) - // tree_view_->SetModel(NULL); bb_model_->RemoveObserver(this); } @@ -76,14 +76,28 @@ void BookmarkEditorGtk::Init(GtkWindow* parent_window) { DCHECK(bb_model_); bb_model_->AddObserver(this); + // TODO(erg): Redo this entire class as a normal GtkWindow with it's modality + // manually set to TRUE because using the stock GtkDialog class gives me + // almost no control over the buttons on the bottom. dialog_ = gtk_dialog_new_with_buttons( l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_TITLE).c_str(), parent_window, GTK_DIALOG_MODAL, NULL); - // TODO(erg): Add "New Folder" button here and insert at correct place; to - // the extreme left of the dialog. + if (show_tree_) { + // We want the New Folder button to not automatically dismiss the dialog so + // we have to do that manually. gtk_dialog_add_button() always makes the + // button dismiss the dialog box. This isn't 100% accurate to what I want; + // see above about redoing this as a GtkWindow. + GtkWidget* action_area = GTK_DIALOG(dialog_)->action_area; + new_folder_button_ = gtk_button_new_with_label("New Folder"); + g_signal_connect(new_folder_button_, "clicked", + G_CALLBACK(OnNewFolderClicked), this); + gtk_box_pack_start(GTK_BOX(action_area), new_folder_button_, + FALSE, FALSE, 0); + } + close_button_ = gtk_dialog_add_button(GTK_DIALOG(dialog_), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT); @@ -102,6 +116,16 @@ void BookmarkEditorGtk::Init(GtkWindow* parent_window) { // ||| | | ||| // ||+-----------------+ +---------------------------------------+|| // |+-------------------------------------------------------------+| + // |+- GtkScrollWindow |scroll_window| ---------------------------+| + // ||+- GtkTreeView |tree_view_| --------------------------------+|| + // |||+- GtkTreeViewColumn |name_column| -----------------------+||| + // |||| |||| + // |||| |||| + // |||| |||| + // |||| |||| + // |||+---------------------------------------------------------+||| + // ||+-----------------------------------------------------------+|| + // |+-------------------------------------------------------------+| // +---------------------------------------------------------------+ GtkWidget* content_area = GTK_DIALOG(dialog_)->vbox; gtk_container_set_border_width(GTK_CONTAINER(content_area), 12); @@ -147,13 +171,39 @@ void BookmarkEditorGtk::Init(GtkWindow* parent_window) { gtk_box_pack_start(GTK_BOX(content_area), table, FALSE, FALSE, 0); - // TODO(erg): Port the windows bookmark tree model and enable this tree view. - // - // folder_tree_ = gtk_tree_view_new(); - // gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(folder_tree_), TRUE); - // gtk_widget_set_size_request(folder_tree_, kTreeWidth, -1); - // gtk_widget_show(folder_tree_); - // gtk_box_pack_start(GTK_BOX(content_area), folder_tree_, FALSE, FALSE, 0); + if (show_tree_) { + GtkTreeIter selected_iter; + int selected_id = node_ ? node_->GetParent()->id() : 0; + bookmark_utils::BuildTreeStoreFrom(bb_model_, selected_id, &tree_store_, + &selected_iter); + + // TODO(erg): Figure out how to place icons here. + GtkTreeViewColumn* name_column = + gtk_tree_view_column_new_with_attributes( + "Folder", gtk_cell_renderer_text_new(), "text", 0, NULL); + + tree_view_ = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_store_)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view_), FALSE); + gtk_tree_view_insert_column(GTK_TREE_VIEW(tree_view_), name_column, -1); + gtk_widget_set_size_request(tree_view_, kTreeWidth, kTreeHeight); + + tree_selection_ = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view_)); + + if (selected_id) { + GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store_), + &selected_iter); + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tree_view_), path); + gtk_tree_selection_select_path(tree_selection_, path); + gtk_tree_path_free(path); + } + + GtkWidget* scroll_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(scroll_window), tree_view_); + gtk_box_pack_start(GTK_BOX(content_area), scroll_window, TRUE, TRUE, 12); + } g_signal_connect(dialog_, "response", G_CALLBACK(OnResponse), this); @@ -228,10 +278,21 @@ std::wstring BookmarkEditorGtk::GetInputTitle() const { } void BookmarkEditorGtk::ApplyEdits() { - // TODO(erg): This is massively simplified because I haven't added a - // GtkTreeView to this class yet. Then, this will have to be a copy of - // BookmarkEditorView::ApplyEdits(). + DCHECK(bb_model_->IsLoaded()); + + GtkTreeIter currently_selected_iter; + if (show_tree_) { + if (!gtk_tree_selection_get_selected(tree_selection_, NULL, + ¤tly_selected_iter)) { + NOTREACHED() << "Something should always be selected"; + return; + } + } + ApplyEdits(¤tly_selected_iter); +} + +void BookmarkEditorGtk::ApplyEdits(GtkTreeIter* selected_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. // We don't want to do that here, so we remove ourselves as an observer. @@ -240,29 +301,33 @@ void BookmarkEditorGtk::ApplyEdits() { GURL new_url(GetInputURL()); std::wstring new_title(GetInputTitle()); - BookmarkNode* old_parent = node_ ? node_->GetParent() : NULL; - const int old_index = old_parent ? old_parent->IndexOfChild(node_) : -1; - - if (!node_) { - BookmarkNode* node = - bb_model_->AddURL(parent_, parent_->GetChildCount(), new_title, - new_url); - if (handler_.get()) - handler_->NodeCreated(node); + if (!show_tree_) { + bookmark_utils::ApplyEditsWithNoGroupChange( + bb_model_, parent_, node_, new_title, new_url, handler_.get()); return; } - // If we're not showing the tree we only need to modify the node. - if (old_index == -1) { + + // Create the new groups and update the titles. + BookmarkNode* new_parent = bookmark_utils::CommitTreeStoreDifferencesBetween( + bb_model_, tree_store_, selected_parent); + + if (!new_parent) { + // Bookmarks must be parented. 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); - } + + bookmark_utils::ApplyEditsWithPossibleGroupChange( + bb_model_, new_parent, node_, new_title, new_url, handler_.get()); +} + +void BookmarkEditorGtk::AddNewGroup(GtkTreeIter* parent, GtkTreeIter* child) { + gtk_tree_store_append(tree_store_, child, parent); + gtk_tree_store_set( + tree_store_, child, + 0, l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME).c_str(), + 1, 0, + -1); } // static @@ -306,3 +371,26 @@ void BookmarkEditorGtk::OnEntryChanged(GtkEditable* entry, gtk_widget_set_sensitive(GTK_WIDGET(dialog->ok_button_), true); } } + +// static +void BookmarkEditorGtk::OnNewFolderClicked(GtkWidget* button, + BookmarkEditorGtk* dialog) { + // TODO(erg): Make the inserted item here editable and edit it. If that's + // impossible (it's probably possible), fall back on the folder editor. + GtkTreeIter iter; + if (!gtk_tree_selection_get_selected(dialog->tree_selection_, + NULL, + &iter)) { + NOTREACHED() << "Something should always be selected"; + return; + } + + GtkTreeIter new_item_iter; + dialog->AddNewGroup(&iter, &new_item_iter); + + GtkTreePath* path = gtk_tree_model_get_path( + GTK_TREE_MODEL(dialog->tree_store_), &new_item_iter); + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dialog->tree_view_), path); + gtk_tree_selection_select_path(dialog->tree_selection_, path); + gtk_tree_path_free(path); +} diff --git a/chrome/browser/gtk/bookmark_editor_gtk.h b/chrome/browser/gtk/bookmark_editor_gtk.h index 1dbdbe8..b6669a0 100644 --- a/chrome/browser/gtk/bookmark_editor_gtk.h +++ b/chrome/browser/gtk/bookmark_editor_gtk.h @@ -13,6 +13,16 @@ // GTK version of the bookmark editor dialog. class BookmarkEditorGtk : public BookmarkEditor, public BookmarkModelObserver { + FRIEND_TEST(BookmarkEditorGtkTest, ChangeParent); + FRIEND_TEST(BookmarkEditorGtkTest, ChangeParentAndURL); + FRIEND_TEST(BookmarkEditorGtkTest, ChangeURLToExistingURL); + FRIEND_TEST(BookmarkEditorGtkTest, EditTitleKeepsPosition); + FRIEND_TEST(BookmarkEditorGtkTest, EditURLKeepsPosition); + FRIEND_TEST(BookmarkEditorGtkTest, ModelsMatch); + FRIEND_TEST(BookmarkEditorGtkTest, MoveToNewParent); + FRIEND_TEST(BookmarkEditorGtkTest, NewURL); + FRIEND_TEST(BookmarkEditorGtkTest, ChangeURLNoTree); + FRIEND_TEST(BookmarkEditorGtkTest, ChangeTitleNoTree); public: BookmarkEditorGtk(GtkWindow* window, Profile* profile, @@ -60,8 +70,20 @@ class BookmarkEditorGtk : public BookmarkEditor, // Returns the title the user has input. std::wstring GetInputTitle() const; + // Invokes ApplyEdits with the selected node. + // + // TODO(erg): This was copied from the windows version. Both should be + // cleaned up so that we don't overload ApplyEdits. void ApplyEdits(); + // Applies the edits done by the user. |selected_parent| gives the parent of + // the URL being edited. + void ApplyEdits(GtkTreeIter* selected_parent); + + // Adds a new group parented on |parent| and sets |child| to point to this + // new group. + void AddNewGroup(GtkTreeIter* parent, GtkTreeIter* child); + static void OnResponse(GtkDialog* dialog, int response_id, BookmarkEditorGtk* window); @@ -72,6 +94,8 @@ class BookmarkEditorGtk : public BookmarkEditor, static void OnWindowDestroy(GtkWidget* widget, BookmarkEditorGtk* dialog); static void OnEntryChanged(GtkEditable* entry, BookmarkEditorGtk* dialog); + static void OnNewFolderClicked(GtkWidget* button, BookmarkEditorGtk* dialog); + // Profile the entry is from. Profile* profile_; @@ -81,7 +105,16 @@ class BookmarkEditorGtk : public BookmarkEditor, GtkWidget* url_entry_; GtkWidget* close_button_; GtkWidget* ok_button_; - GtkWidget* folder_tree_; + GtkWidget* new_folder_button_; + GtkWidget* tree_view_; + + // Helper object that manages the currently selected item in |tree_view_|. + GtkTreeSelection* tree_selection_; + + // Our local copy of the bookmark data that we make from the BookmarkModel + // that we can modify as much as we want and still discard when the user + // clicks Cancel. + GtkTreeStore* tree_store_; // TODO(erg): BookmarkEditorView has an EditorTreeModel object here; convert // that into a GObject that implements the interface GtkTreeModel. diff --git a/chrome/browser/gtk/bookmark_editor_gtk_unittest.cc b/chrome/browser/gtk/bookmark_editor_gtk_unittest.cc new file mode 100644 index 0000000..9c76f28 --- /dev/null +++ b/chrome/browser/gtk/bookmark_editor_gtk_unittest.cc @@ -0,0 +1,304 @@ +// 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 <gtk/gtk.h> + +#include "base/string_util.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/gtk/bookmark_editor_gtk.h" +#include "chrome/browser/gtk/bookmark_tree_model.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::Time; +using base::TimeDelta; +using bookmark_utils::GetTitleFromTreeIter; + +// Base class for bookmark editor tests. This class is a copy from +// bookmark_editor_view_unittest.cc, and all the tests in this file are +// GTK-ifications of the corresponding views tests. Testing here is really +// important because on Linux, we make round trip copies from chrome's +// BookmarkModel class to GTK's native GtkTreeStore. +class BookmarkEditorGtkTest : public testing::Test { + public: + BookmarkEditorGtkTest() : model_(NULL) { + } + + virtual void SetUp() { + profile_.reset(new TestingProfile()); + profile_->set_has_history_service(true); + profile_->CreateBookmarkModel(true); + + model_ = profile_->GetBookmarkModel(); + + AddTestData(); + } + + virtual void TearDown() { + } + + protected: + MessageLoopForUI message_loop_; + BookmarkModel* model_; + scoped_ptr<TestingProfile> profile_; + + std::string base_path() const { return "file:///c:/tmp/"; } + + BookmarkNode* GetNode(const std::string& name) { + return model_->GetMostRecentlyAddedNodeForURL(GURL(base_path() + name)); + } + + private: + // Creates the following structure: + // bookmark bar node + // a + // F1 + // f1a + // F11 + // f11a + // F2 + // other node + // oa + // OF1 + // of1a + void AddTestData() { + std::string test_base = base_path(); + + 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"); + + // Children of the other node. + model_->AddURL(model_->other_node(), 0, L"oa", + GURL(test_base + "oa")); + BookmarkNode* of1 = model_->AddGroup(model_->other_node(), 1, L"OF1"); + model_->AddURL(of1, 0, L"of1a", GURL(test_base + "of1a")); + } +}; + +// Makes sure the tree model matches that of the bookmark bar model. +TEST_F(BookmarkEditorGtkTest, ModelsMatch) { + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, NULL, + BookmarkEditor::SHOW_TREE, NULL); + + // The root should have two children, one for the bookmark bar node, + // the other for the 'other bookmarks' folder. + GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); + GtkTreeIter toplevel; + ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &toplevel)); + GtkTreeIter bookmark_bar_node = toplevel; + ASSERT_TRUE(gtk_tree_model_iter_next(store, &toplevel)); + GtkTreeIter other_node = toplevel; + ASSERT_FALSE(gtk_tree_model_iter_next(store, &toplevel)); + + // The bookmark bar should have 2 nodes: folder F1 and F2. + GtkTreeIter f1_iter; + GtkTreeIter child; + ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node)); + ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &bookmark_bar_node)); + f1_iter = child; + ASSERT_EQ(L"F1", GetTitleFromTreeIter(store, &child)); + ASSERT_TRUE(gtk_tree_model_iter_next(store, &child)); + ASSERT_EQ(L"F2", GetTitleFromTreeIter(store, &child)); + ASSERT_FALSE(gtk_tree_model_iter_next(store, &child)); + + // F1 should have one child, F11 + ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f1_iter)); + ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &f1_iter)); + ASSERT_EQ(L"F11", GetTitleFromTreeIter(store, &child)); + ASSERT_FALSE(gtk_tree_model_iter_next(store, &child)); + + // Other node should have one child (OF1). + ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &other_node)); + ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &other_node)); + ASSERT_EQ(L"OF1", GetTitleFromTreeIter(store, &child)); + ASSERT_FALSE(gtk_tree_model_iter_next(store, &child)); +} + +// Changes the title and makes sure parent/visual order doesn't change. +TEST_F(BookmarkEditorGtkTest, EditTitleKeepsPosition) { + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"), + BookmarkEditor::SHOW_TREE, NULL); + gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a"); + + GtkTreeIter bookmark_bar_node; + GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); + ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); + editor.ApplyEdits(&bookmark_bar_node); + + BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode(); + ASSERT_EQ(L"new_a", bb_node->GetChild(0)->GetTitle()); + // The URL shouldn't have changed. + ASSERT_TRUE(GURL(base_path() + "a") == bb_node->GetChild(0)->GetURL()); +} + +// Changes the url and makes sure parent/visual order doesn't change. +TEST_F(BookmarkEditorGtkTest, EditURLKeepsPosition) { + Time node_time = Time::Now() + TimeDelta::FromDays(2); + GetNode("a")->date_added_ = node_time; + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"), + BookmarkEditor::SHOW_TREE, NULL); + gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), + GURL(base_path() + "new_a").spec().c_str()); + + GtkTreeIter bookmark_bar_node; + GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); + ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); + editor.ApplyEdits(&bookmark_bar_node); + + BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode(); + ASSERT_EQ(L"a", bb_node->GetChild(0)->GetTitle()); + // The URL should have changed. + ASSERT_TRUE(GURL(base_path() + "new_a") == bb_node->GetChild(0)->GetURL()); + ASSERT_TRUE(node_time == bb_node->GetChild(0)->date_added()); +} + +// Moves 'a' to be a child of the other node. +TEST_F(BookmarkEditorGtkTest, ChangeParent) { + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"), + BookmarkEditor::SHOW_TREE, NULL); + + GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); + GtkTreeIter gtk_other_node; + ASSERT_TRUE(gtk_tree_model_get_iter_first(store, >k_other_node)); + ASSERT_TRUE(gtk_tree_model_iter_next(store, >k_other_node)); + editor.ApplyEdits(>k_other_node); + + BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node(); + ASSERT_EQ(L"a", other_node->GetChild(2)->GetTitle()); + ASSERT_TRUE(GURL(base_path() + "a") == other_node->GetChild(2)->GetURL()); +} + +// Moves 'a' to be a child of the other node. +// Moves 'a' to be a child of the other node and changes its url to new_a. +TEST_F(BookmarkEditorGtkTest, ChangeParentAndURL) { + Time node_time = Time::Now() + TimeDelta::FromDays(2); + GetNode("a")->date_added_ = node_time; + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"), + BookmarkEditor::SHOW_TREE, NULL); + + gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), + GURL(base_path() + "new_a").spec().c_str()); + + GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); + GtkTreeIter gtk_other_node; + ASSERT_TRUE(gtk_tree_model_get_iter_first(store, >k_other_node)); + ASSERT_TRUE(gtk_tree_model_iter_next(store, >k_other_node)); + editor.ApplyEdits(>k_other_node); + + BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node(); + ASSERT_EQ(L"a", other_node->GetChild(2)->GetTitle()); + ASSERT_TRUE(GURL(base_path() + "new_a") == other_node->GetChild(2)->GetURL()); + ASSERT_TRUE(node_time == other_node->GetChild(2)->date_added()); +} + +// Creates a new folder and moves a node to it. +TEST_F(BookmarkEditorGtkTest, MoveToNewParent) { + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, GetNode("a"), + BookmarkEditor::SHOW_TREE, NULL); + + GtkTreeIter bookmark_bar_node; + GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); + ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); + + // The bookmark bar should have 2 nodes: folder F1 and F2. + GtkTreeIter f2_iter; + ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node)); + ASSERT_TRUE(gtk_tree_model_iter_children(store, &f2_iter, + &bookmark_bar_node)); + ASSERT_TRUE(gtk_tree_model_iter_next(store, &f2_iter)); + + // Create two nodes: "F21" as a child of "F2" and "F211" as a child of "F21". + GtkTreeIter f21_iter; + editor.AddNewGroup(&f2_iter, &f21_iter); + gtk_tree_store_set(editor.tree_store_, &f21_iter, 0, "F21", -1); + GtkTreeIter f211_iter; + editor.AddNewGroup(&f21_iter, &f211_iter); + gtk_tree_store_set(editor.tree_store_, &f211_iter, 0, "F211", -1); + + ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f2_iter)); + + editor.ApplyEdits(&f2_iter); + + BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode(); + BookmarkNode* mf2 = bb_node->GetChild(1); + + // F2 in the model should have two children now: F21 and the node edited. + ASSERT_EQ(2, mf2->GetChildCount()); + // F21 should be first. + ASSERT_EQ(L"F21", mf2->GetChild(0)->GetTitle()); + // Then a. + ASSERT_EQ(L"a", mf2->GetChild(1)->GetTitle()); + + // F21 should have one child, F211. + BookmarkNode* mf21 = mf2->GetChild(0); + ASSERT_EQ(1, mf21->GetChildCount()); + ASSERT_EQ(L"F211", mf21->GetChild(0)->GetTitle()); +} + +// Brings up the editor, creating a new URL on the bookmark bar. +TEST_F(BookmarkEditorGtkTest, NewURL) { + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, NULL, + BookmarkEditor::SHOW_TREE, NULL); + + gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), + GURL(base_path() + "a").spec().c_str()); + gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a"); + + GtkTreeIter bookmark_bar_node; + GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); + ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); + editor.ApplyEdits(&bookmark_bar_node); + + BookmarkNode* bb_node = profile_->GetBookmarkModel()->GetBookmarkBarNode(); + ASSERT_EQ(4, bb_node->GetChildCount()); + + BookmarkNode* new_node = bb_node->GetChild(3); + 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(BookmarkEditorGtkTest, ChangeURLNoTree) { + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, + model_->other_node()->GetChild(0), + BookmarkEditor::NO_TREE, NULL); + + gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), + GURL(base_path() + "a").spec().c_str()); + gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "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(BookmarkEditorGtkTest, ChangeTitleNoTree) { + BookmarkEditorGtk editor(NULL, profile_.get(), NULL, + model_->other_node()->GetChild(0), + BookmarkEditor::NO_TREE, NULL); + gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a"); + + editor.ApplyEdits(); + + 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/gtk/bookmark_tree_model.cc b/chrome/browser/gtk/bookmark_tree_model.cc new file mode 100644 index 0000000..309a9a1 --- /dev/null +++ b/chrome/browser/gtk/bookmark_tree_model.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2009 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/gtk/bookmark_tree_model.h" + +#include <gtk/gtk.h> + +#include "base/string_util.h" +#include "chrome/browser/bookmarks/bookmark_model.h" + +namespace { + +// Helper function for BuildTreeStoreFrom() which recursively inserts data from +// a BookmarkNode tree into a GtkTreeStore. +void RecursiveInsert(BookmarkNode* node, int selected_id, + GtkTreeStore* store, GtkTreeIter* selected_iter, + GtkTreeIter* parent) { + GtkTreeIter iter; + + for (int i = 0; i < node->GetChildCount(); ++i) { + BookmarkNode* child = node->GetChild(i); + if (child->is_folder()) { + gtk_tree_store_append(store, &iter, parent); + gtk_tree_store_set(store, &iter, + 0, WideToUTF8(child->GetTitle()).c_str(), + 1, child->id(), + -1); + if (selected_id && child->id() == selected_id) { + // Save the iterator. Since we're using a GtkTreeStore, we're + // guaranteed that the iterator will remain valid as long as the above + // appended item exists. + *selected_iter = iter; + } + + RecursiveInsert(child, selected_id, store, selected_iter, &iter); + } + } +} + +// Helper function for CommitTreeStoreDifferencesBetween() which recursively +// merges changes back from a GtkTreeStore into a tree of BookmarkNodes. This +// function only works on non-root nodes; our caller handles that special case. +void RecursiveResolve(BookmarkModel* bb_model, BookmarkNode* bb_node, + GtkTreeModel* tree_model, GtkTreeIter* parent_iter, + GtkTreePath* selected_path, + BookmarkNode** selected_node) { + GtkTreePath* current_path = gtk_tree_model_get_path(tree_model, parent_iter); + if (gtk_tree_path_compare(current_path, selected_path) == 0) + *selected_node = bb_node; + gtk_tree_path_free(current_path); + + GtkTreeIter child_iter; + if (gtk_tree_model_iter_children(tree_model, &child_iter, parent_iter)) { + do { + int id = bookmark_utils::GetIdFromTreeIter(tree_model, &child_iter); + std::wstring title = + bookmark_utils::GetTitleFromTreeIter(tree_model, &child_iter); + BookmarkNode* child_bb_node = NULL; + if (id == 0) { + child_bb_node = bb_model->AddGroup(bb_node, bb_node->GetChildCount(), + title); + } else { + // Existing node, reset the title (BBModel ignores changes if the title + // is the same). + for (int j = 0; j < bb_node->GetChildCount(); ++j) { + BookmarkNode* node = bb_node->GetChild(j); + if (node->is_folder() && node->id() == id) { + child_bb_node = node; + break; + } + } + DCHECK(child_bb_node); + bb_model->SetTitle(child_bb_node, title); + } + RecursiveResolve(bb_model, child_bb_node, + tree_model, &child_iter, + selected_path, selected_node); + } while (gtk_tree_model_iter_next(tree_model, &child_iter)); + } +} + +} // namespace + +namespace bookmark_utils { + +void BuildTreeStoreFrom(BookmarkModel* model, int selected_id, + GtkTreeStore** store, GtkTreeIter* selected_iter) { + *store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_INT); + RecursiveInsert(model->root_node(), selected_id, *store, selected_iter, NULL); +} + +BookmarkNode* CommitTreeStoreDifferencesBetween( + BookmarkModel* bb_model, GtkTreeStore* tree_store, GtkTreeIter* selected) { + BookmarkNode* node_to_return = NULL; + GtkTreeModel* tree_model = GTK_TREE_MODEL(tree_store); + + GtkTreePath* selected_path = gtk_tree_model_get_path(tree_model, selected); + + GtkTreeIter tree_root; + if (!gtk_tree_model_get_iter_first(tree_model, &tree_root)) + NOTREACHED() << "Impossible missing bookmarks case"; + + // The top level of this tree is weird and needs to be special cased. The + // BookmarksNode tree is rooted on a root node while the GtkTreeStore has a + // set of top level nodes that are the root BookmarksNode's children. These + // items in the top level are not editable and therefore don't need the extra + // complexity of trying to modify their title. + BookmarkNode* root_node = bb_model->root_node(); + do { + DCHECK(GetIdFromTreeIter(tree_model, &tree_root) != 0) + << "It should be impossible to add another toplevel node"; + + int id = GetIdFromTreeIter(tree_model, &tree_root); + BookmarkNode* child_node = NULL; + for (int j = 0; j < root_node->GetChildCount(); ++j) { + BookmarkNode* node = root_node->GetChild(j); + if (node->is_folder() && node->id() == id) { + child_node = node; + break; + } + } + DCHECK(child_node); + + GtkTreeIter child_iter = tree_root; + RecursiveResolve(bb_model, child_node, tree_model, &child_iter, + selected_path, &node_to_return); + } while (gtk_tree_model_iter_next(tree_model, &tree_root)); + + gtk_tree_path_free(selected_path); + return node_to_return; +} + +int GetIdFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter) { + GValue value = { 0, }; + int ret_val = -1; + gtk_tree_model_get_value(model, iter, 1, &value); + if (G_VALUE_HOLDS_INT(&value)) + ret_val = g_value_get_int(&value); + else + NOTREACHED() << "Impossible type mismatch"; + + return ret_val; +} + +std::wstring GetTitleFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter) { + GValue value = { 0, }; + std::wstring ret_val; + gtk_tree_model_get_value(model, iter, 0, &value); + if (G_VALUE_HOLDS_STRING(&value)) { + const gchar* utf8str = g_value_get_string(&value); + ret_val = UTF8ToWide(utf8str); + g_value_unset(&value); + } else { + NOTREACHED() << "Impossible type mismatch"; + } + + return ret_val; +} + +} // namespace bookmark_utils diff --git a/chrome/browser/gtk/bookmark_tree_model.h b/chrome/browser/gtk/bookmark_tree_model.h new file mode 100644 index 0000000..d86165a --- /dev/null +++ b/chrome/browser/gtk/bookmark_tree_model.h @@ -0,0 +1,43 @@ +// Copyright (c) 2009 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_GTK_BOOKMARK_TREE_MODEL_H_ +#define CHROME_BROWSER_GTK_BOOKMARK_TREE_MODEL_H_ + +#include <string> + +class BookmarkModel; +class BookmarkNode; + +typedef struct _GtkTreeIter GtkTreeIter; +typedef struct _GtkTreeModel GtkTreeModel; +typedef struct _GtkTreeStore GtkTreeStore; + +namespace bookmark_utils { + +// Copies the tree of folders from the BookmarkModel into a GtkTreeStore. We +// want the user to be able to modify the tree of folders, but to be able to +// click Cancel and discard their modifications. |selected_id| is the +// node->id() of the BookmarkNode that should selected on +// node->screen. |selected_iter| is an out value that points to the +// node->representation of the node associated with |selected_id| in |store|. +void BuildTreeStoreFrom(BookmarkModel* model, int selected_id, + GtkTreeStore** store, GtkTreeIter* selected_iter); + +// Commits changes to a GtkTreeStore built from BuildTreeStoreFrom() back +// into the BookmarkModel it was generated from. Returns the BookmarkNode that +// represented by |selected|. +BookmarkNode* CommitTreeStoreDifferencesBetween( + BookmarkModel* model, GtkTreeStore* tree_store, + GtkTreeIter* selected); + +// Returns the id field of the row pointed to by |iter|. +int GetIdFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter); + +// Returns the title field of the row pointed to by |iter|. +std::wstring GetTitleFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter); + +} // namespace bookmark_utils + +#endif // CHROME_BROWSER_GTK_BOOKMARK_TREE_MODEL_H_ diff --git a/chrome/browser/views/bookmark_editor_view.cc b/chrome/browser/views/bookmark_editor_view.cc index 60d8179..ede739c 100644 --- a/chrome/browser/views/bookmark_editor_view.cc +++ b/chrome/browser/views/bookmark_editor_view.cc @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/string_util.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/history/history.h" #include "chrome/browser/profile.h" #include "chrome/browser/net/url_fixer_upper.h" @@ -24,7 +25,6 @@ #include "grit/generated_resources.h" #include "grit/locale_settings.h" -using base::Time; using views::Button; using views::ColumnSet; using views::GridLayout; @@ -487,30 +487,9 @@ void BookmarkEditorView::ApplyEdits(EditorNode* parent) { GURL new_url(GetInputURL()); std::wstring new_title(GetInputTitle()); - BookmarkNode* old_parent = node_ ? node_->GetParent() : NULL; - const int old_index = old_parent ? old_parent->IndexOfChild(node_) : -1; - if (!show_tree_) { - if (!node_) { - BookmarkNode* node = - bb_model_->AddURL(parent_, parent_->GetChildCount(), new_title, - new_url); - if (handler_.get()) - handler_->NodeCreated(node); - 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); - } + bookmark_utils::ApplyEditsWithNoGroupChange( + bb_model_, parent_, node_, new_title, new_url, handler_.get()); return; } @@ -519,44 +498,8 @@ void BookmarkEditorView::ApplyEdits(EditorNode* parent) { ApplyNameChangesAndCreateNewGroups( bb_model_->root_node(), tree_model_->GetRoot(), parent, &new_parent); - if (!new_parent) { - // Bookmarks must be parented. - NOTREACHED(); - return; - } - - if (node_) { - Time date_added = node_->date_added(); - if (new_parent == node_->GetParent()) { - // The parent is the same. - if (new_url != node_->GetURL()) { - bb_model_->Remove(old_parent, old_index); - BookmarkNode* new_node = - bb_model_->AddURL(old_parent, old_index, new_title, new_url); - new_node->date_added_ = date_added; - } else { - bb_model_->SetTitle(node_, new_title); - } - } else if (new_url != node_->GetURL()) { - // The parent and URL changed. - bb_model_->Remove(old_parent, old_index); - BookmarkNode* new_node = - bb_model_->AddURL(new_parent, new_parent->GetChildCount(), new_title, - new_url); - new_node->date_added_ = date_added; - } else { - // The parent and title changed. Move the node and change the title. - bb_model_->Move(node_, new_parent, new_parent->GetChildCount()); - bb_model_->SetTitle(node_, new_title); - } - } else { - // We're adding a new URL. - BookmarkNode* node = - bb_model_->AddURL(new_parent, new_parent->GetChildCount(), new_title, - new_url); - if (handler_.get()) - handler_->NodeCreated(node); - } + bookmark_utils::ApplyEditsWithPossibleGroupChange( + bb_model_, new_parent, node_, new_title, new_url, handler_.get()); } void BookmarkEditorView::ApplyNameChangesAndCreateNewGroups( diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 7d5f696..649cb03 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -806,6 +806,8 @@ 'browser/gtk/bookmark_bubble_gtk.h', 'browser/gtk/bookmark_editor_gtk.cc', 'browser/gtk/bookmark_editor_gtk.h', + 'browser/gtk/bookmark_tree_model.cc', + 'browser/gtk/bookmark_tree_model.h', 'browser/gtk/browser_toolbar_gtk.cc', 'browser/gtk/browser_toolbar_gtk.h', 'browser/gtk/browser_window_factory_gtk.cc', @@ -2378,6 +2380,7 @@ 'browser/extensions/test_extension_loader.cc', 'browser/extensions/user_script_master_unittest.cc', 'browser/google_url_tracker_unittest.cc', + 'browser/gtk/bookmark_editor_gtk_unittest.cc', 'browser/gtk/go_button_gtk_unittest.cc', 'browser/gtk/tabs/tab_renderer_gtk_unittest.cc', 'browser/history/expire_history_backend_unittest.cc', |