bookmark_bubble_view.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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.
4
5#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h"
6
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/browser.h"
17#include "chrome/browser/ui/browser_list.h"
18#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.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"
34
35using content::UserMetricsAction;
36using views::ColumnSet;
37using views::GridLayout;
38
39namespace {
40
41// Minimum width of the the bubble.
42const int kMinBubbleWidth = 350;
43
44}  // namespace
45
46// Declared in browser_dialogs.h so callers don't have to depend on our header.
47
48namespace chrome {
49
50void ShowBookmarkBubbleView(views::View* anchor_view,
51                            BookmarkBubbleViewObserver* observer,
52                            Profile* profile,
53                            const GURL& url,
54                            bool newly_bookmarked) {
55  BookmarkBubbleView::ShowBubble(anchor_view, observer, profile, url,
56                                 newly_bookmarked);
57}
58
59void HideBookmarkBubbleView() {
60  BookmarkBubbleView::Hide();
61}
62
63bool IsBookmarkBubbleViewShowing() {
64  return BookmarkBubbleView::IsShowing();
65}
66
67}  // namespace chrome
68
69// BookmarkBubbleView ---------------------------------------------------------
70
71BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;
72
73// static
74void BookmarkBubbleView::ShowBubble(views::View* anchor_view,
75                                    BookmarkBubbleViewObserver* observer,
76                                    Profile* profile,
77                                    const GURL& url,
78                                    bool newly_bookmarked) {
79  if (IsShowing())
80    return;
81
82  bookmark_bubble_ = new BookmarkBubbleView(anchor_view, observer, profile, url,
83                                            newly_bookmarked);
84  views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show();
85  // Select the entire title textfield contents when the bubble is first shown.
86  bookmark_bubble_->title_tf_->SelectAll(true);
87  bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
88
89  if (bookmark_bubble_->observer_)
90    bookmark_bubble_->observer_->OnBookmarkBubbleShown(url);
91}
92
93// static
94bool BookmarkBubbleView::IsShowing() {
95  return bookmark_bubble_ != NULL;
96}
97
98void BookmarkBubbleView::Hide() {
99  if (IsShowing())
100    bookmark_bubble_->GetWidget()->Close();
101}
102
103BookmarkBubbleView::~BookmarkBubbleView() {
104  if (apply_edits_) {
105    ApplyEdits();
106  } else if (remove_bookmark_) {
107    BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
108    const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
109    if (node)
110      model->Remove(node->parent(), node->parent()->GetIndexOf(node));
111  }
112}
113
114views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
115  return title_tf_;
116}
117
118void BookmarkBubbleView::WindowClosing() {
119  // We have to reset |bubble_| here, not in our destructor, because we'll be
120  // destroyed asynchronously and the shown state will be checked before then.
121  DCHECK_EQ(bookmark_bubble_, this);
122  bookmark_bubble_ = NULL;
123
124  if (observer_)
125    observer_->OnBookmarkBubbleHidden();
126 }
127
128bool BookmarkBubbleView::AcceleratorPressed(
129    const ui::Accelerator& accelerator) {
130  if (accelerator.key_code() == ui::VKEY_RETURN) {
131     if (edit_button_->HasFocus())
132       HandleButtonPressed(edit_button_);
133     else
134       HandleButtonPressed(close_button_);
135     return true;
136  } else if (accelerator.key_code() == ui::VKEY_ESCAPE) {
137    remove_bookmark_ = newly_bookmarked_;
138    apply_edits_ = false;
139  }
140
141  return BubbleDelegateView::AcceleratorPressed(accelerator);
142}
143
144void BookmarkBubbleView::Init() {
145  views::Label* title_label = new views::Label(
146      l10n_util::GetStringUTF16(
147          newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
148                              IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
149  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
150  title_label->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont));
151
152  remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
153      IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
154  remove_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
155
156  edit_button_ = new views::LabelButton(
157      this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
158  edit_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
159
160  close_button_ = new views::LabelButton(
161      this, l10n_util::GetStringUTF16(IDS_DONE));
162  close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
163  close_button_->SetIsDefault(true);
164
165  views::Label* combobox_label = new views::Label(
166      l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));
167
168  parent_combobox_ = new views::Combobox(&parent_model_);
169  parent_combobox_->set_listener(this);
170  parent_combobox_->SetAccessibleName(combobox_label->text());
171
172  GridLayout* layout = new GridLayout(this);
173  SetLayoutManager(layout);
174
175  const int kTitleColumnSetID = 0;
176  ColumnSet* cs = layout->AddColumnSet(kTitleColumnSetID);
177  cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
178                0, 0);
179
180  // The column layout used for middle and bottom rows.
181  const int kFirstColumnSetID = 1;
182  cs = layout->AddColumnSet(kFirstColumnSetID);
183  cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
184                GridLayout::USE_PREF, 0, 0);
185  cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
186
187  cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
188                GridLayout::USE_PREF, 0, 0);
189  cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);
190
191  cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
192                GridLayout::USE_PREF, 0, 0);
193  cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
194  cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
195                GridLayout::USE_PREF, 0, 0);
196
197  layout->StartRow(0, kTitleColumnSetID);
198  layout->AddView(title_label);
199  layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
200
201  layout->StartRow(0, kFirstColumnSetID);
202  views::Label* label = new views::Label(
203      l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
204  layout->AddView(label);
205  title_tf_ = new views::Textfield();
206  title_tf_->SetText(GetTitle());
207  layout->AddView(title_tf_, 5, 1);
208
209  layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
210
211  layout->StartRow(0, kFirstColumnSetID);
212  layout->AddView(combobox_label);
213  layout->AddView(parent_combobox_, 5, 1);
214
215  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
216
217  layout->StartRow(0, kFirstColumnSetID);
218  layout->SkipColumns(2);
219  layout->AddView(remove_button_);
220  layout->AddView(edit_button_);
221  layout->AddView(close_button_);
222
223  AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
224}
225
226BookmarkBubbleView::BookmarkBubbleView(views::View* anchor_view,
227                                       BookmarkBubbleViewObserver* observer,
228                                       Profile* profile,
229                                       const GURL& url,
230                                       bool newly_bookmarked)
231    : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
232      observer_(observer),
233      profile_(profile),
234      url_(url),
235      newly_bookmarked_(newly_bookmarked),
236      parent_model_(
237          BookmarkModelFactory::GetForProfile(profile_),
238          BookmarkModelFactory::GetForProfile(profile_)->
239              GetMostRecentlyAddedNodeForURL(url)),
240      remove_button_(NULL),
241      edit_button_(NULL),
242      close_button_(NULL),
243      title_tf_(NULL),
244      parent_combobox_(NULL),
245      remove_bookmark_(false),
246      apply_edits_(true) {
247  const SkColor background_color = GetNativeTheme()->GetSystemColor(
248      ui::NativeTheme::kColorId_DialogBackground);
249  set_color(background_color);
250  set_background(views::Background::CreateSolidBackground(background_color));
251  set_margins(gfx::Insets(12, 19, 18, 18));
252  // Compensate for built-in vertical padding in the anchor view's image.
253  set_anchor_view_insets(gfx::Insets(7, 0, 7, 0));
254}
255
256string16 BookmarkBubbleView::GetTitle() {
257  BookmarkModel* bookmark_model =
258      BookmarkModelFactory::GetForProfile(profile_);
259  const BookmarkNode* node =
260      bookmark_model->GetMostRecentlyAddedNodeForURL(url_);
261  if (node)
262    return node->GetTitle();
263  else
264    NOTREACHED();
265  return string16();
266}
267
268gfx::Size BookmarkBubbleView::GetMinimumSize() {
269  gfx::Size size(views::BubbleDelegateView::GetPreferredSize());
270  size.SetToMax(gfx::Size(kMinBubbleWidth, 0));
271  return size;
272}
273
274void BookmarkBubbleView::ButtonPressed(views::Button* sender,
275                                       const ui::Event& event) {
276  HandleButtonPressed(sender);
277}
278
279void BookmarkBubbleView::OnSelectedIndexChanged(views::Combobox* combobox) {
280  if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
281    content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
282    ShowEditor();
283  }
284}
285
286void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
287  if (sender == remove_button_) {
288    content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
289    // Set this so we remove the bookmark after the window closes.
290    remove_bookmark_ = true;
291    apply_edits_ = false;
292    StartFade(false);
293  } else if (sender == edit_button_) {
294    content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
295    ShowEditor();
296  } else {
297    DCHECK_EQ(close_button_, sender);
298    StartFade(false);
299  }
300}
301
302void BookmarkBubbleView::ShowEditor() {
303  const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
304      profile_)->GetMostRecentlyAddedNodeForURL(url_);
305  views::Widget* parent = anchor_widget();
306  DCHECK(parent);
307
308  Profile* profile = profile_;
309  ApplyEdits();
310  GetWidget()->Close();
311
312  if (node && parent)
313    BookmarkEditor::Show(parent->GetNativeWindow(), profile,
314                         BookmarkEditor::EditDetails::EditNode(node),
315                         BookmarkEditor::SHOW_TREE);
316}
317
318void BookmarkBubbleView::ApplyEdits() {
319  // Set this to make sure we don't attempt to apply edits again.
320  apply_edits_ = false;
321
322  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
323  const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
324  if (node) {
325    const string16 new_title = title_tf_->text();
326    if (new_title != node->GetTitle()) {
327      model->SetTitle(node, new_title);
328      content::RecordAction(
329          UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
330    }
331    parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());
332  }
333}
334