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