bookmark_editor_view.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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 "chrome/grit/generated_resources.h"
20#include "chrome/grit/locale_settings.h"
21#include "components/bookmarks/browser/bookmark_model.h"
22#include "components/bookmarks/browser/bookmark_utils.h"
23#include "components/url_fixer/url_fixer.h"
24#include "components/user_prefs/user_prefs.h"
25#include "ui/accessibility/ax_view_state.h"
26#include "ui/base/l10n/l10n_util.h"
27#include "ui/events/event.h"
28#include "ui/views/background.h"
29#include "ui/views/controls/button/label_button.h"
30#include "ui/views/controls/label.h"
31#include "ui/views/controls/menu/menu_runner.h"
32#include "ui/views/controls/textfield/textfield.h"
33#include "ui/views/controls/tree/tree_view.h"
34#include "ui/views/focus/focus_manager.h"
35#include "ui/views/layout/grid_layout.h"
36#include "ui/views/layout/layout_constants.h"
37#include "ui/views/widget/widget.h"
38#include "ui/views/window/dialog_client_view.h"
39#include "url/gurl.h"
40
41using bookmarks::BookmarkExpandedStateTracker;
42using views::GridLayout;
43
44namespace {
45
46// Background color of text field when URL is invalid.
47const SkColor kErrorColor = SkColorSetRGB(0xFF, 0xBC, 0xBC);
48
49}  // namespace
50
51// static
52void BookmarkEditor::Show(gfx::NativeWindow parent_window,
53                          Profile* profile,
54                          const EditDetails& details,
55                          Configuration configuration) {
56  DCHECK(profile);
57  BookmarkEditorView* editor = new BookmarkEditorView(profile,
58      details.parent_node, details, configuration);
59  editor->Show(parent_window);
60}
61
62BookmarkEditorView::BookmarkEditorView(
63    Profile* profile,
64    const BookmarkNode* parent,
65    const EditDetails& details,
66    BookmarkEditor::Configuration configuration)
67    : profile_(profile),
68      tree_view_(NULL),
69      url_label_(NULL),
70      url_tf_(NULL),
71      title_label_(NULL),
72      title_tf_(NULL),
73      parent_(parent),
74      details_(details),
75      bb_model_(BookmarkModelFactory::GetForProfile(profile)),
76      running_menu_for_root_(false),
77      show_tree_(configuration == SHOW_TREE) {
78  DCHECK(profile);
79  DCHECK(bb_model_);
80  DCHECK(bb_model_->client()->CanBeEditedByUser(parent));
81  Init();
82}
83
84BookmarkEditorView::~BookmarkEditorView() {
85  // The tree model is deleted before the view. Reset the model otherwise the
86  // tree will reference a deleted model.
87  if (tree_view_)
88    tree_view_->SetModel(NULL);
89  bb_model_->RemoveObserver(this);
90}
91
92base::string16 BookmarkEditorView::GetDialogButtonLabel(
93    ui::DialogButton button) const {
94  if (button == ui::DIALOG_BUTTON_OK)
95    return l10n_util::GetStringUTF16(IDS_SAVE);
96  return views::DialogDelegateView::GetDialogButtonLabel(button);
97}
98
99bool BookmarkEditorView::IsDialogButtonEnabled(ui::DialogButton button) const {
100  if (button == ui::DIALOG_BUTTON_OK) {
101    if (!bb_model_->loaded())
102      return false;
103
104    if (details_.GetNodeType() != BookmarkNode::FOLDER)
105      return GetInputURL().is_valid();
106  }
107  return true;
108}
109
110views::View* BookmarkEditorView::CreateExtraView() {
111  return new_folder_button_.get();
112}
113
114ui::ModalType BookmarkEditorView::GetModalType() const {
115  return ui::MODAL_TYPE_WINDOW;
116}
117
118bool BookmarkEditorView::CanResize() const {
119  return true;
120}
121
122base::string16 BookmarkEditorView::GetWindowTitle() const {
123  return l10n_util::GetStringUTF16(details_.GetWindowTitleId());
124}
125
126bool BookmarkEditorView::Accept() {
127  if (!IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK)) {
128    if (details_.GetNodeType() != BookmarkNode::FOLDER) {
129      // The url is invalid, focus the url field.
130      url_tf_->SelectAll(true);
131      url_tf_->RequestFocus();
132    }
133    return false;
134  }
135  // Otherwise save changes and close the dialog box.
136  ApplyEdits();
137  return true;
138}
139
140gfx::Size BookmarkEditorView::GetPreferredSize() const {
141  if (!show_tree_)
142    return views::View::GetPreferredSize();
143
144  return gfx::Size(views::Widget::GetLocalizedContentsSize(
145      IDS_EDITBOOKMARK_DIALOG_WIDTH_CHARS,
146      IDS_EDITBOOKMARK_DIALOG_HEIGHT_LINES));
147}
148
149void BookmarkEditorView::OnTreeViewSelectionChanged(
150    views::TreeView* tree_view) {
151}
152
153bool BookmarkEditorView::CanEdit(views::TreeView* tree_view,
154                                 ui::TreeModelNode* node) {
155  // Only allow editting of children of the bookmark bar node and other node.
156  EditorNode* bb_node = tree_model_->AsNode(node);
157  return (bb_node->parent() && bb_node->parent()->parent());
158}
159
160void BookmarkEditorView::ContentsChanged(views::Textfield* sender,
161                                         const base::string16& new_contents) {
162  UserInputChanged();
163}
164
165bool BookmarkEditorView::HandleKeyEvent(views::Textfield* sender,
166                                        const ui::KeyEvent& key_event) {
167    return false;
168}
169
170void BookmarkEditorView::GetAccessibleState(ui::AXViewState* state) {
171  state->name =
172      l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_TITLE);
173  state->role = ui::AX_ROLE_DIALOG;
174}
175
176void BookmarkEditorView::ButtonPressed(views::Button* sender,
177                                       const ui::Event& event) {
178  DCHECK_EQ(new_folder_button_.get(), sender);
179  NewFolder();
180}
181
182bool BookmarkEditorView::IsCommandIdChecked(int command_id) const {
183  return false;
184}
185
186bool BookmarkEditorView::IsCommandIdEnabled(int command_id) const {
187  switch (command_id) {
188    case IDS_EDIT:
189    case IDS_DELETE:
190      return !running_menu_for_root_;
191    case IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM:
192      return true;
193    default:
194      NOTREACHED();
195      return false;
196  }
197}
198
199bool BookmarkEditorView::GetAcceleratorForCommandId(
200    int command_id,
201    ui::Accelerator* accelerator) {
202  return GetWidget()->GetAccelerator(command_id, accelerator);
203}
204
205void BookmarkEditorView::ExecuteCommand(int command_id, int event_flags) {
206  DCHECK(tree_view_->GetSelectedNode());
207  if (command_id == IDS_EDIT) {
208    tree_view_->StartEditing(tree_view_->GetSelectedNode());
209  } else if (command_id == IDS_DELETE) {
210    EditorNode* node = tree_model_->AsNode(tree_view_->GetSelectedNode());
211    if (!node)
212      return;
213    if (node->value != 0) {
214      const BookmarkNode* b_node =
215          bookmarks::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
231void 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
243void 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(
255      GetMenuModel(),
256      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
257
258  if (context_menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
259                                      NULL,
260                                      gfx::Rect(point, gfx::Size()),
261                                      views::MENU_ANCHOR_TOPRIGHT,
262                                      source_type) ==
263      views::MenuRunner::MENU_DELETED) {
264    return;
265  }
266}
267
268void 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
367void 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
375void BookmarkEditorView::BookmarkNodeAdded(BookmarkModel* model,
376                                           const BookmarkNode* parent,
377                                           int index) {
378  Reset();
379}
380
381void 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
397void BookmarkEditorView::BookmarkAllUserNodesRemoved(
398    BookmarkModel* model,
399    const std::set<GURL>& removed_urls) {
400  Reset();
401}
402
403void BookmarkEditorView::BookmarkNodeChildrenReordered(
404    BookmarkModel* model,
405    const BookmarkNode* node) {
406  Reset();
407}
408
409void 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
434GURL 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
440void 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
451void 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
463BookmarkEditorView::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
472void 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
495BookmarkEditorView::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
507void 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
521BookmarkEditorView::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
534void 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
549void 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  bookmarks::DeleteBookmarkFolders(bb_model_, deletes_);
581}
582
583void 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
616void BookmarkEditorView::UpdateExpandedNodes(
617    EditorNode* editor_node,
618    BookmarkExpandedStateTracker::Nodes* expanded_nodes) {
619  if (!tree_view_->IsExpanded(editor_node))
620    return;
621
622  // The root is 0.
623  if (editor_node->value != 0) {
624    expanded_nodes->insert(
625        bookmarks::GetBookmarkNodeByID(bb_model_, editor_node->value));
626  }
627
628  for (int i = 0; i < editor_node->child_count(); ++i)
629    UpdateExpandedNodes(editor_node->GetChild(i), expanded_nodes);
630}
631
632ui::SimpleMenuModel* BookmarkEditorView::GetMenuModel() {
633  if (!context_menu_model_.get()) {
634    context_menu_model_.reset(new ui::SimpleMenuModel(this));
635    context_menu_model_->AddItemWithStringId(IDS_EDIT, IDS_EDIT);
636    context_menu_model_->AddItemWithStringId(IDS_DELETE, IDS_DELETE);
637    context_menu_model_->AddItemWithStringId(
638        IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM,
639        IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM);
640  }
641  return context_menu_model_.get();
642}
643
644void BookmarkEditorView::EditorTreeModel::SetTitle(
645    ui::TreeModelNode* node,
646    const base::string16& title) {
647  if (!title.empty())
648    ui::TreeNodeModel<EditorNode>::SetTitle(node, title);
649}
650