bookmark_editor_view.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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
42using bookmarks::BookmarkExpandedStateTracker;
43using views::GridLayout;
44
45namespace {
46
47// Background color of text field when URL is invalid.
48const SkColor kErrorColor = SkColorSetRGB(0xFF, 0xBC, 0xBC);
49
50}  // namespace
51
52// static
53void 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
63BookmarkEditorView::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
85BookmarkEditorView::~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
93base::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
100bool 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
111views::View* BookmarkEditorView::CreateExtraView() {
112  return new_folder_button_.get();
113}
114
115ui::ModalType BookmarkEditorView::GetModalType() const {
116  return ui::MODAL_TYPE_WINDOW;
117}
118
119bool BookmarkEditorView::CanResize() const {
120  return true;
121}
122
123base::string16 BookmarkEditorView::GetWindowTitle() const {
124  return l10n_util::GetStringUTF16(details_.GetWindowTitleId());
125}
126
127bool 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
141gfx::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
150void BookmarkEditorView::OnTreeViewSelectionChanged(
151    views::TreeView* tree_view) {
152}
153
154bool 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
161void BookmarkEditorView::ContentsChanged(views::Textfield* sender,
162                                         const base::string16& new_contents) {
163  UserInputChanged();
164}
165
166bool BookmarkEditorView::HandleKeyEvent(views::Textfield* sender,
167                                        const ui::KeyEvent& key_event) {
168    return false;
169}
170
171void 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
177void BookmarkEditorView::ButtonPressed(views::Button* sender,
178                                       const ui::Event& event) {
179  DCHECK_EQ(new_folder_button_.get(), sender);
180  NewFolder();
181}
182
183bool BookmarkEditorView::IsCommandIdChecked(int command_id) const {
184  return false;
185}
186
187bool 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
200bool BookmarkEditorView::GetAcceleratorForCommandId(
201    int command_id,
202    ui::Accelerator* accelerator) {
203  return GetWidget()->GetAccelerator(command_id, accelerator);
204}
205
206void 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 =
216          bookmarks::GetBookmarkNodeByID(bb_model_, node->value);
217      if (!b_node->empty() &&
218          !chrome::ConfirmDeleteBookmarkNode(b_node,
219            GetWidget()->GetNativeWindow())) {
220        // The folder is not empty and the user didn't confirm.
221        return;
222      }
223      deletes_.push_back(node->value);
224    }
225    tree_model_->Remove(node->parent(), node);
226  } else {
227    DCHECK_EQ(IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM, command_id);
228    NewFolder();
229  }
230}
231
232void BookmarkEditorView::Show(gfx::NativeWindow parent) {
233  CreateBrowserModalDialogViews(this, parent);
234  UserInputChanged();
235  if (show_tree_ && bb_model_->loaded())
236    ExpandAndSelect();
237  GetWidget()->Show();
238  // Select all the text in the name Textfield.
239  title_tf_->SelectAll(true);
240  // Give focus to the name Textfield.
241  title_tf_->RequestFocus();
242}
243
244void BookmarkEditorView::ShowContextMenuForView(
245    views::View* source,
246    const gfx::Point& point,
247    ui::MenuSourceType source_type) {
248  DCHECK_EQ(tree_view_, source);
249  if (!tree_view_->GetSelectedNode())
250    return;
251  running_menu_for_root_ =
252      (tree_model_->GetParent(tree_view_->GetSelectedNode()) ==
253       tree_model_->GetRoot());
254
255  context_menu_runner_.reset(new views::MenuRunner(
256      GetMenuModel(),
257      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
258
259  if (context_menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
260                                      NULL,
261                                      gfx::Rect(point, gfx::Size()),
262                                      views::MENU_ANCHOR_TOPRIGHT,
263                                      source_type) ==
264      views::MenuRunner::MENU_DELETED) {
265    return;
266  }
267}
268
269void BookmarkEditorView::Init() {
270  bb_model_->AddObserver(this);
271
272  title_label_ = new views::Label(
273      l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NAME_LABEL));
274
275  base::string16 title;
276  GURL url;
277  if (details_.type == EditDetails::EXISTING_NODE) {
278    title = details_.existing_node->GetTitle();
279    url = details_.existing_node->url();
280  } else if (details_.type == EditDetails::NEW_FOLDER) {
281    title = l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME);
282  } else if (details_.type == EditDetails::NEW_URL) {
283    url = details_.url;
284    title = details_.title;
285  }
286  title_tf_ = new views::Textfield;
287  title_tf_->SetAccessibleName(
288      l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_EDITOR_NAME_LABEL));
289  title_tf_->SetText(title);
290  title_tf_->set_controller(this);
291
292  if (show_tree_) {
293    tree_view_ = new views::TreeView;
294    tree_view_->SetRootShown(false);
295    tree_view_->set_context_menu_controller(this);
296
297    new_folder_button_.reset(new views::LabelButton(this,
298        l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_BUTTON)));
299    new_folder_button_->SetStyle(views::Button::STYLE_BUTTON);
300    new_folder_button_->set_owned_by_client();
301    new_folder_button_->SetEnabled(false);
302  }
303
304  GridLayout* layout = GridLayout::CreatePanel(this);
305  SetLayoutManager(layout);
306
307  const int labels_column_set_id = 0;
308  const int single_column_view_set_id = 1;
309  const int buttons_column_set_id = 2;
310
311  views::ColumnSet* column_set = layout->AddColumnSet(labels_column_set_id);
312  column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
313                        GridLayout::USE_PREF, 0, 0);
314  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
315  column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
316                        GridLayout::USE_PREF, 0, 0);
317
318  column_set = layout->AddColumnSet(single_column_view_set_id);
319  column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
320                        GridLayout::USE_PREF, 0, 0);
321
322  column_set = layout->AddColumnSet(buttons_column_set_id);
323  column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
324                        GridLayout::USE_PREF, 0, 0);
325  column_set->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
326  column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
327                        GridLayout::USE_PREF, 0, 0);
328  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
329  column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
330                        GridLayout::USE_PREF, 0, 0);
331  column_set->LinkColumnSizes(0, 2, 4, -1);
332
333  layout->StartRow(0, labels_column_set_id);
334  layout->AddView(title_label_);
335  layout->AddView(title_tf_);
336
337  if (details_.GetNodeType() != BookmarkNode::FOLDER) {
338    url_label_ = new views::Label(
339        l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_URL_LABEL));
340
341    url_tf_ = new views::Textfield;
342    PrefService* prefs =
343        profile_ ? user_prefs::UserPrefs::Get(profile_) : NULL;
344    url_tf_->SetText(chrome::FormatBookmarkURLForDisplay(url, prefs));
345    url_tf_->set_controller(this);
346    url_tf_->SetAccessibleName(
347        l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_EDITOR_URL_LABEL));
348
349    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
350
351    layout->StartRow(0, labels_column_set_id);
352    layout->AddView(url_label_);
353    layout->AddView(url_tf_);
354  }
355
356  if (show_tree_) {
357    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
358    layout->StartRow(1, single_column_view_set_id);
359    layout->AddView(tree_view_->CreateParentIfNecessary());
360  }
361
362  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
363
364  if (!show_tree_ || bb_model_->loaded())
365    Reset();
366}
367
368void BookmarkEditorView::BookmarkNodeMoved(BookmarkModel* model,
369                                           const BookmarkNode* old_parent,
370                                           int old_index,
371                                           const BookmarkNode* new_parent,
372                                           int new_index) {
373  Reset();
374}
375
376void BookmarkEditorView::BookmarkNodeAdded(BookmarkModel* model,
377                                           const BookmarkNode* parent,
378                                           int index) {
379  Reset();
380}
381
382void BookmarkEditorView::BookmarkNodeRemoved(
383    BookmarkModel* model,
384    const BookmarkNode* parent,
385    int index,
386    const BookmarkNode* node,
387    const std::set<GURL>& removed_urls) {
388  if ((details_.type == EditDetails::EXISTING_NODE &&
389       details_.existing_node->HasAncestor(node)) ||
390      (parent_ && parent_->HasAncestor(node))) {
391    // The node, or its parent was removed. Close the dialog.
392    GetWidget()->Close();
393  } else {
394    Reset();
395  }
396}
397
398void BookmarkEditorView::BookmarkAllUserNodesRemoved(
399    BookmarkModel* model,
400    const std::set<GURL>& removed_urls) {
401  Reset();
402}
403
404void BookmarkEditorView::BookmarkNodeChildrenReordered(
405    BookmarkModel* model,
406    const BookmarkNode* node) {
407  Reset();
408}
409
410void BookmarkEditorView::Reset() {
411  if (!show_tree_) {
412    if (parent())
413      UserInputChanged();
414    return;
415  }
416
417  new_folder_button_->SetEnabled(true);
418
419  // Do this first, otherwise when we invoke SetModel with the real one
420  // tree_view will try to invoke something on the model we just deleted.
421  tree_view_->SetModel(NULL);
422
423  EditorNode* root_node = CreateRootNode();
424  tree_model_.reset(new EditorTreeModel(root_node));
425
426  tree_view_->SetModel(tree_model_.get());
427  tree_view_->SetController(this);
428
429  context_menu_runner_.reset();
430
431  if (parent())
432    ExpandAndSelect();
433}
434
435GURL BookmarkEditorView::GetInputURL() const {
436  if (details_.GetNodeType() == BookmarkNode::FOLDER)
437    return GURL();
438  return url_fixer::FixupURL(base::UTF16ToUTF8(url_tf_->text()), std::string());
439}
440
441void BookmarkEditorView::UserInputChanged() {
442  if (details_.GetNodeType() != BookmarkNode::FOLDER) {
443    const GURL url(GetInputURL());
444    if (!url.is_valid())
445      url_tf_->SetBackgroundColor(kErrorColor);
446    else
447      url_tf_->UseDefaultBackgroundColor();
448  }
449  GetDialogClientView()->UpdateDialogButtons();
450}
451
452void BookmarkEditorView::NewFolder() {
453  // Create a new entry parented to the selected item, or the bookmark
454  // bar if nothing is selected.
455  EditorNode* parent = tree_model_->AsNode(tree_view_->GetSelectedNode());
456  if (!parent) {
457    NOTREACHED();
458    return;
459  }
460
461  tree_view_->StartEditing(AddNewFolder(parent));
462}
463
464BookmarkEditorView::EditorNode* BookmarkEditorView::AddNewFolder(
465    EditorNode* parent) {
466  EditorNode* new_node = new EditorNode(
467      l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME), 0);
468  // |new_node| is now owned by |parent|.
469  tree_model_->Add(parent, new_node, parent->child_count());
470  return new_node;
471}
472
473void BookmarkEditorView::ExpandAndSelect() {
474  BookmarkExpandedStateTracker::Nodes expanded_nodes =
475      bb_model_->expanded_state_tracker()->GetExpandedNodes();
476  for (BookmarkExpandedStateTracker::Nodes::const_iterator i(
477       expanded_nodes.begin()); i != expanded_nodes.end(); ++i) {
478    EditorNode* editor_node =
479        FindNodeWithID(tree_model_->GetRoot(), (*i)->id());
480    if (editor_node)
481      tree_view_->Expand(editor_node);
482  }
483
484  const BookmarkNode* to_select = parent_;
485  if (details_.type == EditDetails::EXISTING_NODE)
486    to_select = details_.existing_node->parent();
487  int64 folder_id_to_select = to_select->id();
488  EditorNode* b_node =
489      FindNodeWithID(tree_model_->GetRoot(), folder_id_to_select);
490  if (!b_node)
491    b_node = tree_model_->GetRoot()->GetChild(0);  // Bookmark bar node.
492
493  tree_view_->SetSelectedNode(b_node);
494}
495
496BookmarkEditorView::EditorNode* BookmarkEditorView::CreateRootNode() {
497  EditorNode* root_node = new EditorNode(base::string16(), 0);
498  const BookmarkNode* bb_root_node = bb_model_->root_node();
499  CreateNodes(bb_root_node, root_node);
500  DCHECK(root_node->child_count() >= 2 && root_node->child_count() <= 4);
501  DCHECK_EQ(BookmarkNode::BOOKMARK_BAR, bb_root_node->GetChild(0)->type());
502  DCHECK_EQ(BookmarkNode::OTHER_NODE, bb_root_node->GetChild(1)->type());
503  if (root_node->child_count() >= 3)
504    DCHECK_EQ(BookmarkNode::MOBILE, bb_root_node->GetChild(2)->type());
505  return root_node;
506}
507
508void BookmarkEditorView::CreateNodes(const BookmarkNode* bb_node,
509                                     BookmarkEditorView::EditorNode* b_node) {
510  for (int i = 0; i < bb_node->child_count(); ++i) {
511    const BookmarkNode* child_bb_node = bb_node->GetChild(i);
512    if (child_bb_node->IsVisible() && child_bb_node->is_folder() &&
513        bb_model_->client()->CanBeEditedByUser(child_bb_node)) {
514      EditorNode* new_b_node = new EditorNode(child_bb_node->GetTitle(),
515                                              child_bb_node->id());
516      b_node->Add(new_b_node, b_node->child_count());
517      CreateNodes(child_bb_node, new_b_node);
518    }
519  }
520}
521
522BookmarkEditorView::EditorNode* BookmarkEditorView::FindNodeWithID(
523    BookmarkEditorView::EditorNode* node,
524    int64 id) {
525  if (node->value == id)
526    return node;
527  for (int i = 0; i < node->child_count(); ++i) {
528    EditorNode* result = FindNodeWithID(node->GetChild(i), id);
529    if (result)
530      return result;
531  }
532  return NULL;
533}
534
535void BookmarkEditorView::ApplyEdits() {
536  DCHECK(bb_model_->loaded());
537
538  if (tree_view_)
539    tree_view_->CommitEdit();
540
541  EditorNode* parent = show_tree_ ?
542      tree_model_->AsNode(tree_view_->GetSelectedNode()) : NULL;
543  if (show_tree_ && !parent) {
544    NOTREACHED();
545    return;
546  }
547  ApplyEdits(parent);
548}
549
550void BookmarkEditorView::ApplyEdits(EditorNode* parent) {
551  DCHECK(!show_tree_ || parent);
552
553  // We're going to apply edits to the bookmark bar model, which will call us
554  // back. Normally when a structural edit occurs we reset the tree model.
555  // We don't want to do that here, so we remove ourselves as an observer.
556  bb_model_->RemoveObserver(this);
557
558  GURL new_url(GetInputURL());
559  base::string16 new_title(title_tf_->text());
560
561  if (!show_tree_) {
562    BookmarkEditor::ApplyEditsWithNoFolderChange(
563        bb_model_, parent_, details_, new_title, new_url);
564    return;
565  }
566
567  // Create the new folders and update the titles.
568  const BookmarkNode* new_parent = NULL;
569  ApplyNameChangesAndCreateNewFolders(
570      bb_model_->root_node(), tree_model_->GetRoot(), parent, &new_parent);
571
572  BookmarkEditor::ApplyEditsWithPossibleFolderChange(
573      bb_model_, new_parent, details_, new_title, new_url);
574
575  BookmarkExpandedStateTracker::Nodes expanded_nodes;
576  UpdateExpandedNodes(tree_model_->GetRoot(), &expanded_nodes);
577  bb_model_->expanded_state_tracker()->SetExpandedNodes(expanded_nodes);
578
579  // Remove the folders that were removed. This has to be done after all the
580  // other changes have been committed.
581  bookmarks::DeleteBookmarkFolders(bb_model_, deletes_);
582}
583
584void BookmarkEditorView::ApplyNameChangesAndCreateNewFolders(
585    const BookmarkNode* bb_node,
586    BookmarkEditorView::EditorNode* b_node,
587    BookmarkEditorView::EditorNode* parent_b_node,
588    const BookmarkNode** parent_bb_node) {
589  if (parent_b_node == b_node)
590    *parent_bb_node = bb_node;
591  for (int i = 0; i < b_node->child_count(); ++i) {
592    EditorNode* child_b_node = b_node->GetChild(i);
593    const BookmarkNode* child_bb_node = NULL;
594    if (child_b_node->value == 0) {
595      // New folder.
596      child_bb_node = bb_model_->AddFolder(bb_node,
597          bb_node->child_count(), child_b_node->GetTitle());
598      child_b_node->value = child_bb_node->id();
599    } else {
600      // Existing node, reset the title (BookmarkModel ignores changes if the
601      // title is the same).
602      for (int j = 0; j < bb_node->child_count(); ++j) {
603        const BookmarkNode* node = bb_node->GetChild(j);
604        if (node->is_folder() && node->id() == child_b_node->value) {
605          child_bb_node = node;
606          break;
607        }
608      }
609      DCHECK(child_bb_node);
610      bb_model_->SetTitle(child_bb_node, child_b_node->GetTitle());
611    }
612    ApplyNameChangesAndCreateNewFolders(child_bb_node, child_b_node,
613                                        parent_b_node, parent_bb_node);
614  }
615}
616
617void BookmarkEditorView::UpdateExpandedNodes(
618    EditorNode* editor_node,
619    BookmarkExpandedStateTracker::Nodes* expanded_nodes) {
620  if (!tree_view_->IsExpanded(editor_node))
621    return;
622
623  // The root is 0.
624  if (editor_node->value != 0) {
625    expanded_nodes->insert(
626        bookmarks::GetBookmarkNodeByID(bb_model_, editor_node->value));
627  }
628
629  for (int i = 0; i < editor_node->child_count(); ++i)
630    UpdateExpandedNodes(editor_node->GetChild(i), expanded_nodes);
631}
632
633ui::SimpleMenuModel* BookmarkEditorView::GetMenuModel() {
634  if (!context_menu_model_.get()) {
635    context_menu_model_.reset(new ui::SimpleMenuModel(this));
636    context_menu_model_->AddItemWithStringId(IDS_EDIT, IDS_EDIT);
637    context_menu_model_->AddItemWithStringId(IDS_DELETE, IDS_DELETE);
638    context_menu_model_->AddItemWithStringId(
639        IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM,
640        IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM);
641  }
642  return context_menu_model_.get();
643}
644
645void BookmarkEditorView::EditorTreeModel::SetTitle(
646    ui::TreeModelNode* node,
647    const base::string16& title) {
648  if (!title.empty())
649    ui::TreeNodeModel<EditorNode>::SetTitle(node, title);
650}
651