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/profiles/profile.h"
13 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
14 #include "chrome/browser/ui/sync/sync_promo_ui.h"
15 #include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.h"
16 #include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.h"
17 #include "chrome/grit/generated_resources.h"
18 #include "components/bookmarks/browser/bookmark_model.h"
19 #include "components/bookmarks/browser/bookmark_utils.h"
20 #include "content/public/browser/user_metrics.h"
21 #include "ui/accessibility/ax_view_state.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/events/keycodes/keyboard_codes.h"
25 #include "ui/views/bubble/bubble_frame_view.h"
26 #include "ui/views/controls/button/label_button.h"
27 #include "ui/views/controls/combobox/combobox.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/link.h"
30 #include "ui/views/controls/textfield/textfield.h"
31 #include "ui/views/layout/grid_layout.h"
32 #include "ui/views/layout/layout_constants.h"
33 #include "ui/views/widget/widget.h"
35 using base::UserMetricsAction
;
36 using views::ColumnSet
;
37 using views::GridLayout
;
41 // Width of the border of a button.
42 const int kControlBorderWidth
= 2;
44 // This combobox prevents any lengthy content from stretching the bubble view.
45 class UnsizedCombobox
: public views::Combobox
{
47 explicit UnsizedCombobox(ui::ComboboxModel
* model
) : views::Combobox(model
) {}
48 virtual ~UnsizedCombobox() {}
50 virtual gfx::Size
GetPreferredSize() const override
{
51 return gfx::Size(0, views::Combobox::GetPreferredSize().height());
55 DISALLOW_COPY_AND_ASSIGN(UnsizedCombobox
);
60 BookmarkBubbleView
* BookmarkBubbleView::bookmark_bubble_
= NULL
;
63 void BookmarkBubbleView::ShowBubble(views::View
* anchor_view
,
64 BookmarkBubbleViewObserver
* observer
,
65 scoped_ptr
<BookmarkBubbleDelegate
> delegate
,
68 bool newly_bookmarked
) {
72 bookmark_bubble_
= new BookmarkBubbleView(anchor_view
,
78 views::BubbleDelegateView::CreateBubble(bookmark_bubble_
)->Show();
79 // Select the entire title textfield contents when the bubble is first shown.
80 bookmark_bubble_
->title_tf_
->SelectAll(true);
81 bookmark_bubble_
->SetArrowPaintType(views::BubbleBorder::PAINT_NONE
);
83 if (bookmark_bubble_
->observer_
)
84 bookmark_bubble_
->observer_
->OnBookmarkBubbleShown(url
);
88 bool BookmarkBubbleView::IsShowing() {
89 return bookmark_bubble_
!= NULL
;
92 void BookmarkBubbleView::Hide() {
94 bookmark_bubble_
->GetWidget()->Close();
97 BookmarkBubbleView::~BookmarkBubbleView() {
100 } else if (remove_bookmark_
) {
101 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(profile_
);
102 const BookmarkNode
* node
= model
->GetMostRecentlyAddedUserNodeForURL(url_
);
104 model
->Remove(node
->parent(), node
->parent()->GetIndexOf(node
));
106 // |parent_combobox_| needs to be destroyed before |parent_model_| as it
107 // uses |parent_model_| in its destructor.
108 delete parent_combobox_
;
111 views::View
* BookmarkBubbleView::GetInitiallyFocusedView() {
115 void BookmarkBubbleView::WindowClosing() {
116 // We have to reset |bubble_| here, not in our destructor, because we'll be
117 // destroyed asynchronously and the shown state will be checked before then.
118 DCHECK_EQ(bookmark_bubble_
, this);
119 bookmark_bubble_
= NULL
;
122 observer_
->OnBookmarkBubbleHidden();
125 bool BookmarkBubbleView::AcceleratorPressed(
126 const ui::Accelerator
& accelerator
) {
127 ui::KeyboardCode key_code
= accelerator
.key_code();
128 if (key_code
== ui::VKEY_RETURN
) {
129 HandleButtonPressed(close_button_
);
132 if (key_code
== ui::VKEY_E
&& accelerator
.IsAltDown()) {
133 HandleButtonPressed(edit_button_
);
136 if (key_code
== ui::VKEY_R
&& accelerator
.IsAltDown()) {
137 HandleButtonPressed(remove_button_
);
140 if (key_code
== ui::VKEY_ESCAPE
) {
141 remove_bookmark_
= newly_bookmarked_
;
142 apply_edits_
= false;
145 return BubbleDelegateView::AcceleratorPressed(accelerator
);
148 void BookmarkBubbleView::Init() {
149 views::Label
* title_label
= new views::Label(
150 l10n_util::GetStringUTF16(
151 newly_bookmarked_
? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED
:
152 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK
));
153 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
154 title_label
->SetFontList(rb
->GetFontList(ui::ResourceBundle::MediumFont
));
156 remove_button_
= new views::LabelButton(this, l10n_util::GetStringUTF16(
157 IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK
));
158 remove_button_
->SetStyle(views::Button::STYLE_BUTTON
);
160 edit_button_
= new views::LabelButton(
161 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS
));
162 edit_button_
->SetStyle(views::Button::STYLE_BUTTON
);
164 close_button_
= new views::LabelButton(
165 this, l10n_util::GetStringUTF16(IDS_DONE
));
166 close_button_
->SetStyle(views::Button::STYLE_BUTTON
);
167 close_button_
->SetIsDefault(true);
169 views::Label
* combobox_label
= new views::Label(
170 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT
));
172 parent_combobox_
= new UnsizedCombobox(&parent_model_
);
173 parent_combobox_
->set_listener(this);
174 parent_combobox_
->SetAccessibleName(
175 l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_TEXT
));
177 GridLayout
* layout
= new GridLayout(this);
178 SetLayoutManager(layout
);
180 // Column sets used in the layout of the bubble.
183 CONTENT_COLUMN_SET_ID
,
184 SYNC_PROMO_COLUMN_SET_ID
187 ColumnSet
* cs
= layout
->AddColumnSet(TITLE_COLUMN_SET_ID
);
188 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
189 cs
->AddColumn(GridLayout::CENTER
, GridLayout::CENTER
, 0, GridLayout::USE_PREF
,
191 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
193 // The column layout used for middle and bottom rows.
194 cs
= layout
->AddColumnSet(CONTENT_COLUMN_SET_ID
);
195 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
196 cs
->AddColumn(GridLayout::LEADING
, GridLayout::CENTER
, 0,
197 GridLayout::USE_PREF
, 0, 0);
198 cs
->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing
);
200 cs
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
201 GridLayout::USE_PREF
, 0, 0);
202 cs
->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing
);
204 cs
->AddColumn(GridLayout::LEADING
, GridLayout::TRAILING
, 0,
205 GridLayout::USE_PREF
, 0, 0);
206 cs
->AddPaddingColumn(0, views::kRelatedButtonHSpacing
);
207 cs
->AddColumn(GridLayout::LEADING
, GridLayout::TRAILING
, 0,
208 GridLayout::USE_PREF
, 0, 0);
209 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
211 layout
->StartRow(0, TITLE_COLUMN_SET_ID
);
212 layout
->AddView(title_label
);
213 layout
->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing
);
215 layout
->StartRow(0, CONTENT_COLUMN_SET_ID
);
216 views::Label
* label
= new views::Label(
217 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT
));
218 layout
->AddView(label
);
219 title_tf_
= new views::Textfield();
220 title_tf_
->SetText(GetTitle());
221 title_tf_
->SetAccessibleName(
222 l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_TITLE_TEXT
));
224 layout
->AddView(title_tf_
, 5, 1);
226 layout
->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing
);
228 layout
->StartRow(0, CONTENT_COLUMN_SET_ID
);
229 layout
->AddView(combobox_label
);
230 layout
->AddView(parent_combobox_
, 5, 1);
232 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
234 layout
->StartRow(0, CONTENT_COLUMN_SET_ID
);
235 layout
->SkipColumns(2);
236 layout
->AddView(remove_button_
);
237 layout
->AddView(edit_button_
);
238 layout
->AddView(close_button_
);
240 layout
->AddPaddingRow(
242 views::kUnrelatedControlVerticalSpacing
- kControlBorderWidth
);
244 if (SyncPromoUI::ShouldShowSyncPromo(profile_
)) {
245 // The column layout used for the sync promo.
246 cs
= layout
->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID
);
247 cs
->AddColumn(GridLayout::FILL
,
250 GridLayout::USE_PREF
,
253 layout
->StartRow(0, SYNC_PROMO_COLUMN_SET_ID
);
255 sync_promo_view_
= new BookmarkSyncPromoView(delegate_
.get());
256 layout
->AddView(sync_promo_view_
);
259 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN
, ui::EF_NONE
));
260 AddAccelerator(ui::Accelerator(ui::VKEY_E
, ui::EF_ALT_DOWN
));
261 AddAccelerator(ui::Accelerator(ui::VKEY_R
, ui::EF_ALT_DOWN
));
264 BookmarkBubbleView::BookmarkBubbleView(
265 views::View
* anchor_view
,
266 BookmarkBubbleViewObserver
* observer
,
267 scoped_ptr
<BookmarkBubbleDelegate
> delegate
,
270 bool newly_bookmarked
)
271 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
273 delegate_(delegate
.Pass()),
276 newly_bookmarked_(newly_bookmarked
),
278 BookmarkModelFactory::GetForProfile(profile_
),
279 BookmarkModelFactory::GetForProfile(profile_
)->
280 GetMostRecentlyAddedUserNodeForURL(url
)),
281 remove_button_(NULL
),
285 parent_combobox_(NULL
),
286 sync_promo_view_(NULL
),
287 remove_bookmark_(false),
289 set_margins(gfx::Insets(views::kPanelVertMargin
, 0, 0, 0));
290 // Compensate for built-in vertical padding in the anchor view's image.
291 set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
294 base::string16
BookmarkBubbleView::GetTitle() {
295 BookmarkModel
* bookmark_model
=
296 BookmarkModelFactory::GetForProfile(profile_
);
297 const BookmarkNode
* node
=
298 bookmark_model
->GetMostRecentlyAddedUserNodeForURL(url_
);
300 return node
->GetTitle();
303 return base::string16();
306 void BookmarkBubbleView::GetAccessibleState(ui::AXViewState
* state
) {
307 BubbleDelegateView::GetAccessibleState(state
);
309 l10n_util::GetStringUTF16(
310 newly_bookmarked_
? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED
:
311 IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK
);
314 void BookmarkBubbleView::ButtonPressed(views::Button
* sender
,
315 const ui::Event
& event
) {
316 HandleButtonPressed(sender
);
319 void BookmarkBubbleView::OnPerformAction(views::Combobox
* combobox
) {
320 if (combobox
->selected_index() + 1 == parent_model_
.GetItemCount()) {
321 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
326 void BookmarkBubbleView::HandleButtonPressed(views::Button
* sender
) {
327 if (sender
== remove_button_
) {
328 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
329 // Set this so we remove the bookmark after the window closes.
330 remove_bookmark_
= true;
331 apply_edits_
= false;
332 GetWidget()->Close();
333 } else if (sender
== edit_button_
) {
334 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
337 DCHECK_EQ(close_button_
, sender
);
338 GetWidget()->Close();
342 void BookmarkBubbleView::ShowEditor() {
343 const BookmarkNode
* node
= BookmarkModelFactory::GetForProfile(
344 profile_
)->GetMostRecentlyAddedUserNodeForURL(url_
);
345 views::Widget
* parent
= anchor_widget();
348 Profile
* profile
= profile_
;
350 GetWidget()->Close();
353 BookmarkEditor::Show(parent
->GetNativeWindow(), profile
,
354 BookmarkEditor::EditDetails::EditNode(node
),
355 BookmarkEditor::SHOW_TREE
);
358 void BookmarkBubbleView::ApplyEdits() {
359 // Set this to make sure we don't attempt to apply edits again.
360 apply_edits_
= false;
362 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(profile_
);
363 const BookmarkNode
* node
= model
->GetMostRecentlyAddedUserNodeForURL(url_
);
365 const base::string16 new_title
= title_tf_
->text();
366 if (new_title
!= node
->GetTitle()) {
367 model
->SetTitle(node
, new_title
);
368 content::RecordAction(
369 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
371 parent_model_
.MaybeChangeParent(node
, parent_combobox_
->selected_index());