Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / bookmarks / bookmark_bubble_view.cc
blob5d2981357560f32a5199da61d7ff4b6996eee6af
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_bubble_view.h"
7 #include "base/strings/string16.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/app/chrome_command_ids.h"
11 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
12 #include "chrome/browser/platform_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/bookmarks/bookmark_bubble_observer.h"
15 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
16 #include "chrome/browser/ui/browser_dialogs.h"
17 #include "chrome/browser/ui/sync/sync_promo_ui.h"
18 #include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "components/bookmarks/browser/bookmark_model.h"
21 #include "components/bookmarks/browser/bookmark_utils.h"
22 #include "content/public/browser/user_metrics.h"
23 #include "ui/accessibility/ax_view_state.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/events/keycodes/keyboard_codes.h"
27 #include "ui/views/bubble/bubble_frame_view.h"
28 #include "ui/views/controls/button/label_button.h"
29 #include "ui/views/controls/combobox/combobox.h"
30 #include "ui/views/controls/label.h"
31 #include "ui/views/controls/link.h"
32 #include "ui/views/controls/textfield/textfield.h"
33 #include "ui/views/layout/grid_layout.h"
34 #include "ui/views/layout/layout_constants.h"
35 #include "ui/views/widget/widget.h"
37 using base::UserMetricsAction;
38 using bookmarks::BookmarkModel;
39 using bookmarks::BookmarkNode;
40 using views::ColumnSet;
41 using views::GridLayout;
43 namespace {
45 // Width of the border of a button.
46 const int kControlBorderWidth = 2;
48 // This combobox prevents any lengthy content from stretching the bubble view.
49 class UnsizedCombobox : public views::Combobox {
50 public:
51 explicit UnsizedCombobox(ui::ComboboxModel* model) : views::Combobox(model) {}
52 ~UnsizedCombobox() override {}
54 gfx::Size GetPreferredSize() const override {
55 return gfx::Size(0, views::Combobox::GetPreferredSize().height());
58 private:
59 DISALLOW_COPY_AND_ASSIGN(UnsizedCombobox);
62 } // namespace
64 BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;
66 // static
67 void BookmarkBubbleView::ShowBubble(views::View* anchor_view,
68 const gfx::Rect& anchor_rect,
69 gfx::NativeView parent_window,
70 bookmarks::BookmarkBubbleObserver* observer,
71 scoped_ptr<BookmarkBubbleDelegate> delegate,
72 Profile* profile,
73 const GURL& url,
74 bool already_bookmarked) {
75 if (bookmark_bubble_)
76 return;
78 bookmark_bubble_ =
79 new BookmarkBubbleView(anchor_view, observer, delegate.Pass(), profile,
80 url, !already_bookmarked);
81 if (!anchor_view) {
82 bookmark_bubble_->SetAnchorRect(anchor_rect);
83 bookmark_bubble_->set_parent_window(parent_window);
85 views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show();
86 // Select the entire title textfield contents when the bubble is first shown.
87 bookmark_bubble_->title_tf_->SelectAll(true);
88 bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
90 if (bookmark_bubble_->observer_) {
91 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile);
92 const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url);
93 bookmark_bubble_->observer_->OnBookmarkBubbleShown(node);
97 void BookmarkBubbleView::Hide() {
98 if (bookmark_bubble_)
99 bookmark_bubble_->GetWidget()->Close();
102 BookmarkBubbleView::~BookmarkBubbleView() {
103 if (apply_edits_) {
104 ApplyEdits();
105 } else if (remove_bookmark_) {
106 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
107 const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
108 if (node)
109 model->Remove(node);
111 // |parent_combobox_| needs to be destroyed before |parent_model_| as it
112 // uses |parent_model_| in its destructor.
113 delete parent_combobox_;
116 void BookmarkBubbleView::WindowClosing() {
117 // We have to reset |bubble_| here, not in our destructor, because we'll be
118 // destroyed asynchronously and the shown state will be checked before then.
119 DCHECK_EQ(bookmark_bubble_, this);
120 bookmark_bubble_ = NULL;
122 if (observer_)
123 observer_->OnBookmarkBubbleHidden();
126 bool BookmarkBubbleView::AcceleratorPressed(
127 const ui::Accelerator& accelerator) {
128 ui::KeyboardCode key_code = accelerator.key_code();
129 if (key_code == ui::VKEY_RETURN) {
130 HandleButtonPressed(close_button_);
131 return true;
133 if (key_code == ui::VKEY_E && accelerator.IsAltDown()) {
134 HandleButtonPressed(edit_button_);
135 return true;
137 if (key_code == ui::VKEY_R && accelerator.IsAltDown()) {
138 HandleButtonPressed(remove_button_);
139 return true;
141 if (key_code == ui::VKEY_ESCAPE) {
142 remove_bookmark_ = newly_bookmarked_;
143 apply_edits_ = false;
146 return BubbleDelegateView::AcceleratorPressed(accelerator);
149 void BookmarkBubbleView::Init() {
150 views::Label* title_label = new views::Label(
151 l10n_util::GetStringUTF16(
152 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
153 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
154 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
155 title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::MediumFont));
157 remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
158 IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
159 remove_button_->SetStyle(views::Button::STYLE_BUTTON);
161 edit_button_ = new views::LabelButton(
162 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
163 edit_button_->SetStyle(views::Button::STYLE_BUTTON);
165 close_button_ = new views::LabelButton(
166 this, l10n_util::GetStringUTF16(IDS_DONE));
167 close_button_->SetStyle(views::Button::STYLE_BUTTON);
168 close_button_->SetIsDefault(true);
170 views::Label* combobox_label = new views::Label(
171 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));
173 parent_combobox_ = new UnsizedCombobox(&parent_model_);
174 parent_combobox_->set_listener(this);
175 parent_combobox_->SetAccessibleName(
176 l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_TEXT));
178 GridLayout* layout = new GridLayout(this);
179 SetLayoutManager(layout);
181 // Column sets used in the layout of the bubble.
182 enum ColumnSetID {
183 TITLE_COLUMN_SET_ID,
184 CONTENT_COLUMN_SET_ID,
185 SYNC_PROMO_COLUMN_SET_ID
188 ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
189 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
190 cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
191 0, 0);
192 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
194 // The column layout used for middle and bottom rows.
195 cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
196 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
197 cs->AddColumn(views::kControlLabelGridAlignment, GridLayout::CENTER, 0,
198 GridLayout::USE_PREF, 0, 0);
199 cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
201 cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
202 GridLayout::USE_PREF, 0, 0);
203 cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);
205 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
206 GridLayout::USE_PREF, 0, 0);
207 cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
208 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
209 GridLayout::USE_PREF, 0, 0);
210 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
212 layout->StartRow(0, TITLE_COLUMN_SET_ID);
213 layout->AddView(title_label);
214 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
216 layout->StartRow(0, CONTENT_COLUMN_SET_ID);
217 views::Label* label = new views::Label(
218 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
219 layout->AddView(label);
220 title_tf_ = new views::Textfield();
221 title_tf_->SetText(GetTitle());
222 title_tf_->SetAccessibleName(
223 l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_TITLE_TEXT));
225 layout->AddView(title_tf_, 5, 1);
227 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
229 layout->StartRow(0, CONTENT_COLUMN_SET_ID);
230 layout->AddView(combobox_label);
231 layout->AddView(parent_combobox_, 5, 1);
233 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
235 layout->StartRow(0, CONTENT_COLUMN_SET_ID);
236 layout->SkipColumns(2);
237 layout->AddView(remove_button_);
238 layout->AddView(edit_button_);
239 layout->AddView(close_button_);
241 layout->AddPaddingRow(
243 views::kUnrelatedControlVerticalSpacing - kControlBorderWidth);
245 if (SyncPromoUI::ShouldShowSyncPromo(profile_)) {
246 // The column layout used for the sync promo.
247 cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID);
248 cs->AddColumn(GridLayout::FILL,
249 GridLayout::FILL,
251 GridLayout::USE_PREF,
254 layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID);
256 sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get());
257 layout->AddView(sync_promo_view_);
260 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
261 AddAccelerator(ui::Accelerator(ui::VKEY_E, ui::EF_ALT_DOWN));
262 AddAccelerator(ui::Accelerator(ui::VKEY_R, ui::EF_ALT_DOWN));
265 const char* BookmarkBubbleView::GetClassName() const {
266 return "BookmarkBubbleView";
269 views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
270 return title_tf_;
273 BookmarkBubbleView::BookmarkBubbleView(
274 views::View* anchor_view,
275 bookmarks::BookmarkBubbleObserver* observer,
276 scoped_ptr<BookmarkBubbleDelegate> delegate,
277 Profile* profile,
278 const GURL& url,
279 bool newly_bookmarked)
280 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
281 observer_(observer),
282 delegate_(delegate.Pass()),
283 profile_(profile),
284 url_(url),
285 newly_bookmarked_(newly_bookmarked),
286 parent_model_(
287 BookmarkModelFactory::GetForProfile(profile_),
288 BookmarkModelFactory::GetForProfile(profile_)->
289 GetMostRecentlyAddedUserNodeForURL(url)),
290 remove_button_(NULL),
291 edit_button_(NULL),
292 close_button_(NULL),
293 title_tf_(NULL),
294 parent_combobox_(NULL),
295 sync_promo_view_(NULL),
296 remove_bookmark_(false),
297 apply_edits_(true) {
298 set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
299 // Compensate for built-in vertical padding in the anchor view's image.
300 set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
303 base::string16 BookmarkBubbleView::GetTitle() {
304 BookmarkModel* bookmark_model =
305 BookmarkModelFactory::GetForProfile(profile_);
306 const BookmarkNode* node =
307 bookmark_model->GetMostRecentlyAddedUserNodeForURL(url_);
308 if (node)
309 return node->GetTitle();
310 else
311 NOTREACHED();
312 return base::string16();
315 void BookmarkBubbleView::GetAccessibleState(ui::AXViewState* state) {
316 BubbleDelegateView::GetAccessibleState(state);
317 state->name =
318 l10n_util::GetStringUTF16(
319 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
320 IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK);
323 void BookmarkBubbleView::ButtonPressed(views::Button* sender,
324 const ui::Event& event) {
325 HandleButtonPressed(sender);
328 void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
329 if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
330 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
331 ShowEditor();
335 void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
336 if (sender == remove_button_) {
337 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
338 // Set this so we remove the bookmark after the window closes.
339 remove_bookmark_ = true;
340 apply_edits_ = false;
341 GetWidget()->Close();
342 } else if (sender == edit_button_) {
343 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
344 ShowEditor();
345 } else {
346 DCHECK_EQ(close_button_, sender);
347 GetWidget()->Close();
351 void BookmarkBubbleView::ShowEditor() {
352 const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
353 profile_)->GetMostRecentlyAddedUserNodeForURL(url_);
354 gfx::NativeWindow native_parent =
355 anchor_widget() ? anchor_widget()->GetNativeWindow()
356 : platform_util::GetTopLevel(parent_window());
357 DCHECK(native_parent);
359 Profile* profile = profile_;
360 ApplyEdits();
361 GetWidget()->Close();
363 if (node && native_parent)
364 BookmarkEditor::Show(native_parent, profile,
365 BookmarkEditor::EditDetails::EditNode(node),
366 BookmarkEditor::SHOW_TREE);
369 void BookmarkBubbleView::ApplyEdits() {
370 // Set this to make sure we don't attempt to apply edits again.
371 apply_edits_ = false;
373 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
374 const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
375 if (node) {
376 const base::string16 new_title = title_tf_->text();
377 if (new_title != node->GetTitle()) {
378 model->SetTitle(node, new_title);
379 content::RecordAction(
380 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
382 parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());