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