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/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 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 BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;
52 void BookmarkBubbleView::ShowBubble(views::View* anchor_view,
53 BookmarkBubbleViewObserver* observer,
54 scoped_ptr<BookmarkBubbleDelegate> delegate,
57 bool newly_bookmarked) {
61 bookmark_bubble_ = new BookmarkBubbleView(anchor_view,
67 views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show();
68 // Select the entire title textfield contents when the bubble is first shown.
69 bookmark_bubble_->title_tf_->SelectAll(true);
70 bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
72 if (bookmark_bubble_->observer_)
73 bookmark_bubble_->observer_->OnBookmarkBubbleShown(url);
77 bool BookmarkBubbleView::IsShowing() {
78 return bookmark_bubble_ != NULL;
81 void BookmarkBubbleView::Hide() {
83 bookmark_bubble_->GetWidget()->Close();
86 BookmarkBubbleView::~BookmarkBubbleView() {
89 } else if (remove_bookmark_) {
90 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
91 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
93 model->Remove(node->parent(), node->parent()->GetIndexOf(node));
95 // |parent_combobox_| needs to be destroyed before |parent_model_| as it
96 // uses |parent_model_| in its destructor.
97 delete parent_combobox_;
100 views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
104 void BookmarkBubbleView::WindowClosing() {
105 // We have to reset |bubble_| here, not in our destructor, because we'll be
106 // destroyed asynchronously and the shown state will be checked before then.
107 DCHECK_EQ(bookmark_bubble_, this);
108 bookmark_bubble_ = NULL;
111 observer_->OnBookmarkBubbleHidden();
114 bool BookmarkBubbleView::AcceleratorPressed(
115 const ui::Accelerator& accelerator) {
116 if (accelerator.key_code() == ui::VKEY_RETURN) {
117 if (edit_button_->HasFocus())
118 HandleButtonPressed(edit_button_);
120 HandleButtonPressed(close_button_);
122 } else if (accelerator.key_code() == ui::VKEY_ESCAPE) {
123 remove_bookmark_ = newly_bookmarked_;
124 apply_edits_ = false;
127 return BubbleDelegateView::AcceleratorPressed(accelerator);
130 void BookmarkBubbleView::Init() {
131 views::Label* title_label = new views::Label(
132 l10n_util::GetStringUTF16(
133 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
134 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
135 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
136 title_label->SetFont(rb->GetFont(ui::ResourceBundle::MediumFont));
138 remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
139 IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
140 remove_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
142 edit_button_ = new views::LabelButton(
143 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
144 edit_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
146 close_button_ = new views::LabelButton(
147 this, l10n_util::GetStringUTF16(IDS_DONE));
148 close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
149 close_button_->SetIsDefault(true);
151 views::Label* combobox_label = new views::Label(
152 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));
154 parent_combobox_ = new views::Combobox(&parent_model_);
155 parent_combobox_->set_listener(this);
156 parent_combobox_->SetAccessibleName(combobox_label->text());
158 GridLayout* layout = new GridLayout(this);
159 SetLayoutManager(layout);
161 // Column sets used in the layout of the bubble.
164 CONTENT_COLUMN_SET_ID,
165 SYNC_PROMO_COLUMN_SET_ID
168 ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
169 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
170 cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
172 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
174 // The column layout used for middle and bottom rows.
175 cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
176 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
177 cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
178 GridLayout::USE_PREF, 0, 0);
179 cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
181 cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
182 GridLayout::USE_PREF, 0, 0);
183 cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);
185 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
186 GridLayout::USE_PREF, 0, 0);
187 cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
188 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
189 GridLayout::USE_PREF, 0, 0);
190 cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
192 layout->StartRow(0, TITLE_COLUMN_SET_ID);
193 layout->AddView(title_label);
194 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
196 layout->StartRow(0, CONTENT_COLUMN_SET_ID);
197 views::Label* label = new views::Label(
198 l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
199 layout->AddView(label);
200 title_tf_ = new views::Textfield();
201 title_tf_->SetText(GetTitle());
202 layout->AddView(title_tf_, 5, 1);
204 layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
206 layout->StartRow(0, CONTENT_COLUMN_SET_ID);
207 layout->AddView(combobox_label);
208 layout->AddView(parent_combobox_, 5, 1);
210 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
212 layout->StartRow(0, CONTENT_COLUMN_SET_ID);
213 layout->SkipColumns(2);
214 layout->AddView(remove_button_);
215 layout->AddView(edit_button_);
216 layout->AddView(close_button_);
218 layout->AddPaddingRow(
220 views::kUnrelatedControlVerticalSpacing - kControlBorderWidth);
222 if (SyncPromoUI::ShouldShowSyncPromo(profile_)) {
223 // The column layout used for the sync promo.
224 cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID);
225 cs->AddColumn(GridLayout::FILL,
228 GridLayout::USE_PREF,
231 layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID);
233 sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get());
234 layout->AddView(sync_promo_view_);
237 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
240 BookmarkBubbleView::BookmarkBubbleView(
241 views::View* anchor_view,
242 BookmarkBubbleViewObserver* observer,
243 scoped_ptr<BookmarkBubbleDelegate> delegate,
246 bool newly_bookmarked)
247 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
249 delegate_(delegate.Pass()),
252 newly_bookmarked_(newly_bookmarked),
254 BookmarkModelFactory::GetForProfile(profile_),
255 BookmarkModelFactory::GetForProfile(profile_)->
256 GetMostRecentlyAddedNodeForURL(url)),
257 remove_button_(NULL),
261 parent_combobox_(NULL),
262 sync_promo_view_(NULL),
263 remove_bookmark_(false),
265 const SkColor background_color = GetNativeTheme()->GetSystemColor(
266 ui::NativeTheme::kColorId_DialogBackground);
267 set_color(background_color);
268 set_background(views::Background::CreateSolidBackground(background_color));
269 set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
270 // Compensate for built-in vertical padding in the anchor view's image.
271 set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
274 string16 BookmarkBubbleView::GetTitle() {
275 BookmarkModel* bookmark_model =
276 BookmarkModelFactory::GetForProfile(profile_);
277 const BookmarkNode* node =
278 bookmark_model->GetMostRecentlyAddedNodeForURL(url_);
280 return node->GetTitle();
286 gfx::Size BookmarkBubbleView::GetMinimumSize() {
287 gfx::Size size(views::BubbleDelegateView::GetPreferredSize());
288 size.SetToMax(gfx::Size(kMinBubbleWidth, 0));
292 void BookmarkBubbleView::ButtonPressed(views::Button* sender,
293 const ui::Event& event) {
294 HandleButtonPressed(sender);
297 void BookmarkBubbleView::OnSelectedIndexChanged(views::Combobox* combobox) {
298 if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
299 content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
304 void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
305 if (sender == remove_button_) {
306 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
307 // Set this so we remove the bookmark after the window closes.
308 remove_bookmark_ = true;
309 apply_edits_ = false;
311 } else if (sender == edit_button_) {
312 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
315 DCHECK_EQ(close_button_, sender);
320 void BookmarkBubbleView::ShowEditor() {
321 const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
322 profile_)->GetMostRecentlyAddedNodeForURL(url_);
323 views::Widget* parent = anchor_widget();
326 Profile* profile = profile_;
328 GetWidget()->Close();
331 BookmarkEditor::Show(parent->GetNativeWindow(), profile,
332 BookmarkEditor::EditDetails::EditNode(node),
333 BookmarkEditor::SHOW_TREE);
336 void BookmarkBubbleView::ApplyEdits() {
337 // Set this to make sure we don't attempt to apply edits again.
338 apply_edits_ = false;
340 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
341 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
343 const string16 new_title = title_tf_->text();
344 if (new_title != node->GetTitle()) {
345 model->SetTitle(node, new_title);
346 content::RecordAction(
347 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
349 parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());