summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-05 00:35:09 +0000
committererg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-05 00:35:09 +0000
commit140aea057fa118579380f685f362f2533862b03f (patch)
tree627c15d8adda34f2c602589e0bac815bbdc7d4d9
parent57e3074163dc21bc3bc8cd37dc05f191532d836a (diff)
downloadchromium_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.h6
-rw-r--r--chrome/browser/bookmarks/bookmark_utils.cc78
-rw-r--r--chrome/browser/bookmarks/bookmark_utils.h19
-rw-r--r--chrome/browser/gtk/bookmark_editor_gtk.cc154
-rw-r--r--chrome/browser/gtk/bookmark_editor_gtk.h35
-rw-r--r--chrome/browser/gtk/bookmark_editor_gtk_unittest.cc304
-rw-r--r--chrome/browser/gtk/bookmark_tree_model.cc161
-rw-r--r--chrome/browser/gtk/bookmark_tree_model.h43
-rw-r--r--chrome/browser/views/bookmark_editor_view.cc67
-rw-r--r--chrome/chrome.gyp3
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,
+ &currently_selected_iter)) {
+ NOTREACHED() << "Something should always be selected";
+ return;
+ }
+ }
+ ApplyEdits(&currently_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, &gtk_other_node));
+ ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
+ editor.ApplyEdits(&gtk_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, &gtk_other_node));
+ ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
+ editor.ApplyEdits(&gtk_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',