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