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