Home | History | Annotate | Download | only in bookmarks
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/views/bookmarks/bookmark_editor_view.h"
      6 
      7 #include <string>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/logging.h"
     11 #include "base/prefs/pref_service.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
     15 #include "chrome/browser/history/history_service.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
     18 #include "chrome/browser/ui/views/constrained_window_views.h"
     19 #include "components/bookmarks/browser/bookmark_model.h"
     20 #include "components/bookmarks/browser/bookmark_utils.h"
     21 #include "components/url_fixer/url_fixer.h"
     22 #include "components/user_prefs/user_prefs.h"
     23 #include "grit/chromium_strings.h"
     24 #include "grit/generated_resources.h"
     25 #include "grit/locale_settings.h"
     26 #include "ui/accessibility/ax_view_state.h"
     27 #include "ui/base/l10n/l10n_util.h"
     28 #include "ui/events/event.h"
     29 #include "ui/views/background.h"
     30 #include "ui/views/controls/button/label_button.h"
     31 #include "ui/views/controls/label.h"
     32 #include "ui/views/controls/menu/menu_runner.h"
     33 #include "ui/views/controls/textfield/textfield.h"
     34 #include "ui/views/controls/tree/tree_view.h"
     35 #include "ui/views/focus/focus_manager.h"
     36 #include "ui/views/layout/grid_layout.h"
     37 #include "ui/views/layout/layout_constants.h"
     38 #include "ui/views/widget/widget.h"
     39 #include "ui/views/window/dialog_client_view.h"
     40 #include "url/gurl.h"
     41 
     42 using bookmarks::BookmarkExpandedStateTracker;
     43 using views::GridLayout;
     44 
     45 namespace {
     46 
     47 // Background color of text field when URL is invalid.
     48 const SkColor kErrorColor = SkColorSetRGB(0xFF, 0xBC, 0xBC);
     49 
     50 }  // namespace
     51 
     52 // static
     53 void BookmarkEditor::Show(gfx::NativeWindow parent_window,
     54                           Profile* profile,
     55                           const EditDetails& details,
     56                           Configuration configuration) {
     57   DCHECK(profile);
     58   BookmarkEditorView* editor = new BookmarkEditorView(profile,
     59       details.parent_node, details, configuration);
     60   editor->Show(parent_window);
     61 }
     62 
     63 BookmarkEditorView::BookmarkEditorView(
     64     Profile* profile,
     65     const BookmarkNode* parent,
     66     const EditDetails& details,
     67     BookmarkEditor::Configuration configuration)
     68     : profile_(profile),
     69       tree_view_(NULL),
     70       url_label_(NULL),
     71       url_tf_(NULL),
     72       title_label_(NULL),
     73       title_tf_(NULL),
     74       parent_(parent),
     75       details_(details),
     76       bb_model_(BookmarkModelFactory::GetForProfile(profile)),
     77       running_menu_for_root_(false),
     78       show_tree_(configuration == SHOW_TREE) {
     79   DCHECK(profile);
     80   DCHECK(bb_model_);
     81   DCHECK(bb_model_->client()->CanBeEditedByUser(parent));
     82   Init();
     83 }
     84 
     85 BookmarkEditorView::~BookmarkEditorView() {
     86   // The tree model is deleted before the view. Reset the model otherwise the
     87   // tree will reference a deleted model.
     88   if (tree_view_)
     89     tree_view_->SetModel(NULL);
     90   bb_model_->RemoveObserver(this);
     91 }
     92 
     93 base::string16 BookmarkEditorView::GetDialogButtonLabel(
     94     ui::DialogButton button) const {
     95   if (button == ui::DIALOG_BUTTON_OK)
     96     return l10n_util::GetStringUTF16(IDS_SAVE);
     97   return views::DialogDelegateView::GetDialogButtonLabel(button);
     98 }
     99 
    100 bool BookmarkEditorView::IsDialogButtonEnabled(ui::DialogButton button) const {
    101   if (button == ui::DIALOG_BUTTON_OK) {
    102     if (!bb_model_->loaded())
    103       return false;
    104 
    105     if (details_.GetNodeType() != BookmarkNode::FOLDER)
    106       return GetInputURL().is_valid();
    107   }
    108   return true;
    109 }
    110 
    111 views::View* BookmarkEditorView::CreateExtraView() {
    112   return new_folder_button_.get();
    113 }
    114 
    115 ui::ModalType BookmarkEditorView::GetModalType() const {
    116   return ui::MODAL_TYPE_WINDOW;
    117 }
    118 
    119 bool BookmarkEditorView::CanResize() const {
    120   return true;
    121 }
    122 
    123 base::string16 BookmarkEditorView::GetWindowTitle() const {
    124   return l10n_util::GetStringUTF16(details_.GetWindowTitleId());
    125 }
    126 
    127 bool BookmarkEditorView::Accept() {
    128   if (!IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK)) {
    129     if (details_.GetNodeType() != BookmarkNode::FOLDER) {
    130       // The url is invalid, focus the url field.
    131       url_tf_->SelectAll(true);
    132       url_tf_->RequestFocus();
    133     }
    134     return false;
    135   }
    136   // Otherwise save changes and close the dialog box.
    137   ApplyEdits();
    138   return true;
    139 }
    140 
    141 gfx::Size BookmarkEditorView::GetPreferredSize() const {
    142   if (!show_tree_)
    143     return views::View::GetPreferredSize();
    144 
    145   return gfx::Size(views::Widget::GetLocalizedContentsSize(
    146       IDS_EDITBOOKMARK_DIALOG_WIDTH_CHARS,
    147       IDS_EDITBOOKMARK_DIALOG_HEIGHT_LINES));
    148 }
    149 
    150 void BookmarkEditorView::OnTreeViewSelectionChanged(
    151     views::TreeView* tree_view) {
    152 }
    153 
    154 bool BookmarkEditorView::CanEdit(views::TreeView* tree_view,
    155                                  ui::TreeModelNode* node) {
    156   // Only allow editting of children of the bookmark bar node and other node.
    157   EditorNode* bb_node = tree_model_->AsNode(node);
    158   return (bb_node->parent() && bb_node->parent()->parent());
    159 }
    160 
    161 void BookmarkEditorView::ContentsChanged(views::Textfield* sender,
    162                                          const base::string16& new_contents) {
    163   UserInputChanged();
    164 }
    165 
    166 bool BookmarkEditorView::HandleKeyEvent(views::Textfield* sender,
    167                                         const ui::KeyEvent& key_event) {
    168     return false;
    169 }
    170 
    171 void BookmarkEditorView::GetAccessibleState(ui::AXViewState* state) {
    172   state->name =
    173       l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_TITLE);
    174   state->role = ui::AX_ROLE_DIALOG;
    175 }
    176 
    177 void BookmarkEditorView::ButtonPressed(views::Button* sender,
    178                                        const ui::Event& event) {
    179   DCHECK_EQ(new_folder_button_.get(), sender);
    180   NewFolder();
    181 }
    182 
    183 bool BookmarkEditorView::IsCommandIdChecked(int command_id) const {
    184   return false;
    185 }
    186 
    187 bool BookmarkEditorView::IsCommandIdEnabled(int command_id) const {
    188   switch (command_id) {
    189     case IDS_EDIT:
    190     case IDS_DELETE:
    191       return !running_menu_for_root_;
    192     case IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM:
    193       return true;
    194     default:
    195       NOTREACHED();
    196       return false;
    197   }
    198 }
    199 
    200 bool BookmarkEditorView::GetAcceleratorForCommandId(
    201     int command_id,
    202     ui::Accelerator* accelerator) {
    203   return GetWidget()->GetAccelerator(command_id, accelerator);
    204 }
    205 
    206 void BookmarkEditorView::ExecuteCommand(int command_id, int event_flags) {
    207   DCHECK(tree_view_->GetSelectedNode());
    208   if (command_id == IDS_EDIT) {
    209     tree_view_->StartEditing(tree_view_->GetSelectedNode());
    210   } else if (command_id == IDS_DELETE) {
    211     EditorNode* node = tree_model_->AsNode(tree_view_->GetSelectedNode());
    212     if (!node)
    213       return;
    214     if (node->value != 0) {
    215       const BookmarkNode* b_node = GetBookmarkNodeByID(bb_model_, node->value);
    216       if (!b_node->empty() &&
    217           !chrome::ConfirmDeleteBookmarkNode(b_node,
    218             GetWidget()->GetNativeWindow())) {
    219         // The folder is not empty and the user didn't confirm.
    220         return;
    221       }
    222       deletes_.push_back(node->value);
    223     }
    224     tree_model_->Remove(node->parent(), node);
    225   } else {
    226     DCHECK_EQ(IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM, command_id);
    227     NewFolder();
    228   }
    229 }
    230 
    231 void BookmarkEditorView::Show(gfx::NativeWindow parent) {
    232   CreateBrowserModalDialogViews(this, parent);
    233   UserInputChanged();
    234   if (show_tree_ && bb_model_->loaded())
    235     ExpandAndSelect();
    236   GetWidget()->Show();
    237   // Select all the text in the name Textfield.
    238   title_tf_->SelectAll(true);
    239   // Give focus to the name Textfield.
    240   title_tf_->RequestFocus();
    241 }
    242 
    243 void BookmarkEditorView::ShowContextMenuForView(
    244     views::View* source,
    245     const gfx::Point& point,
    246     ui::MenuSourceType source_type) {
    247   DCHECK_EQ(tree_view_, source);
    248   if (!tree_view_->GetSelectedNode())
    249     return;
    250   running_menu_for_root_ =
    251       (tree_model_->GetParent(tree_view_->GetSelectedNode()) ==
    252        tree_model_->GetRoot());
    253 
    254   context_menu_runner_.reset(new views::MenuRunner(GetMenuModel()));
    255 
    256   if (context_menu_runner_->RunMenuAt(
    257           source->GetWidget()->GetTopLevelWidget(),
    258           NULL,
    259           gfx::Rect(point, gfx::Size()),
    260           views::MENU_ANCHOR_TOPRIGHT,
    261           source_type,
    262           views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU) ==
    263       views::MenuRunner::MENU_DELETED) {
    264     return;
    265   }
    266 }
    267 
    268 void BookmarkEditorView::Init() {
    269   bb_model_->AddObserver(this);
    270 
    271   title_label_ = new views::Label(
    272       l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NAME_LABEL));
    273 
    274   base::string16 title;
    275   GURL url;
    276   if (details_.type == EditDetails::EXISTING_NODE) {
    277     title = details_.existing_node->GetTitle();
    278     url = details_.existing_node->url();
    279   } else if (details_.type == EditDetails::NEW_FOLDER) {
    280     title = l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME);
    281   } else if (details_.type == EditDetails::NEW_URL) {
    282     url = details_.url;
    283     title = details_.title;
    284   }
    285   title_tf_ = new views::Textfield;
    286   title_tf_->SetAccessibleName(
    287       l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_EDITOR_NAME_LABEL));
    288   title_tf_->SetText(title);
    289   title_tf_->set_controller(this);
    290 
    291   if (show_tree_) {
    292     tree_view_ = new views::TreeView;
    293     tree_view_->SetRootShown(false);
    294     tree_view_->set_context_menu_controller(this);
    295 
    296     new_folder_button_.reset(new views::LabelButton(this,
    297         l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_BUTTON)));
    298     new_folder_button_->SetStyle(views::Button::STYLE_BUTTON);
    299     new_folder_button_->set_owned_by_client();
    300     new_folder_button_->SetEnabled(false);
    301   }
    302 
    303   GridLayout* layout = GridLayout::CreatePanel(this);
    304   SetLayoutManager(layout);
    305 
    306   const int labels_column_set_id = 0;
    307   const int single_column_view_set_id = 1;
    308   const int buttons_column_set_id = 2;
    309 
    310   views::ColumnSet* column_set = layout->AddColumnSet(labels_column_set_id);
    311   column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
    312                         GridLayout::USE_PREF, 0, 0);
    313   column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    314   column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
    315                         GridLayout::USE_PREF, 0, 0);
    316 
    317   column_set = layout->AddColumnSet(single_column_view_set_id);
    318   column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
    319                         GridLayout::USE_PREF, 0, 0);
    320 
    321   column_set = layout->AddColumnSet(buttons_column_set_id);
    322   column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
    323                         GridLayout::USE_PREF, 0, 0);
    324   column_set->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
    325   column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
    326                         GridLayout::USE_PREF, 0, 0);
    327   column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    328   column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
    329                         GridLayout::USE_PREF, 0, 0);
    330   column_set->LinkColumnSizes(0, 2, 4, -1);
    331 
    332   layout->StartRow(0, labels_column_set_id);
    333   layout->AddView(title_label_);
    334   layout->AddView(title_tf_);
    335 
    336   if (details_.GetNodeType() != BookmarkNode::FOLDER) {
    337     url_label_ = new views::Label(
    338         l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_URL_LABEL));
    339 
    340     url_tf_ = new views::Textfield;
    341     PrefService* prefs =
    342         profile_ ? user_prefs::UserPrefs::Get(profile_) : NULL;
    343     url_tf_->SetText(chrome::FormatBookmarkURLForDisplay(url, prefs));
    344     url_tf_->set_controller(this);
    345     url_tf_->SetAccessibleName(
    346         l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_EDITOR_URL_LABEL));
    347 
    348     layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    349 
    350     layout->StartRow(0, labels_column_set_id);
    351     layout->AddView(url_label_);
    352     layout->AddView(url_tf_);
    353   }
    354 
    355   if (show_tree_) {
    356     layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    357     layout->StartRow(1, single_column_view_set_id);
    358     layout->AddView(tree_view_->CreateParentIfNecessary());
    359   }
    360 
    361   layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    362 
    363   if (!show_tree_ || bb_model_->loaded())
    364     Reset();
    365 }
    366 
    367 void BookmarkEditorView::BookmarkNodeMoved(BookmarkModel* model,
    368                                            const BookmarkNode* old_parent,
    369                                            int old_index,
    370                                            const BookmarkNode* new_parent,
    371                                            int new_index) {
    372   Reset();
    373 }
    374 
    375 void BookmarkEditorView::BookmarkNodeAdded(BookmarkModel* model,
    376                                            const BookmarkNode* parent,
    377                                            int index) {
    378   Reset();
    379 }
    380 
    381 void BookmarkEditorView::BookmarkNodeRemoved(
    382     BookmarkModel* model,
    383     const BookmarkNode* parent,
    384     int index,
    385     const BookmarkNode* node,
    386     const std::set<GURL>& removed_urls) {
    387   if ((details_.type == EditDetails::EXISTING_NODE &&
    388        details_.existing_node->HasAncestor(node)) ||
    389       (parent_ && parent_->HasAncestor(node))) {
    390     // The node, or its parent was removed. Close the dialog.
    391     GetWidget()->Close();
    392   } else {
    393     Reset();
    394   }
    395 }
    396 
    397 void BookmarkEditorView::BookmarkAllUserNodesRemoved(
    398     BookmarkModel* model,
    399     const std::set<GURL>& removed_urls) {
    400   Reset();
    401 }
    402 
    403 void BookmarkEditorView::BookmarkNodeChildrenReordered(
    404     BookmarkModel* model,
    405     const BookmarkNode* node) {
    406   Reset();
    407 }
    408 
    409 void BookmarkEditorView::Reset() {
    410   if (!show_tree_) {
    411     if (parent())
    412       UserInputChanged();
    413     return;
    414   }
    415 
    416   new_folder_button_->SetEnabled(true);
    417 
    418   // Do this first, otherwise when we invoke SetModel with the real one
    419   // tree_view will try to invoke something on the model we just deleted.
    420   tree_view_->SetModel(NULL);
    421 
    422   EditorNode* root_node = CreateRootNode();
    423   tree_model_.reset(new EditorTreeModel(root_node));
    424 
    425   tree_view_->SetModel(tree_model_.get());
    426   tree_view_->SetController(this);
    427 
    428   context_menu_runner_.reset();
    429 
    430   if (parent())
    431     ExpandAndSelect();
    432 }
    433 
    434 GURL BookmarkEditorView::GetInputURL() const {
    435   if (details_.GetNodeType() == BookmarkNode::FOLDER)
    436     return GURL();
    437   return url_fixer::FixupURL(base::UTF16ToUTF8(url_tf_->text()), std::string());
    438 }
    439 
    440 void BookmarkEditorView::UserInputChanged() {
    441   if (details_.GetNodeType() != BookmarkNode::FOLDER) {
    442     const GURL url(GetInputURL());
    443     if (!url.is_valid())
    444       url_tf_->SetBackgroundColor(kErrorColor);
    445     else
    446       url_tf_->UseDefaultBackgroundColor();
    447   }
    448   GetDialogClientView()->UpdateDialogButtons();
    449 }
    450 
    451 void BookmarkEditorView::NewFolder() {
    452   // Create a new entry parented to the selected item, or the bookmark
    453   // bar if nothing is selected.
    454   EditorNode* parent = tree_model_->AsNode(tree_view_->GetSelectedNode());
    455   if (!parent) {
    456     NOTREACHED();
    457     return;
    458   }
    459 
    460   tree_view_->StartEditing(AddNewFolder(parent));
    461 }
    462 
    463 BookmarkEditorView::EditorNode* BookmarkEditorView::AddNewFolder(
    464     EditorNode* parent) {
    465   EditorNode* new_node = new EditorNode(
    466       l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME), 0);
    467   // |new_node| is now owned by |parent|.
    468   tree_model_->Add(parent, new_node, parent->child_count());
    469   return new_node;
    470 }
    471 
    472 void BookmarkEditorView::ExpandAndSelect() {
    473   BookmarkExpandedStateTracker::Nodes expanded_nodes =
    474       bb_model_->expanded_state_tracker()->GetExpandedNodes();
    475   for (BookmarkExpandedStateTracker::Nodes::const_iterator i(
    476        expanded_nodes.begin()); i != expanded_nodes.end(); ++i) {
    477     EditorNode* editor_node =
    478         FindNodeWithID(tree_model_->GetRoot(), (*i)->id());
    479     if (editor_node)
    480       tree_view_->Expand(editor_node);
    481   }
    482 
    483   const BookmarkNode* to_select = parent_;
    484   if (details_.type == EditDetails::EXISTING_NODE)
    485     to_select = details_.existing_node->parent();
    486   int64 folder_id_to_select = to_select->id();
    487   EditorNode* b_node =
    488       FindNodeWithID(tree_model_->GetRoot(), folder_id_to_select);
    489   if (!b_node)
    490     b_node = tree_model_->GetRoot()->GetChild(0);  // Bookmark bar node.
    491 
    492   tree_view_->SetSelectedNode(b_node);
    493 }
    494 
    495 BookmarkEditorView::EditorNode* BookmarkEditorView::CreateRootNode() {
    496   EditorNode* root_node = new EditorNode(base::string16(), 0);
    497   const BookmarkNode* bb_root_node = bb_model_->root_node();
    498   CreateNodes(bb_root_node, root_node);
    499   DCHECK(root_node->child_count() >= 2 && root_node->child_count() <= 4);
    500   DCHECK_EQ(BookmarkNode::BOOKMARK_BAR, bb_root_node->GetChild(0)->type());
    501   DCHECK_EQ(BookmarkNode::OTHER_NODE, bb_root_node->GetChild(1)->type());
    502   if (root_node->child_count() >= 3)
    503     DCHECK_EQ(BookmarkNode::MOBILE, bb_root_node->GetChild(2)->type());
    504   return root_node;
    505 }
    506 
    507 void BookmarkEditorView::CreateNodes(const BookmarkNode* bb_node,
    508                                      BookmarkEditorView::EditorNode* b_node) {
    509   for (int i = 0; i < bb_node->child_count(); ++i) {
    510     const BookmarkNode* child_bb_node = bb_node->GetChild(i);
    511     if (child_bb_node->IsVisible() && child_bb_node->is_folder() &&
    512         bb_model_->client()->CanBeEditedByUser(child_bb_node)) {
    513       EditorNode* new_b_node = new EditorNode(child_bb_node->GetTitle(),
    514                                               child_bb_node->id());
    515       b_node->Add(new_b_node, b_node->child_count());
    516       CreateNodes(child_bb_node, new_b_node);
    517     }
    518   }
    519 }
    520 
    521 BookmarkEditorView::EditorNode* BookmarkEditorView::FindNodeWithID(
    522     BookmarkEditorView::EditorNode* node,
    523     int64 id) {
    524   if (node->value == id)
    525     return node;
    526   for (int i = 0; i < node->child_count(); ++i) {
    527     EditorNode* result = FindNodeWithID(node->GetChild(i), id);
    528     if (result)
    529       return result;
    530   }
    531   return NULL;
    532 }
    533 
    534 void BookmarkEditorView::ApplyEdits() {
    535   DCHECK(bb_model_->loaded());
    536 
    537   if (tree_view_)
    538     tree_view_->CommitEdit();
    539 
    540   EditorNode* parent = show_tree_ ?
    541       tree_model_->AsNode(tree_view_->GetSelectedNode()) : NULL;
    542   if (show_tree_ && !parent) {
    543     NOTREACHED();
    544     return;
    545   }
    546   ApplyEdits(parent);
    547 }
    548 
    549 void BookmarkEditorView::ApplyEdits(EditorNode* parent) {
    550   DCHECK(!show_tree_ || parent);
    551 
    552   // We're going to apply edits to the bookmark bar model, which will call us
    553   // back. Normally when a structural edit occurs we reset the tree model.
    554   // We don't want to do that here, so we remove ourselves as an observer.
    555   bb_model_->RemoveObserver(this);
    556 
    557   GURL new_url(GetInputURL());
    558   base::string16 new_title(title_tf_->text());
    559 
    560   if (!show_tree_) {
    561     BookmarkEditor::ApplyEditsWithNoFolderChange(
    562         bb_model_, parent_, details_, new_title, new_url);
    563     return;
    564   }
    565 
    566   // Create the new folders and update the titles.
    567   const BookmarkNode* new_parent = NULL;
    568   ApplyNameChangesAndCreateNewFolders(
    569       bb_model_->root_node(), tree_model_->GetRoot(), parent, &new_parent);
    570 
    571   BookmarkEditor::ApplyEditsWithPossibleFolderChange(
    572       bb_model_, new_parent, details_, new_title, new_url);
    573 
    574   BookmarkExpandedStateTracker::Nodes expanded_nodes;
    575   UpdateExpandedNodes(tree_model_->GetRoot(), &expanded_nodes);
    576   bb_model_->expanded_state_tracker()->SetExpandedNodes(expanded_nodes);
    577 
    578   // Remove the folders that were removed. This has to be done after all the
    579   // other changes have been committed.
    580   bookmark_utils::DeleteBookmarkFolders(bb_model_, deletes_);
    581 }
    582 
    583 void BookmarkEditorView::ApplyNameChangesAndCreateNewFolders(
    584     const BookmarkNode* bb_node,
    585     BookmarkEditorView::EditorNode* b_node,
    586     BookmarkEditorView::EditorNode* parent_b_node,
    587     const BookmarkNode** parent_bb_node) {
    588   if (parent_b_node == b_node)
    589     *parent_bb_node = bb_node;
    590   for (int i = 0; i < b_node->child_count(); ++i) {
    591     EditorNode* child_b_node = b_node->GetChild(i);
    592     const BookmarkNode* child_bb_node = NULL;
    593     if (child_b_node->value == 0) {
    594       // New folder.
    595       child_bb_node = bb_model_->AddFolder(bb_node,
    596           bb_node->child_count(), child_b_node->GetTitle());
    597       child_b_node->value = child_bb_node->id();
    598     } else {
    599       // Existing node, reset the title (BookmarkModel ignores changes if the
    600       // title is the same).
    601       for (int j = 0; j < bb_node->child_count(); ++j) {
    602         const BookmarkNode* node = bb_node->GetChild(j);
    603         if (node->is_folder() && node->id() == child_b_node->value) {
    604           child_bb_node = node;
    605           break;
    606         }
    607       }
    608       DCHECK(child_bb_node);
    609       bb_model_->SetTitle(child_bb_node, child_b_node->GetTitle());
    610     }
    611     ApplyNameChangesAndCreateNewFolders(child_bb_node, child_b_node,
    612                                         parent_b_node, parent_bb_node);
    613   }
    614 }
    615 
    616 void BookmarkEditorView::UpdateExpandedNodes(
    617     EditorNode* editor_node,
    618     BookmarkExpandedStateTracker::Nodes* expanded_nodes) {
    619   if (!tree_view_->IsExpanded(editor_node))
    620     return;
    621 
    622   if (editor_node->value != 0)  // The root is 0.
    623     expanded_nodes->insert(GetBookmarkNodeByID(bb_model_, editor_node->value));
    624 
    625   for (int i = 0; i < editor_node->child_count(); ++i)
    626     UpdateExpandedNodes(editor_node->GetChild(i), expanded_nodes);
    627 }
    628 
    629 ui::SimpleMenuModel* BookmarkEditorView::GetMenuModel() {
    630   if (!context_menu_model_.get()) {
    631     context_menu_model_.reset(new ui::SimpleMenuModel(this));
    632     context_menu_model_->AddItemWithStringId(IDS_EDIT, IDS_EDIT);
    633     context_menu_model_->AddItemWithStringId(IDS_DELETE, IDS_DELETE);
    634     context_menu_model_->AddItemWithStringId(
    635         IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM,
    636         IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM);
    637   }
    638   return context_menu_model_.get();
    639 }
    640 
    641 void BookmarkEditorView::EditorTreeModel::SetTitle(
    642     ui::TreeModelNode* node,
    643     const base::string16& title) {
    644   if (!title.empty())
    645     ui::TreeNodeModel<EditorNode>::SetTitle(node, title);
    646 }
    647