• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_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 "components/bookmarks/browser/bookmark_model.h"
18 #include "components/bookmarks/browser/bookmark_utils.h"
19 #include "content/public/browser/user_metrics.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #include "ui/accessibility/ax_view_state.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/events/keycodes/keyboard_codes.h"
26 #include "ui/views/bubble/bubble_frame_view.h"
27 #include "ui/views/controls/button/label_button.h"
28 #include "ui/views/controls/combobox/combobox.h"
29 #include "ui/views/controls/label.h"
30 #include "ui/views/controls/link.h"
31 #include "ui/views/controls/textfield/textfield.h"
32 #include "ui/views/layout/grid_layout.h"
33 #include "ui/views/layout/layout_constants.h"
34 #include "ui/views/widget/widget.h"
35 
36 using base::UserMetricsAction;
37 using views::ColumnSet;
38 using views::GridLayout;
39 
40 namespace {
41 
42 // Width of the border of a button.
43 const int kControlBorderWidth = 2;
44 
45 // This combobox prevents any lengthy content from stretching the bubble view.
46 class UnsizedCombobox : public views::Combobox {
47  public:
UnsizedCombobox(ui::ComboboxModel * model)48   explicit UnsizedCombobox(ui::ComboboxModel* model) : views::Combobox(model) {}
~UnsizedCombobox()49   virtual ~UnsizedCombobox() {}
50 
GetPreferredSize() const51   virtual gfx::Size GetPreferredSize() const OVERRIDE {
52     return gfx::Size(0, views::Combobox::GetPreferredSize().height());
53   }
54 
55  private:
56   DISALLOW_COPY_AND_ASSIGN(UnsizedCombobox);
57 };
58 
59 }  // namespace
60 
61 BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;
62 
63 // static
ShowBubble(views::View * anchor_view,BookmarkBubbleViewObserver * observer,scoped_ptr<BookmarkBubbleDelegate> delegate,Profile * profile,const GURL & url,bool newly_bookmarked)64 void BookmarkBubbleView::ShowBubble(views::View* anchor_view,
65                                     BookmarkBubbleViewObserver* observer,
66                                     scoped_ptr<BookmarkBubbleDelegate> delegate,
67                                     Profile* profile,
68                                     const GURL& url,
69                                     bool newly_bookmarked) {
70   if (IsShowing())
71     return;
72 
73   bookmark_bubble_ = new BookmarkBubbleView(anchor_view,
74                                             observer,
75                                             delegate.Pass(),
76                                             profile,
77                                             url,
78                                             newly_bookmarked);
79   views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show();
80   // Select the entire title textfield contents when the bubble is first shown.
81   bookmark_bubble_->title_tf_->SelectAll(true);
82   bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
83 
84   if (bookmark_bubble_->observer_)
85     bookmark_bubble_->observer_->OnBookmarkBubbleShown(url);
86 }
87 
88 // static
IsShowing()89 bool BookmarkBubbleView::IsShowing() {
90   return bookmark_bubble_ != NULL;
91 }
92 
Hide()93 void BookmarkBubbleView::Hide() {
94   if (IsShowing())
95     bookmark_bubble_->GetWidget()->Close();
96 }
97 
~BookmarkBubbleView()98 BookmarkBubbleView::~BookmarkBubbleView() {
99   if (apply_edits_) {
100     ApplyEdits();
101   } else if (remove_bookmark_) {
102     BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
103     const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
104     if (node)
105       model->Remove(node->parent(), node->parent()->GetIndexOf(node));
106   }
107   // |parent_combobox_| needs to be destroyed before |parent_model_| as it
108   // uses |parent_model_| in its destructor.
109   delete parent_combobox_;
110 }
111 
GetInitiallyFocusedView()112 views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
113   return title_tf_;
114 }
115 
WindowClosing()116 void BookmarkBubbleView::WindowClosing() {
117   // We have to reset |bubble_| here, not in our destructor, because we'll be
118   // destroyed asynchronously and the shown state will be checked before then.
119   DCHECK_EQ(bookmark_bubble_, this);
120   bookmark_bubble_ = NULL;
121 
122   if (observer_)
123     observer_->OnBookmarkBubbleHidden();
124 }
125 
AcceleratorPressed(const ui::Accelerator & accelerator)126 bool BookmarkBubbleView::AcceleratorPressed(
127     const ui::Accelerator& accelerator) {
128   if (accelerator.key_code() == ui::VKEY_RETURN) {
129      if (edit_button_->HasFocus())
130        HandleButtonPressed(edit_button_);
131      else
132        HandleButtonPressed(close_button_);
133      return true;
134   } else if (accelerator.key_code() == ui::VKEY_ESCAPE) {
135     remove_bookmark_ = newly_bookmarked_;
136     apply_edits_ = false;
137   }
138 
139   return BubbleDelegateView::AcceleratorPressed(accelerator);
140 }
141 
Init()142 void BookmarkBubbleView::Init() {
143   views::Label* title_label = new views::Label(
144       l10n_util::GetStringUTF16(
145           newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
146                               IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
147   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
148   title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::MediumFont));
149 
150   remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
151       IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
152   remove_button_->SetStyle(views::Button::STYLE_BUTTON);
153 
154   edit_button_ = new views::LabelButton(
155       this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
156   edit_button_->SetStyle(views::Button::STYLE_BUTTON);
157 
158   close_button_ = new views::LabelButton(
159       this, l10n_util::GetStringUTF16(IDS_DONE));
160   close_button_->SetStyle(views::Button::STYLE_BUTTON);
161   close_button_->SetIsDefault(true);
162 
163   views::Label* combobox_label = new views::Label(
164       l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));
165 
166   parent_combobox_ = new UnsizedCombobox(&parent_model_);
167   parent_combobox_->set_listener(this);
168   parent_combobox_->SetAccessibleName(
169       l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_TEXT));
170 
171   GridLayout* layout = new GridLayout(this);
172   SetLayoutManager(layout);
173 
174   // Column sets used in the layout of the bubble.
175   enum ColumnSetID {
176     TITLE_COLUMN_SET_ID,
177     CONTENT_COLUMN_SET_ID,
178     SYNC_PROMO_COLUMN_SET_ID
179   };
180 
181   ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
182   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
183   cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
184                 0, 0);
185   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
186 
187   // The column layout used for middle and bottom rows.
188   cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
189   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
190   cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
191                 GridLayout::USE_PREF, 0, 0);
192   cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
193 
194   cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
195                 GridLayout::USE_PREF, 0, 0);
196   cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);
197 
198   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
199                 GridLayout::USE_PREF, 0, 0);
200   cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
201   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
202                 GridLayout::USE_PREF, 0, 0);
203   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
204 
205   layout->StartRow(0, TITLE_COLUMN_SET_ID);
206   layout->AddView(title_label);
207   layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
208 
209   layout->StartRow(0, CONTENT_COLUMN_SET_ID);
210   views::Label* label = new views::Label(
211       l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
212   layout->AddView(label);
213   title_tf_ = new views::Textfield();
214   title_tf_->SetText(GetTitle());
215   title_tf_->SetAccessibleName(
216       l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_TITLE_TEXT));
217 
218   layout->AddView(title_tf_, 5, 1);
219 
220   layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
221 
222   layout->StartRow(0, CONTENT_COLUMN_SET_ID);
223   layout->AddView(combobox_label);
224   layout->AddView(parent_combobox_, 5, 1);
225 
226   layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
227 
228   layout->StartRow(0, CONTENT_COLUMN_SET_ID);
229   layout->SkipColumns(2);
230   layout->AddView(remove_button_);
231   layout->AddView(edit_button_);
232   layout->AddView(close_button_);
233 
234   layout->AddPaddingRow(
235       0,
236       views::kUnrelatedControlVerticalSpacing - kControlBorderWidth);
237 
238   if (SyncPromoUI::ShouldShowSyncPromo(profile_)) {
239     // The column layout used for the sync promo.
240     cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID);
241     cs->AddColumn(GridLayout::FILL,
242                   GridLayout::FILL,
243                   1,
244                   GridLayout::USE_PREF,
245                   0,
246                   0);
247     layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID);
248 
249     sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get());
250     layout->AddView(sync_promo_view_);
251   }
252 
253   AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
254 }
255 
BookmarkBubbleView(views::View * anchor_view,BookmarkBubbleViewObserver * observer,scoped_ptr<BookmarkBubbleDelegate> delegate,Profile * profile,const GURL & url,bool newly_bookmarked)256 BookmarkBubbleView::BookmarkBubbleView(
257     views::View* anchor_view,
258     BookmarkBubbleViewObserver* observer,
259     scoped_ptr<BookmarkBubbleDelegate> delegate,
260     Profile* profile,
261     const GURL& url,
262     bool newly_bookmarked)
263     : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
264       observer_(observer),
265       delegate_(delegate.Pass()),
266       profile_(profile),
267       url_(url),
268       newly_bookmarked_(newly_bookmarked),
269       parent_model_(
270           BookmarkModelFactory::GetForProfile(profile_),
271           BookmarkModelFactory::GetForProfile(profile_)->
272               GetMostRecentlyAddedUserNodeForURL(url)),
273       remove_button_(NULL),
274       edit_button_(NULL),
275       close_button_(NULL),
276       title_tf_(NULL),
277       parent_combobox_(NULL),
278       sync_promo_view_(NULL),
279       remove_bookmark_(false),
280       apply_edits_(true) {
281   set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
282   // Compensate for built-in vertical padding in the anchor view's image.
283   set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
284 }
285 
GetTitle()286 base::string16 BookmarkBubbleView::GetTitle() {
287   BookmarkModel* bookmark_model =
288       BookmarkModelFactory::GetForProfile(profile_);
289   const BookmarkNode* node =
290       bookmark_model->GetMostRecentlyAddedUserNodeForURL(url_);
291   if (node)
292     return node->GetTitle();
293   else
294     NOTREACHED();
295   return base::string16();
296 }
297 
GetAccessibleState(ui::AXViewState * state)298 void BookmarkBubbleView::GetAccessibleState(ui::AXViewState* state) {
299   BubbleDelegateView::GetAccessibleState(state);
300   state->name =
301       l10n_util::GetStringUTF16(
302           newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
303                               IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK);
304 }
305 
ButtonPressed(views::Button * sender,const ui::Event & event)306 void BookmarkBubbleView::ButtonPressed(views::Button* sender,
307                                        const ui::Event& event) {
308   HandleButtonPressed(sender);
309 }
310 
OnPerformAction(views::Combobox * combobox)311 void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
312   if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
313     content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
314     ShowEditor();
315   }
316 }
317 
HandleButtonPressed(views::Button * sender)318 void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
319   if (sender == remove_button_) {
320     content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
321     // Set this so we remove the bookmark after the window closes.
322     remove_bookmark_ = true;
323     apply_edits_ = false;
324     GetWidget()->Close();
325   } else if (sender == edit_button_) {
326     content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
327     ShowEditor();
328   } else {
329     DCHECK_EQ(close_button_, sender);
330     GetWidget()->Close();
331   }
332 }
333 
ShowEditor()334 void BookmarkBubbleView::ShowEditor() {
335   const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
336       profile_)->GetMostRecentlyAddedUserNodeForURL(url_);
337   views::Widget* parent = anchor_widget();
338   DCHECK(parent);
339 
340   Profile* profile = profile_;
341   ApplyEdits();
342   GetWidget()->Close();
343 
344   if (node && parent)
345     BookmarkEditor::Show(parent->GetNativeWindow(), profile,
346                          BookmarkEditor::EditDetails::EditNode(node),
347                          BookmarkEditor::SHOW_TREE);
348 }
349 
ApplyEdits()350 void BookmarkBubbleView::ApplyEdits() {
351   // Set this to make sure we don't attempt to apply edits again.
352   apply_edits_ = false;
353 
354   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
355   const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
356   if (node) {
357     const base::string16 new_title = title_tf_->text();
358     if (new_title != node->GetTitle()) {
359       model->SetTitle(node, new_title);
360       content::RecordAction(
361           UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
362     }
363     parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());
364   }
365 }
366