• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/string16.h"
8 #include "base/string_util.h"
9 #include "base/utf_string_conversions.h"
10 #include "chrome/app/chrome_command_ids.h"
11 #include "chrome/browser/bookmarks/bookmark_editor.h"
12 #include "chrome/browser/bookmarks/bookmark_model.h"
13 #include "chrome/browser/bookmarks/bookmark_utils.h"
14 #include "chrome/browser/metrics/user_metrics.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_list.h"
18 #include "chrome/browser/ui/views/bubble/bubble.h"
19 #include "content/common/notification_service.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #include "ui/base/keycodes/keyboard_codes.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/color_utils.h"
27 #include "views/controls/button/native_button.h"
28 #include "views/controls/textfield/textfield.h"
29 #include "views/events/event.h"
30 #include "views/focus/focus_manager.h"
31 #include "views/layout/grid_layout.h"
32 #include "views/layout/layout_constants.h"
33 #include "views/window/client_view.h"
34 #include "views/window/window.h"
35 
36 using views::Combobox;
37 using views::ColumnSet;
38 using views::GridLayout;
39 using views::Label;
40 using views::Link;
41 using views::NativeButton;
42 using views::View;
43 
44 // Padding between "Title:" and the actual title.
45 static const int kTitlePadding = 4;
46 
47 // Minimum width for the fields - they will push out the size of the bubble if
48 // necessary. This should be big enough so that the field pushes the right side
49 // of the bubble far enough so that the edit button's left edge is to the right
50 // of the field's left edge.
51 static const int kMinimumFieldSize = 180;
52 
53 // Bubble close image.
54 static SkBitmap* kCloseImage = NULL;
55 
56 // Declared in browser_dialogs.h so callers don't have to depend on our header.
57 
58 namespace browser {
59 
ShowBookmarkBubbleView(views::Window * parent,const gfx::Rect & bounds,BubbleDelegate * delegate,Profile * profile,const GURL & url,bool newly_bookmarked)60 void ShowBookmarkBubbleView(views::Window* parent,
61                             const gfx::Rect& bounds,
62                             BubbleDelegate* delegate,
63                             Profile* profile,
64                             const GURL& url,
65                             bool newly_bookmarked) {
66   BookmarkBubbleView::Show(parent, bounds, delegate, profile, url,
67                            newly_bookmarked);
68 }
69 
HideBookmarkBubbleView()70 void HideBookmarkBubbleView() {
71   BookmarkBubbleView::Hide();
72 }
73 
IsBookmarkBubbleViewShowing()74 bool IsBookmarkBubbleViewShowing() {
75   return BookmarkBubbleView::IsShowing();
76 }
77 
78 }  // namespace browser
79 
80 // BookmarkBubbleView ---------------------------------------------------------
81 
82 BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;
83 
84 // static
Show(views::Window * parent,const gfx::Rect & bounds,BubbleDelegate * delegate,Profile * profile,const GURL & url,bool newly_bookmarked)85 void BookmarkBubbleView::Show(views::Window* parent,
86                               const gfx::Rect& bounds,
87                               BubbleDelegate* delegate,
88                               Profile* profile,
89                               const GURL& url,
90                               bool newly_bookmarked) {
91   if (IsShowing())
92     return;
93 
94   bookmark_bubble_ = new BookmarkBubbleView(delegate, profile, url,
95                                             newly_bookmarked);
96   // TODO(beng): Pass |parent| after V2 is complete.
97   Bubble* bubble = Bubble::Show(
98       parent->client_view()->GetWidget(), bounds, BubbleBorder::TOP_RIGHT,
99       bookmark_bubble_, bookmark_bubble_);
100   // |bubble_| can be set to NULL in BubbleClosing when we close the bubble
101   // asynchronously. However, that can happen during the Show call above if the
102   // window loses activation while we are getting to ready to show the bubble,
103   // so we must check to make sure we still have a valid bubble before
104   // proceeding.
105   if (!bookmark_bubble_)
106     return;
107   bookmark_bubble_->set_bubble(bubble);
108   bubble->SizeToContents();
109   GURL url_ptr(url);
110   NotificationService::current()->Notify(
111       NotificationType::BOOKMARK_BUBBLE_SHOWN,
112       Source<Profile>(profile->GetOriginalProfile()),
113       Details<GURL>(&url_ptr));
114   bookmark_bubble_->BubbleShown();
115 }
116 
117 // static
IsShowing()118 bool BookmarkBubbleView::IsShowing() {
119   return bookmark_bubble_ != NULL;
120 }
121 
Hide()122 void BookmarkBubbleView::Hide() {
123   if (IsShowing())
124     bookmark_bubble_->Close();
125 }
126 
~BookmarkBubbleView()127 BookmarkBubbleView::~BookmarkBubbleView() {
128   if (apply_edits_) {
129     ApplyEdits();
130   } else if (remove_bookmark_) {
131     BookmarkModel* model = profile_->GetBookmarkModel();
132     const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
133     if (node)
134       model->Remove(node->parent(), node->parent()->GetIndexOf(node));
135   }
136 }
137 
BubbleShown()138 void BookmarkBubbleView::BubbleShown() {
139   DCHECK(GetWidget());
140   GetFocusManager()->RegisterAccelerator(
141       views::Accelerator(ui::VKEY_RETURN, false, false, false), this);
142 
143   title_tf_->RequestFocus();
144   title_tf_->SelectAll();
145 }
146 
AcceleratorPressed(const views::Accelerator & accelerator)147 bool BookmarkBubbleView::AcceleratorPressed(
148     const views::Accelerator& accelerator) {
149   if (accelerator.GetKeyCode() != ui::VKEY_RETURN)
150     return false;
151 
152   if (edit_button_->HasFocus())
153     HandleButtonPressed(edit_button_);
154   else
155     HandleButtonPressed(close_button_);
156   return true;
157 }
158 
ViewHierarchyChanged(bool is_add,View * parent,View * child)159 void BookmarkBubbleView::ViewHierarchyChanged(bool is_add, View* parent,
160                                               View* child) {
161   if (is_add && child == this)
162     Init();
163 }
164 
BookmarkBubbleView(BubbleDelegate * delegate,Profile * profile,const GURL & url,bool newly_bookmarked)165 BookmarkBubbleView::BookmarkBubbleView(BubbleDelegate* delegate,
166                                        Profile* profile,
167                                        const GURL& url,
168                                        bool newly_bookmarked)
169     : delegate_(delegate),
170       profile_(profile),
171       url_(url),
172       newly_bookmarked_(newly_bookmarked),
173       parent_model_(
174           profile_->GetBookmarkModel(),
175           profile_->GetBookmarkModel()->GetMostRecentlyAddedNodeForURL(url)),
176       remove_bookmark_(false),
177       apply_edits_(true) {
178 }
179 
Init()180 void BookmarkBubbleView::Init() {
181   static SkColor kTitleColor;
182   static bool initialized = false;
183   if (!initialized) {
184     kTitleColor = color_utils::GetReadableColor(SkColorSetRGB(6, 45, 117),
185                                                 Bubble::kBackgroundColor);
186     kCloseImage = ResourceBundle::GetSharedInstance().GetBitmapNamed(
187       IDR_INFO_BUBBLE_CLOSE);
188 
189     initialized = true;
190   }
191 
192   remove_link_ = new Link(UTF16ToWide(l10n_util::GetStringUTF16(
193       IDS_BOOMARK_BUBBLE_REMOVE_BOOKMARK)));
194   remove_link_->SetController(this);
195 
196   edit_button_ = new NativeButton(
197       this, UTF16ToWide(l10n_util::GetStringUTF16(IDS_BOOMARK_BUBBLE_OPTIONS)));
198 
199   close_button_ =
200       new NativeButton(this, UTF16ToWide(l10n_util::GetStringUTF16(IDS_DONE)));
201   close_button_->SetIsDefault(true);
202 
203   Label* combobox_label = new Label(
204       UTF16ToWide(l10n_util::GetStringUTF16(IDS_BOOMARK_BUBBLE_FOLDER_TEXT)));
205 
206   parent_combobox_ = new Combobox(&parent_model_);
207   parent_combobox_->SetSelectedItem(parent_model_.node_parent_index());
208   parent_combobox_->set_listener(this);
209   parent_combobox_->SetAccessibleName(
210       WideToUTF16Hack(combobox_label->GetText()));
211 #if defined(TOUCH_UI)
212   // TODO(saintlou): This is a short term workaround for touch
213   parent_combobox_->SetEnabled(false);
214 #endif
215 
216   Label* title_label = new Label(UTF16ToWide(l10n_util::GetStringUTF16(
217       newly_bookmarked_ ? IDS_BOOMARK_BUBBLE_PAGE_BOOKMARKED :
218                           IDS_BOOMARK_BUBBLE_PAGE_BOOKMARK)));
219   title_label->SetFont(
220       ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont));
221   title_label->SetColor(kTitleColor);
222 
223   GridLayout* layout = new GridLayout(this);
224   SetLayoutManager(layout);
225 
226   ColumnSet* cs = layout->AddColumnSet(0);
227 
228   // Top (title) row.
229   cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
230                 0, 0);
231   cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing);
232   cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
233                 0, 0);
234 
235   // Middle (input field) rows.
236   cs = layout->AddColumnSet(2);
237   cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
238                 GridLayout::USE_PREF, 0, 0);
239   cs->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
240   cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
241                 GridLayout::USE_PREF, 0, kMinimumFieldSize);
242 
243   // Bottom (buttons) row.
244   cs = layout->AddColumnSet(3);
245   cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
246   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
247                 GridLayout::USE_PREF, 0, 0);
248   // We subtract 2 to account for the natural button padding, and
249   // to bring the separation visually in line with the row separation
250   // height.
251   cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing - 2);
252   cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
253                 GridLayout::USE_PREF, 0, 0);
254 
255   layout->StartRow(0, 0);
256   layout->AddView(title_label);
257   layout->AddView(remove_link_);
258 
259   layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
260   layout->StartRow(0, 2);
261   layout->AddView(new Label(UTF16ToWide(
262       l10n_util::GetStringUTF16(IDS_BOOMARK_BUBBLE_TITLE_TEXT))));
263   title_tf_ = new views::Textfield();
264   title_tf_->SetText(GetTitle());
265   layout->AddView(title_tf_);
266 
267   layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
268 
269   layout->StartRow(0, 2);
270   layout->AddView(combobox_label);
271   layout->AddView(parent_combobox_);
272   layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
273 
274   layout->StartRow(0, 3);
275   layout->AddView(edit_button_);
276   layout->AddView(close_button_);
277 }
278 
GetTitle()279 string16 BookmarkBubbleView::GetTitle() {
280   BookmarkModel* bookmark_model= profile_->GetBookmarkModel();
281   const BookmarkNode* node =
282       bookmark_model->GetMostRecentlyAddedNodeForURL(url_);
283   if (node)
284     return node->GetTitle();
285   else
286     NOTREACHED();
287   return string16();
288 }
289 
ButtonPressed(views::Button * sender,const views::Event & event)290 void BookmarkBubbleView::ButtonPressed(
291     views::Button* sender, const views::Event& event) {
292   HandleButtonPressed(sender);
293 }
294 
LinkActivated(Link * source,int event_flags)295 void BookmarkBubbleView::LinkActivated(Link* source, int event_flags) {
296   DCHECK(source == remove_link_);
297   UserMetrics::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"),
298                             profile_);
299 
300   // Set this so we remove the bookmark after the window closes.
301   remove_bookmark_ = true;
302   apply_edits_ = false;
303 
304   bubble_->set_fade_away_on_close(true);
305   Close();
306 }
307 
ItemChanged(Combobox * combobox,int prev_index,int new_index)308 void BookmarkBubbleView::ItemChanged(Combobox* combobox,
309                                      int prev_index,
310                                      int new_index) {
311   if (new_index + 1 == parent_model_.GetItemCount()) {
312     UserMetrics::RecordAction(
313               UserMetricsAction("BookmarkBubble_EditFromCombobox"), profile_);
314 
315     ShowEditor();
316     return;
317   }
318 }
319 
BubbleClosing(Bubble * bubble,bool closed_by_escape)320 void BookmarkBubbleView::BubbleClosing(Bubble* bubble,
321                                        bool closed_by_escape) {
322   if (closed_by_escape) {
323     remove_bookmark_ = newly_bookmarked_;
324     apply_edits_ = false;
325   }
326 
327   // We have to reset |bubble_| here, not in our destructor, because we'll be
328   // destroyed asynchronously and the shown state will be checked before then.
329   DCHECK(bookmark_bubble_ == this);
330   bookmark_bubble_ = NULL;
331 
332   if (delegate_)
333     delegate_->BubbleClosing(bubble, closed_by_escape);
334   NotificationService::current()->Notify(
335       NotificationType::BOOKMARK_BUBBLE_HIDDEN,
336       Source<Profile>(profile_->GetOriginalProfile()),
337       NotificationService::NoDetails());
338 }
339 
CloseOnEscape()340 bool BookmarkBubbleView::CloseOnEscape() {
341   return delegate_ ? delegate_->CloseOnEscape() : true;
342 }
343 
FadeInOnShow()344 bool BookmarkBubbleView::FadeInOnShow() {
345   return false;
346 }
347 
accessible_name()348 std::wstring BookmarkBubbleView::accessible_name() {
349   return UTF16ToWide(
350       l10n_util::GetStringUTF16(IDS_BOOMARK_BUBBLE_ADD_BOOKMARK));
351 }
352 
Close()353 void BookmarkBubbleView::Close() {
354   ApplyEdits();
355   static_cast<Bubble*>(GetWidget())->Close();
356 }
357 
HandleButtonPressed(views::Button * sender)358 void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
359   if (sender == edit_button_) {
360     UserMetrics::RecordAction(UserMetricsAction("BookmarkBubble_Edit"),
361                               profile_);
362     bubble_->set_fade_away_on_close(true);
363     ShowEditor();
364   } else {
365     DCHECK(sender == close_button_);
366     bubble_->set_fade_away_on_close(true);
367     Close();
368   }
369   // WARNING: we've most likely been deleted when CloseWindow returns.
370 }
371 
ShowEditor()372 void BookmarkBubbleView::ShowEditor() {
373 #if defined(TOUCH_UI)
374   // Close the Bubble
375   Close();
376 
377   // Open the Bookmark Manager
378   Browser* browser = BrowserList::GetLastActiveWithProfile(profile_);
379   DCHECK(browser);
380   if (browser)
381     browser->OpenBookmarkManager();
382   else
383     NOTREACHED();
384 
385 #else
386   const BookmarkNode* node =
387       profile_->GetBookmarkModel()->GetMostRecentlyAddedNodeForURL(url_);
388 
389 #if defined(OS_WIN)
390   // Parent the editor to our root ancestor (not the root we're in, as that
391   // is the info bubble and will close shortly).
392   HWND parent = GetAncestor(GetWidget()->GetNativeView(), GA_ROOTOWNER);
393 
394   // We're about to show the bookmark editor. When the bookmark editor closes
395   // we want the browser to become active. WidgetWin::Hide() does a hide in
396   // a such way that activation isn't changed, which means when we close
397   // Windows gets confused as to who it should give active status to. We
398   // explicitly hide the bookmark bubble window in such a way that activation
399   // status changes. That way, when the editor closes, activation is properly
400   // restored to the browser.
401   ShowWindow(GetWidget()->GetNativeView(), SW_HIDE);
402 #else
403   gfx::NativeWindow parent = GTK_WINDOW(
404       static_cast<views::WidgetGtk*>(GetWidget())->GetTransientParent());
405 #endif
406 
407   // Even though we just hid the window, we need to invoke Close to schedule
408   // the delete and all that.
409   Close();
410 
411   if (node) {
412     BookmarkEditor::Show(parent, profile_, NULL,
413                          BookmarkEditor::EditDetails(node),
414                          BookmarkEditor::SHOW_TREE);
415   }
416 #endif
417 }
418 
ApplyEdits()419 void BookmarkBubbleView::ApplyEdits() {
420   // Set this to make sure we don't attempt to apply edits again.
421   apply_edits_ = false;
422 
423   BookmarkModel* model = profile_->GetBookmarkModel();
424   const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
425   if (node) {
426     const string16 new_title = title_tf_->text();
427     if (new_title != node->GetTitle()) {
428       model->SetTitle(node, new_title);
429       UserMetrics::RecordAction(
430           UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"),
431           profile_);
432     }
433     // Last index means 'Choose another folder...'
434     if (parent_combobox_->selected_item() <
435         parent_model_.GetItemCount() - 1) {
436       const BookmarkNode* new_parent =
437           parent_model_.GetNodeAt(parent_combobox_->selected_item());
438       if (new_parent != node->parent()) {
439         UserMetrics::RecordAction(
440             UserMetricsAction("BookmarkBubble_ChangeParent"), profile_);
441         model->Move(node, new_parent, new_parent->child_count());
442       }
443     }
444   }
445 }
446