Upstream version 5.34.92.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.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/sync/sync_promo_ui.h"
17 #include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.h"
18 #include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.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/accessibility/accessible_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::Init() {
132   views::Label* title_label = new views::Label(
133       l10n_util::GetStringUTF16(
134           newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
135                               IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
136   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
137   title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::MediumFont));
138
139   remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
140       IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
141   remove_button_->SetStyle(views::Button::STYLE_BUTTON);
142
143   edit_button_ = new views::LabelButton(
144       this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
145   edit_button_->SetStyle(views::Button::STYLE_BUTTON);
146
147   close_button_ = new views::LabelButton(
148       this, l10n_util::GetStringUTF16(IDS_DONE));
149   close_button_->SetStyle(views::Button::STYLE_BUTTON);
150   close_button_->SetIsDefault(true);
151
152   views::Label* combobox_label = new views::Label(
153       l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));
154
155   parent_combobox_ = new views::Combobox(&parent_model_);
156   parent_combobox_->set_listener(this);
157   parent_combobox_->SetAccessibleName(
158       l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_TEXT));
159
160   GridLayout* layout = new GridLayout(this);
161   SetLayoutManager(layout);
162
163   // Column sets used in the layout of the bubble.
164   enum ColumnSetID {
165     TITLE_COLUMN_SET_ID,
166     CONTENT_COLUMN_SET_ID,
167     SYNC_PROMO_COLUMN_SET_ID
168   };
169
170   ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
171   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
172   cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
173                 0, 0);
174   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
175
176   // The column layout used for middle and bottom rows.
177   cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
178   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
179   cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
180                 GridLayout::USE_PREF, 0, 0);
181   cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
182
183   cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
184                 GridLayout::USE_PREF, 0, 0);
185   cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);
186
187   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
188                 GridLayout::USE_PREF, 0, 0);
189   cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
190   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
191                 GridLayout::USE_PREF, 0, 0);
192   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
193
194   layout->StartRow(0, TITLE_COLUMN_SET_ID);
195   layout->AddView(title_label);
196   layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
197
198   layout->StartRow(0, CONTENT_COLUMN_SET_ID);
199   views::Label* label = new views::Label(
200       l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
201   layout->AddView(label);
202   title_tf_ = new views::Textfield();
203   title_tf_->SetText(GetTitle());
204   title_tf_->SetAccessibleName(
205       l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_TITLE_TEXT));
206
207   layout->AddView(title_tf_, 5, 1);
208
209   layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);
210
211   layout->StartRow(0, CONTENT_COLUMN_SET_ID);
212   layout->AddView(combobox_label);
213   layout->AddView(parent_combobox_, 5, 1);
214
215   layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
216
217   layout->StartRow(0, CONTENT_COLUMN_SET_ID);
218   layout->SkipColumns(2);
219   layout->AddView(remove_button_);
220   layout->AddView(edit_button_);
221   layout->AddView(close_button_);
222
223   layout->AddPaddingRow(
224       0,
225       views::kUnrelatedControlVerticalSpacing - kControlBorderWidth);
226
227   if (SyncPromoUI::ShouldShowSyncPromo(profile_)) {
228     // The column layout used for the sync promo.
229     cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID);
230     cs->AddColumn(GridLayout::FILL,
231                   GridLayout::FILL,
232                   1,
233                   GridLayout::USE_PREF,
234                   0,
235                   0);
236     layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID);
237
238     sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get());
239     layout->AddView(sync_promo_view_);
240   }
241
242   AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
243 }
244
245 BookmarkBubbleView::BookmarkBubbleView(
246     views::View* anchor_view,
247     BookmarkBubbleViewObserver* observer,
248     scoped_ptr<BookmarkBubbleDelegate> delegate,
249     Profile* profile,
250     const GURL& url,
251     bool newly_bookmarked)
252     : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
253       observer_(observer),
254       delegate_(delegate.Pass()),
255       profile_(profile),
256       url_(url),
257       newly_bookmarked_(newly_bookmarked),
258       parent_model_(
259           BookmarkModelFactory::GetForProfile(profile_),
260           BookmarkModelFactory::GetForProfile(profile_)->
261               GetMostRecentlyAddedNodeForURL(url)),
262       remove_button_(NULL),
263       edit_button_(NULL),
264       close_button_(NULL),
265       title_tf_(NULL),
266       parent_combobox_(NULL),
267       sync_promo_view_(NULL),
268       remove_bookmark_(false),
269       apply_edits_(true) {
270   const SkColor background_color = GetNativeTheme()->GetSystemColor(
271       ui::NativeTheme::kColorId_DialogBackground);
272   set_color(background_color);
273   set_background(views::Background::CreateSolidBackground(background_color));
274   set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
275   // Compensate for built-in vertical padding in the anchor view's image.
276   set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
277 }
278
279 base::string16 BookmarkBubbleView::GetTitle() {
280   BookmarkModel* bookmark_model =
281       BookmarkModelFactory::GetForProfile(profile_);
282   const BookmarkNode* node =
283       bookmark_model->GetMostRecentlyAddedNodeForURL(url_);
284   if (node)
285     return node->GetTitle();
286   else
287     NOTREACHED();
288   return base::string16();
289 }
290
291 gfx::Size BookmarkBubbleView::GetMinimumSize() {
292   gfx::Size size(views::BubbleDelegateView::GetPreferredSize());
293   size.SetToMax(gfx::Size(kMinBubbleWidth, 0));
294   return size;
295 }
296
297 void BookmarkBubbleView::GetAccessibleState(ui::AccessibleViewState* state) {
298   BubbleDelegateView::GetAccessibleState(state);
299   state->name =
300       l10n_util::GetStringUTF16(
301           newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
302                               IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK);
303 }
304
305 void BookmarkBubbleView::ButtonPressed(views::Button* sender,
306                                        const ui::Event& event) {
307   HandleButtonPressed(sender);
308 }
309
310 void BookmarkBubbleView::OnSelectedIndexChanged(views::Combobox* combobox) {
311   if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
312     content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
313     ShowEditor();
314   }
315 }
316
317 void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
318   if (sender == remove_button_) {
319     content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
320     // Set this so we remove the bookmark after the window closes.
321     remove_bookmark_ = true;
322     apply_edits_ = false;
323     StartFade(false);
324   } else if (sender == edit_button_) {
325     content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
326     ShowEditor();
327   } else {
328     DCHECK_EQ(close_button_, sender);
329     StartFade(false);
330   }
331 }
332
333 void BookmarkBubbleView::ShowEditor() {
334   const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
335       profile_)->GetMostRecentlyAddedNodeForURL(url_);
336   views::Widget* parent = anchor_widget();
337   DCHECK(parent);
338
339   Profile* profile = profile_;
340   ApplyEdits();
341   GetWidget()->Close();
342
343   if (node && parent)
344     BookmarkEditor::Show(parent->GetNativeWindow(), profile,
345                          BookmarkEditor::EditDetails::EditNode(node),
346                          BookmarkEditor::SHOW_TREE);
347 }
348
349 void BookmarkBubbleView::ApplyEdits() {
350   // Set this to make sure we don't attempt to apply edits again.
351   apply_edits_ = false;
352
353   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
354   const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
355   if (node) {
356     const base::string16 new_title = title_tf_->text();
357     if (new_title != node->GetTitle()) {
358       model->SetTitle(node, new_title);
359       content::RecordAction(
360           UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
361     }
362     parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());
363   }
364 }