Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / views / bookmarks / bookmark_editor_view.cc
blobf987e0451af6cd6020f4c4005a7482a89c0ace68
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.
5 #include "chrome/browser/ui/views/bookmarks/bookmark_editor_view.h"
7 #include <string>
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.h"
15 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
16 #include "chrome/browser/bookmarks/bookmark_utils.h"
17 #include "chrome/browser/history/history_service.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
20 #include "chrome/browser/ui/views/constrained_window_views.h"
21 #include "chrome/common/net/url_fixer_upper.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/base/events/event.h"
27 #include "ui/base/l10n/l10n_util.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"
41 using views::GridLayout;
43 namespace {
45 // Background color of text field when URL is invalid.
46 const SkColor kErrorColor = SkColorSetRGB(0xFF, 0xBC, 0xBC);
48 } // namespace
50 // static
51 void BookmarkEditor::Show(gfx::NativeWindow parent_window,
52 Profile* profile,
53 const EditDetails& details,
54 Configuration configuration) {
55 DCHECK(profile);
56 BookmarkEditorView* editor = new BookmarkEditorView(profile,
57 details.parent_node, details, configuration);
58 editor->Show(parent_window);
61 BookmarkEditorView::BookmarkEditorView(
62 Profile* profile,
63 const BookmarkNode* parent,
64 const EditDetails& details,
65 BookmarkEditor::Configuration configuration)
66 : profile_(profile),
67 tree_view_(NULL),
68 url_label_(NULL),
69 url_tf_(NULL),
70 title_label_(NULL),
71 title_tf_(NULL),
72 parent_(parent),
73 details_(details),
74 running_menu_for_root_(false),
75 show_tree_(configuration == SHOW_TREE) {
76 DCHECK(profile);
77 Init();
80 BookmarkEditorView::~BookmarkEditorView() {
81 // The tree model is deleted before the view. Reset the model otherwise the
82 // tree will reference a deleted model.
83 if (tree_view_)
84 tree_view_->SetModel(NULL);
85 bb_model_->RemoveObserver(this);
88 string16 BookmarkEditorView::GetDialogButtonLabel(
89 ui::DialogButton button) const {
90 if (button == ui::DIALOG_BUTTON_OK)
91 return l10n_util::GetStringUTF16(IDS_SAVE);
92 return views::DialogDelegateView::GetDialogButtonLabel(button);
95 bool BookmarkEditorView::IsDialogButtonEnabled(ui::DialogButton button) const {
96 if (button == ui::DIALOG_BUTTON_OK) {
97 if (!bb_model_->loaded())
98 return false;
100 if (details_.GetNodeType() != BookmarkNode::FOLDER)
101 return GetInputURL().is_valid();
103 return true;
106 views::View* BookmarkEditorView::CreateExtraView() {
107 return new_folder_button_.get();
110 ui::ModalType BookmarkEditorView::GetModalType() const {
111 return ui::MODAL_TYPE_WINDOW;
114 bool BookmarkEditorView::CanResize() const {
115 return true;
118 string16 BookmarkEditorView::GetWindowTitle() const {
119 return l10n_util::GetStringUTF16(details_.GetWindowTitleId());
122 bool BookmarkEditorView::Accept() {
123 if (!IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK)) {
124 if (details_.GetNodeType() != BookmarkNode::FOLDER) {
125 // The url is invalid, focus the url field.
126 url_tf_->SelectAll(true);
127 url_tf_->RequestFocus();
129 return false;
131 // Otherwise save changes and close the dialog box.
132 ApplyEdits();
133 return true;
136 gfx::Size BookmarkEditorView::GetPreferredSize() {
137 if (!show_tree_)
138 return views::View::GetPreferredSize();
140 return gfx::Size(views::Widget::GetLocalizedContentsSize(
141 IDS_EDITBOOKMARK_DIALOG_WIDTH_CHARS,
142 IDS_EDITBOOKMARK_DIALOG_HEIGHT_LINES));
145 void BookmarkEditorView::OnTreeViewSelectionChanged(
146 views::TreeView* tree_view) {
149 bool BookmarkEditorView::CanEdit(views::TreeView* tree_view,
150 ui::TreeModelNode* node) {
151 // Only allow editting of children of the bookmark bar node and other node.
152 EditorNode* bb_node = tree_model_->AsNode(node);
153 return (bb_node->parent() && bb_node->parent()->parent());
156 void BookmarkEditorView::ContentsChanged(views::Textfield* sender,
157 const string16& new_contents) {
158 UserInputChanged();
161 bool BookmarkEditorView::HandleKeyEvent(views::Textfield* sender,
162 const ui::KeyEvent& key_event) {
163 return false;
166 void BookmarkEditorView::ButtonPressed(views::Button* sender,
167 const ui::Event& event) {
168 DCHECK_EQ(new_folder_button_.get(), sender);
169 NewFolder();
172 bool BookmarkEditorView::IsCommandIdChecked(int command_id) const {
173 return false;
176 bool BookmarkEditorView::IsCommandIdEnabled(int command_id) const {
177 switch (command_id) {
178 case IDS_EDIT:
179 case IDS_DELETE:
180 return !running_menu_for_root_;
181 case IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM:
182 return true;
183 default:
184 NOTREACHED();
185 return false;
189 bool BookmarkEditorView::GetAcceleratorForCommandId(
190 int command_id,
191 ui::Accelerator* accelerator) {
192 return GetWidget()->GetAccelerator(command_id, accelerator);
195 void BookmarkEditorView::ExecuteCommand(int command_id, int event_flags) {
196 DCHECK(tree_view_->GetSelectedNode());
197 if (command_id == IDS_EDIT) {
198 tree_view_->StartEditing(tree_view_->GetSelectedNode());
199 } else if (command_id == IDS_DELETE) {
200 EditorNode* node = tree_model_->AsNode(tree_view_->GetSelectedNode());
201 if (!node)
202 return;
203 if (node->value != 0) {
204 const BookmarkNode* b_node = bb_model_->GetNodeByID(node->value);
205 if (!b_node->empty() &&
206 !chrome::ConfirmDeleteBookmarkNode(b_node,
207 GetWidget()->GetNativeWindow())) {
208 // The folder is not empty and the user didn't confirm.
209 return;
211 deletes_.push_back(node->value);
213 tree_model_->Remove(node->parent(), node);
214 } else {
215 DCHECK_EQ(IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM, command_id);
216 NewFolder();
220 void BookmarkEditorView::Show(gfx::NativeWindow parent) {
221 CreateBrowserModalDialogViews(this, parent);
222 UserInputChanged();
223 if (show_tree_ && bb_model_->loaded())
224 ExpandAndSelect();
225 GetWidget()->Show();
226 // Select all the text in the name Textfield.
227 title_tf_->SelectAll(true);
228 // Give focus to the name Textfield.
229 title_tf_->RequestFocus();
232 void BookmarkEditorView::ShowContextMenuForView(
233 views::View* source,
234 const gfx::Point& point,
235 ui::MenuSourceType source_type) {
236 DCHECK_EQ(tree_view_, source);
237 if (!tree_view_->GetSelectedNode())
238 return;
239 running_menu_for_root_ =
240 (tree_model_->GetParent(tree_view_->GetSelectedNode()) ==
241 tree_model_->GetRoot());
243 context_menu_runner_.reset(new views::MenuRunner(GetMenuModel()));
245 if (context_menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
246 NULL, gfx::Rect(point, gfx::Size()), views::MenuItemView::TOPRIGHT,
247 source_type,
248 views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU) ==
249 views::MenuRunner::MENU_DELETED)
250 return;
253 void BookmarkEditorView::Init() {
254 bb_model_ = BookmarkModelFactory::GetForProfile(profile_);
255 DCHECK(bb_model_);
256 bb_model_->AddObserver(this);
258 title_label_ = new views::Label(
259 l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NAME_LABEL));
261 string16 title;
262 GURL url;
263 if (details_.type == EditDetails::EXISTING_NODE) {
264 title = details_.existing_node->GetTitle();
265 url = details_.existing_node->url();
266 } else if (details_.type == EditDetails::NEW_FOLDER) {
267 title = l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME);
268 } else if (details_.type == EditDetails::NEW_URL) {
269 url = details_.url;
270 title = details_.title;
272 title_tf_ = new views::Textfield;
273 title_tf_->SetText(title);
274 title_tf_->SetController(this);
275 title_tf_->SetAccessibleName(title_label_->text());
277 if (show_tree_) {
278 tree_view_ = new views::TreeView;
279 tree_view_->SetRootShown(false);
280 tree_view_->set_context_menu_controller(this);
282 new_folder_button_.reset(new views::LabelButton(this,
283 l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_BUTTON)));
284 new_folder_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
285 new_folder_button_->set_owned_by_client();
286 new_folder_button_->SetEnabled(false);
289 GridLayout* layout = GridLayout::CreatePanel(this);
290 SetLayoutManager(layout);
292 const int labels_column_set_id = 0;
293 const int single_column_view_set_id = 1;
294 const int buttons_column_set_id = 2;
296 views::ColumnSet* column_set = layout->AddColumnSet(labels_column_set_id);
297 column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
298 GridLayout::USE_PREF, 0, 0);
299 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
300 column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
301 GridLayout::USE_PREF, 0, 0);
303 column_set = layout->AddColumnSet(single_column_view_set_id);
304 if (views::DialogDelegate::UseNewStyle()) {
305 column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
306 GridLayout::USE_PREF, 0, 0);
307 } else {
308 column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
309 GridLayout::FIXED, 300, 0);
312 column_set = layout->AddColumnSet(buttons_column_set_id);
313 column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
314 GridLayout::USE_PREF, 0, 0);
315 column_set->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
316 column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
317 GridLayout::USE_PREF, 0, 0);
318 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
319 column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0,
320 GridLayout::USE_PREF, 0, 0);
321 column_set->LinkColumnSizes(0, 2, 4, -1);
323 layout->StartRow(0, labels_column_set_id);
324 layout->AddView(title_label_);
325 layout->AddView(title_tf_);
327 if (details_.GetNodeType() != BookmarkNode::FOLDER) {
328 url_label_ = new views::Label(
329 l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_URL_LABEL));
331 url_tf_ = new views::Textfield;
332 PrefService* prefs =
333 profile_ ? user_prefs::UserPrefs::Get(profile_) : NULL;
334 url_tf_->SetText(chrome::FormatBookmarkURLForDisplay(url, prefs));
335 url_tf_->SetController(this);
336 url_tf_->SetAccessibleName(url_label_->text());
338 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
340 layout->StartRow(0, labels_column_set_id);
341 layout->AddView(url_label_);
342 layout->AddView(url_tf_);
345 if (show_tree_) {
346 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
347 layout->StartRow(1, single_column_view_set_id);
348 layout->AddView(tree_view_->CreateParentIfNecessary());
351 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
353 if (!show_tree_ || bb_model_->loaded())
354 Reset();
357 void BookmarkEditorView::BookmarkNodeMoved(BookmarkModel* model,
358 const BookmarkNode* old_parent,
359 int old_index,
360 const BookmarkNode* new_parent,
361 int new_index) {
362 Reset();
365 void BookmarkEditorView::BookmarkNodeAdded(BookmarkModel* model,
366 const BookmarkNode* parent,
367 int index) {
368 Reset();
371 void BookmarkEditorView::BookmarkNodeRemoved(BookmarkModel* model,
372 const BookmarkNode* parent,
373 int index,
374 const BookmarkNode* node) {
375 if ((details_.type == EditDetails::EXISTING_NODE &&
376 details_.existing_node->HasAncestor(node)) ||
377 (parent_ && parent_->HasAncestor(node))) {
378 // The node, or its parent was removed. Close the dialog.
379 GetWidget()->Close();
380 } else {
381 Reset();
385 void BookmarkEditorView::BookmarkAllNodesRemoved(BookmarkModel* model) {
386 Reset();
389 void BookmarkEditorView::BookmarkNodeChildrenReordered(
390 BookmarkModel* model, const BookmarkNode* node) {
391 Reset();
394 void BookmarkEditorView::Reset() {
395 if (!show_tree_) {
396 if (parent())
397 UserInputChanged();
398 return;
401 new_folder_button_->SetEnabled(true);
403 // Do this first, otherwise when we invoke SetModel with the real one
404 // tree_view will try to invoke something on the model we just deleted.
405 tree_view_->SetModel(NULL);
407 EditorNode* root_node = CreateRootNode();
408 tree_model_.reset(new EditorTreeModel(root_node));
410 tree_view_->SetModel(tree_model_.get());
411 tree_view_->SetController(this);
413 context_menu_runner_.reset();
415 if (parent())
416 ExpandAndSelect();
419 GURL BookmarkEditorView::GetInputURL() const {
420 if (details_.GetNodeType() == BookmarkNode::FOLDER)
421 return GURL();
422 return URLFixerUpper::FixupURL(UTF16ToUTF8(url_tf_->text()), std::string());
425 void BookmarkEditorView::UserInputChanged() {
426 if (details_.GetNodeType() != BookmarkNode::FOLDER) {
427 const GURL url(GetInputURL());
428 if (!url.is_valid())
429 url_tf_->SetBackgroundColor(kErrorColor);
430 else
431 url_tf_->UseDefaultBackgroundColor();
433 GetDialogClientView()->UpdateDialogButtons();
436 void BookmarkEditorView::NewFolder() {
437 // Create a new entry parented to the selected item, or the bookmark
438 // bar if nothing is selected.
439 EditorNode* parent = tree_model_->AsNode(tree_view_->GetSelectedNode());
440 if (!parent) {
441 NOTREACHED();
442 return;
445 tree_view_->StartEditing(AddNewFolder(parent));
448 BookmarkEditorView::EditorNode* BookmarkEditorView::AddNewFolder(
449 EditorNode* parent) {
450 EditorNode* new_node = new EditorNode(
451 l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME), 0);
452 // |new_node| is now owned by |parent|.
453 tree_model_->Add(parent, new_node, parent->child_count());
454 return new_node;
457 void BookmarkEditorView::ExpandAndSelect() {
458 BookmarkExpandedStateTracker::Nodes expanded_nodes =
459 bb_model_->expanded_state_tracker()->GetExpandedNodes();
460 for (BookmarkExpandedStateTracker::Nodes::const_iterator i(
461 expanded_nodes.begin()); i != expanded_nodes.end(); ++i) {
462 EditorNode* editor_node =
463 FindNodeWithID(tree_model_->GetRoot(), (*i)->id());
464 if (editor_node)
465 tree_view_->Expand(editor_node);
468 const BookmarkNode* to_select = parent_;
469 if (details_.type == EditDetails::EXISTING_NODE)
470 to_select = details_.existing_node->parent();
471 int64 folder_id_to_select = to_select->id();
472 EditorNode* b_node =
473 FindNodeWithID(tree_model_->GetRoot(), folder_id_to_select);
474 if (!b_node)
475 b_node = tree_model_->GetRoot()->GetChild(0); // Bookmark bar node.
477 tree_view_->SetSelectedNode(b_node);
480 BookmarkEditorView::EditorNode* BookmarkEditorView::CreateRootNode() {
481 EditorNode* root_node = new EditorNode(string16(), 0);
482 const BookmarkNode* bb_root_node = bb_model_->root_node();
483 CreateNodes(bb_root_node, root_node);
484 DCHECK(root_node->child_count() >= 2 && root_node->child_count() <= 3);
485 DCHECK_EQ(BookmarkNode::BOOKMARK_BAR, bb_root_node->GetChild(0)->type());
486 DCHECK_EQ(BookmarkNode::OTHER_NODE, bb_root_node->GetChild(1)->type());
487 if (root_node->child_count() == 3)
488 DCHECK_EQ(BookmarkNode::MOBILE, bb_root_node->GetChild(2)->type());
489 return root_node;
492 void BookmarkEditorView::CreateNodes(const BookmarkNode* bb_node,
493 BookmarkEditorView::EditorNode* b_node) {
494 for (int i = 0; i < bb_node->child_count(); ++i) {
495 const BookmarkNode* child_bb_node = bb_node->GetChild(i);
496 if (child_bb_node->IsVisible() && child_bb_node->is_folder()) {
497 EditorNode* new_b_node = new EditorNode(child_bb_node->GetTitle(),
498 child_bb_node->id());
499 b_node->Add(new_b_node, b_node->child_count());
500 CreateNodes(child_bb_node, new_b_node);
505 BookmarkEditorView::EditorNode* BookmarkEditorView::FindNodeWithID(
506 BookmarkEditorView::EditorNode* node,
507 int64 id) {
508 if (node->value == id)
509 return node;
510 for (int i = 0; i < node->child_count(); ++i) {
511 EditorNode* result = FindNodeWithID(node->GetChild(i), id);
512 if (result)
513 return result;
515 return NULL;
518 void BookmarkEditorView::ApplyEdits() {
519 DCHECK(bb_model_->loaded());
521 if (tree_view_)
522 tree_view_->CommitEdit();
524 EditorNode* parent = show_tree_ ?
525 tree_model_->AsNode(tree_view_->GetSelectedNode()) : NULL;
526 if (show_tree_ && !parent) {
527 NOTREACHED();
528 return;
530 ApplyEdits(parent);
533 void BookmarkEditorView::ApplyEdits(EditorNode* parent) {
534 DCHECK(!show_tree_ || parent);
536 // We're going to apply edits to the bookmark bar model, which will call us
537 // back. Normally when a structural edit occurs we reset the tree model.
538 // We don't want to do that here, so we remove ourselves as an observer.
539 bb_model_->RemoveObserver(this);
541 GURL new_url(GetInputURL());
542 string16 new_title(title_tf_->text());
544 if (!show_tree_) {
545 BookmarkEditor::ApplyEditsWithNoFolderChange(
546 bb_model_, parent_, details_, new_title, new_url);
547 return;
550 // Create the new folders and update the titles.
551 const BookmarkNode* new_parent = NULL;
552 ApplyNameChangesAndCreateNewFolders(
553 bb_model_->root_node(), tree_model_->GetRoot(), parent, &new_parent);
555 BookmarkEditor::ApplyEditsWithPossibleFolderChange(
556 bb_model_, new_parent, details_, new_title, new_url);
558 BookmarkExpandedStateTracker::Nodes expanded_nodes;
559 UpdateExpandedNodes(tree_model_->GetRoot(), &expanded_nodes);
560 bb_model_->expanded_state_tracker()->SetExpandedNodes(expanded_nodes);
562 // Remove the folders that were removed. This has to be done after all the
563 // other changes have been committed.
564 bookmark_utils::DeleteBookmarkFolders(bb_model_, deletes_);
567 void BookmarkEditorView::ApplyNameChangesAndCreateNewFolders(
568 const BookmarkNode* bb_node,
569 BookmarkEditorView::EditorNode* b_node,
570 BookmarkEditorView::EditorNode* parent_b_node,
571 const BookmarkNode** parent_bb_node) {
572 if (parent_b_node == b_node)
573 *parent_bb_node = bb_node;
574 for (int i = 0; i < b_node->child_count(); ++i) {
575 EditorNode* child_b_node = b_node->GetChild(i);
576 const BookmarkNode* child_bb_node = NULL;
577 if (child_b_node->value == 0) {
578 // New folder.
579 child_bb_node = bb_model_->AddFolder(bb_node,
580 bb_node->child_count(), child_b_node->GetTitle());
581 child_b_node->value = child_bb_node->id();
582 } else {
583 // Existing node, reset the title (BookmarkModel ignores changes if the
584 // title is the same).
585 for (int j = 0; j < bb_node->child_count(); ++j) {
586 const BookmarkNode* node = bb_node->GetChild(j);
587 if (node->is_folder() && node->id() == child_b_node->value) {
588 child_bb_node = node;
589 break;
592 DCHECK(child_bb_node);
593 bb_model_->SetTitle(child_bb_node, child_b_node->GetTitle());
595 ApplyNameChangesAndCreateNewFolders(child_bb_node, child_b_node,
596 parent_b_node, parent_bb_node);
600 void BookmarkEditorView::UpdateExpandedNodes(
601 EditorNode* editor_node,
602 BookmarkExpandedStateTracker::Nodes* expanded_nodes) {
603 if (!tree_view_->IsExpanded(editor_node))
604 return;
606 if (editor_node->value != 0) // The root is 0
607 expanded_nodes->insert(bb_model_->GetNodeByID(editor_node->value));
608 for (int i = 0; i < editor_node->child_count(); ++i)
609 UpdateExpandedNodes(editor_node->GetChild(i), expanded_nodes);
612 ui::SimpleMenuModel* BookmarkEditorView::GetMenuModel() {
613 if (!context_menu_model_.get()) {
614 context_menu_model_.reset(new ui::SimpleMenuModel(this));
615 context_menu_model_->AddItemWithStringId(IDS_EDIT, IDS_EDIT);
616 context_menu_model_->AddItemWithStringId(IDS_DELETE, IDS_DELETE);
617 context_menu_model_->AddItemWithStringId(
618 IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM,
619 IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM);
621 return context_menu_model_.get();
624 void BookmarkEditorView::EditorTreeModel::SetTitle(ui::TreeModelNode* node,
625 const string16& title) {
626 if (!title.empty())
627 ui::TreeNodeModel<EditorNode>::SetTitle(node, title);