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.h"
12 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
13 #include "chrome/browser/bookmarks/bookmark_utils.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
16 #include "chrome/browser/ui/sync/sync_promo_ui.h"
17 #include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.h"
18 #include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.h"
19 #include "content/public/browser/user_metrics.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #include "ui/base/keycodes/keyboard_codes.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.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 content::UserMetricsAction
;
36 using views::ColumnSet
;
37 using views::GridLayout
;
41 // Minimum width of the the bubble.
42 const int kMinBubbleWidth
= 350;
44 // Width of the border of a button.
45 const int kControlBorderWidth
= 2;
49 // Declared in browser_dialogs.h so callers don't have to depend on our header.
53 void ShowBookmarkBubbleView(views::View
* anchor_view
,
54 BookmarkBubbleViewObserver
* observer
,
55 scoped_ptr
<BookmarkBubbleDelegate
> delegate
,
58 bool newly_bookmarked
) {
59 BookmarkBubbleView::ShowBubble(anchor_view
,
67 void HideBookmarkBubbleView() {
68 BookmarkBubbleView::Hide();
71 bool IsBookmarkBubbleViewShowing() {
72 return BookmarkBubbleView::IsShowing();
77 // BookmarkBubbleView ---------------------------------------------------------
79 BookmarkBubbleView
* BookmarkBubbleView::bookmark_bubble_
= NULL
;
82 void BookmarkBubbleView::ShowBubble(
83 views::View
* anchor_view
,
84 BookmarkBubbleViewObserver
* observer
,
85 scoped_ptr
<BookmarkBubbleDelegate
> delegate
,
88 bool newly_bookmarked
) {
92 bookmark_bubble_
= new BookmarkBubbleView(anchor_view
,
98 views::BubbleDelegateView::CreateBubble(bookmark_bubble_
)->Show();
99 // Select the entire title textfield contents when the bubble is first shown.
100 bookmark_bubble_
->title_tf_
->SelectAll(true);
101 bookmark_bubble_
->SetArrowPaintType(views::BubbleBorder::PAINT_NONE
);
103 if (bookmark_bubble_
->observer_
)
104 bookmark_bubble_
->observer_
->OnBookmarkBubbleShown(url
);
108 bool BookmarkBubbleView::IsShowing() {
109 return bookmark_bubble_
!= NULL
;
112 void BookmarkBubbleView::Hide() {
114 bookmark_bubble_
->GetWidget()->Close();
117 BookmarkBubbleView::~BookmarkBubbleView() {
120 } else if (remove_bookmark_
) {
121 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(profile_
);
122 const BookmarkNode
* node
= model
->GetMostRecentlyAddedNodeForURL(url_
);
124 model
->Remove(node
->parent(), node
->parent()->GetIndexOf(node
));
128 views::View
* BookmarkBubbleView::GetInitiallyFocusedView() {
132 void BookmarkBubbleView::WindowClosing() {
133 // We have to reset |bubble_| here, not in our destructor, because we'll be
134 // destroyed asynchronously and the shown state will be checked before then.
135 DCHECK_EQ(bookmark_bubble_
, this);
136 bookmark_bubble_
= NULL
;
139 observer_
->OnBookmarkBubbleHidden();
142 bool BookmarkBubbleView::AcceleratorPressed(
143 const ui::Accelerator
& accelerator
) {
144 if (accelerator
.key_code() == ui::VKEY_RETURN
) {
145 if (edit_button_
->HasFocus())
146 HandleButtonPressed(edit_button_
);
148 HandleButtonPressed(close_button_
);
150 } else if (accelerator
.key_code() == ui::VKEY_ESCAPE
) {
151 remove_bookmark_
= newly_bookmarked_
;
152 apply_edits_
= false;
155 return BubbleDelegateView::AcceleratorPressed(accelerator
);
158 void BookmarkBubbleView::Init() {
159 views::Label
* title_label
= new views::Label(
160 l10n_util::GetStringUTF16(
161 newly_bookmarked_
? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED
:
162 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK
));
163 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
164 title_label
->SetFont(rb
.GetFont(ui::ResourceBundle::MediumFont
));
166 remove_button_
= new views::LabelButton(this, l10n_util::GetStringUTF16(
167 IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK
));
168 remove_button_
->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON
);
170 edit_button_
= new views::LabelButton(
171 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS
));
172 edit_button_
->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON
);
174 close_button_
= new views::LabelButton(
175 this, l10n_util::GetStringUTF16(IDS_DONE
));
176 close_button_
->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON
);
177 close_button_
->SetIsDefault(true);
179 views::Label
* combobox_label
= new views::Label(
180 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT
));
182 parent_combobox_
= new views::Combobox(&parent_model_
);
183 parent_combobox_
->set_listener(this);
184 parent_combobox_
->SetAccessibleName(combobox_label
->text());
186 GridLayout
* layout
= new GridLayout(this);
187 SetLayoutManager(layout
);
189 // Column sets used in the layout of the bubble.
192 CONTENT_COLUMN_SET_ID
,
193 SYNC_PROMO_COLUMN_SET_ID
196 ColumnSet
* cs
= layout
->AddColumnSet(TITLE_COLUMN_SET_ID
);
197 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
198 cs
->AddColumn(GridLayout::CENTER
, GridLayout::CENTER
, 0, GridLayout::USE_PREF
,
200 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
202 // The column layout used for middle and bottom rows.
203 cs
= layout
->AddColumnSet(CONTENT_COLUMN_SET_ID
);
204 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
205 cs
->AddColumn(GridLayout::LEADING
, GridLayout::CENTER
, 0,
206 GridLayout::USE_PREF
, 0, 0);
207 cs
->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing
);
209 cs
->AddColumn(GridLayout::FILL
, GridLayout::CENTER
, 0,
210 GridLayout::USE_PREF
, 0, 0);
211 cs
->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing
);
213 cs
->AddColumn(GridLayout::LEADING
, GridLayout::TRAILING
, 0,
214 GridLayout::USE_PREF
, 0, 0);
215 cs
->AddPaddingColumn(0, views::kRelatedButtonHSpacing
);
216 cs
->AddColumn(GridLayout::LEADING
, GridLayout::TRAILING
, 0,
217 GridLayout::USE_PREF
, 0, 0);
218 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
220 layout
->StartRow(0, TITLE_COLUMN_SET_ID
);
221 layout
->AddView(title_label
);
222 layout
->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing
);
224 layout
->StartRow(0, CONTENT_COLUMN_SET_ID
);
225 views::Label
* label
= new views::Label(
226 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT
));
227 layout
->AddView(label
);
228 title_tf_
= new views::Textfield();
229 title_tf_
->SetText(GetTitle());
230 layout
->AddView(title_tf_
, 5, 1);
232 layout
->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing
);
234 layout
->StartRow(0, CONTENT_COLUMN_SET_ID
);
235 layout
->AddView(combobox_label
);
236 layout
->AddView(parent_combobox_
, 5, 1);
238 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
240 layout
->StartRow(0, CONTENT_COLUMN_SET_ID
);
241 layout
->SkipColumns(2);
242 layout
->AddView(remove_button_
);
243 layout
->AddView(edit_button_
);
244 layout
->AddView(close_button_
);
246 layout
->AddPaddingRow(
248 views::kUnrelatedControlVerticalSpacing
- kControlBorderWidth
);
250 if (SyncPromoUI::ShouldShowSyncPromo(profile_
)) {
251 // The column layout used for the sync promo.
252 cs
= layout
->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID
);
253 cs
->AddColumn(GridLayout::FILL
,
256 GridLayout::USE_PREF
,
259 layout
->StartRow(0, SYNC_PROMO_COLUMN_SET_ID
);
261 sync_promo_view_
= new BookmarkSyncPromoView(delegate_
.get());
262 layout
->AddView(sync_promo_view_
);
265 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN
, ui::EF_NONE
));
268 BookmarkBubbleView::BookmarkBubbleView(
269 views::View
* anchor_view
,
270 BookmarkBubbleViewObserver
* observer
,
271 scoped_ptr
<BookmarkBubbleDelegate
> delegate
,
274 bool newly_bookmarked
)
275 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
277 delegate_(delegate
.Pass()),
280 newly_bookmarked_(newly_bookmarked
),
282 BookmarkModelFactory::GetForProfile(profile_
),
283 BookmarkModelFactory::GetForProfile(profile_
)->
284 GetMostRecentlyAddedNodeForURL(url
)),
285 remove_button_(NULL
),
289 parent_combobox_(NULL
),
290 sync_promo_view_(NULL
),
291 remove_bookmark_(false),
293 const SkColor background_color
= GetNativeTheme()->GetSystemColor(
294 ui::NativeTheme::kColorId_DialogBackground
);
295 set_color(background_color
);
296 set_background(views::Background::CreateSolidBackground(background_color
));
297 set_margins(gfx::Insets(views::kPanelVertMargin
, 0, 0, 0));
298 // Compensate for built-in vertical padding in the anchor view's image.
299 set_anchor_view_insets(gfx::Insets(7, 0, 7, 0));
302 string16
BookmarkBubbleView::GetTitle() {
303 BookmarkModel
* bookmark_model
=
304 BookmarkModelFactory::GetForProfile(profile_
);
305 const BookmarkNode
* node
=
306 bookmark_model
->GetMostRecentlyAddedNodeForURL(url_
);
308 return node
->GetTitle();
314 gfx::Size
BookmarkBubbleView::GetMinimumSize() {
315 gfx::Size
size(views::BubbleDelegateView::GetPreferredSize());
316 size
.SetToMax(gfx::Size(kMinBubbleWidth
, 0));
320 void BookmarkBubbleView::ButtonPressed(views::Button
* sender
,
321 const ui::Event
& event
) {
322 HandleButtonPressed(sender
);
325 void BookmarkBubbleView::OnSelectedIndexChanged(views::Combobox
* combobox
) {
326 if (combobox
->selected_index() + 1 == parent_model_
.GetItemCount()) {
327 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
332 void BookmarkBubbleView::HandleButtonPressed(views::Button
* sender
) {
333 if (sender
== remove_button_
) {
334 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
335 // Set this so we remove the bookmark after the window closes.
336 remove_bookmark_
= true;
337 apply_edits_
= false;
339 } else if (sender
== edit_button_
) {
340 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
343 DCHECK_EQ(close_button_
, sender
);
348 void BookmarkBubbleView::ShowEditor() {
349 const BookmarkNode
* node
= BookmarkModelFactory::GetForProfile(
350 profile_
)->GetMostRecentlyAddedNodeForURL(url_
);
351 views::Widget
* parent
= anchor_widget();
354 Profile
* profile
= profile_
;
356 GetWidget()->Close();
359 BookmarkEditor::Show(parent
->GetNativeWindow(), profile
,
360 BookmarkEditor::EditDetails::EditNode(node
),
361 BookmarkEditor::SHOW_TREE
);
364 void BookmarkBubbleView::ApplyEdits() {
365 // Set this to make sure we don't attempt to apply edits again.
366 apply_edits_
= false;
368 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(profile_
);
369 const BookmarkNode
* node
= model
->GetMostRecentlyAddedNodeForURL(url_
);
371 const string16 new_title
= title_tf_
->text();
372 if (new_title
!= node
->GetTitle()) {
373 model
->SetTitle(node
, new_title
);
374 content::RecordAction(
375 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
377 parent_model_
.MaybeChangeParent(node
, parent_combobox_
->selected_index());