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_bar_view.h"
6
7#include <algorithm>
8#include <limits>
9#include <set>
10#include <vector>
11
12#include "base/i18n/rtl.h"
13#include "base/metrics/histogram.h"
14#include "base/string_util.h"
15#include "base/utf_string_conversions.h"
16#include "chrome/browser/bookmarks/bookmark_model.h"
17#include "chrome/browser/bookmarks/bookmark_utils.h"
18#include "chrome/browser/browser_shutdown.h"
19#include "chrome/browser/extensions/extension_service.h"
20#include "chrome/browser/metrics/user_metrics.h"
21#include "chrome/browser/prefs/pref_service.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/browser/sync/sync_ui_util.h"
24#include "chrome/browser/themes/theme_service.h"
25#include "chrome/browser/ui/browser.h"
26#include "chrome/browser/ui/view_ids.h"
27#include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h"
28#include "chrome/browser/ui/views/event_utils.h"
29#include "chrome/browser/ui/views/frame/browser_view.h"
30#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
31#include "chrome/common/chrome_switches.h"
32#include "chrome/common/extensions/extension_constants.h"
33#include "chrome/common/pref_names.h"
34#include "content/browser/renderer_host/render_view_host.h"
35#include "content/browser/renderer_host/render_widget_host_view.h"
36#include "content/browser/tab_contents/page_navigator.h"
37#include "content/browser/tab_contents/tab_contents.h"
38#include "content/common/notification_service.h"
39#include "content/common/page_transition_types.h"
40#include "grit/app_resources.h"
41#include "grit/generated_resources.h"
42#include "grit/theme_resources.h"
43#include "ui/base/accessibility/accessible_view_state.h"
44#include "ui/base/animation/slide_animation.h"
45#include "ui/base/dragdrop/os_exchange_data.h"
46#include "ui/base/l10n/l10n_util.h"
47#include "ui/base/resource/resource_bundle.h"
48#include "ui/base/text/text_elider.h"
49#include "ui/gfx/canvas_skia.h"
50#include "views/controls/button/menu_button.h"
51#include "views/controls/label.h"
52#include "views/controls/menu/menu_item_view.h"
53#include "views/drag_utils.h"
54#include "views/metrics.h"
55#include "views/view_constants.h"
56#include "views/widget/tooltip_manager.h"
57#include "views/widget/widget.h"
58#include "views/window/window.h"
59
60using views::CustomButton;
61using views::DropTargetEvent;
62using views::MenuButton;
63using views::MenuItemView;
64using views::View;
65
66// How much we want the bookmark bar to overlap the toolbar when in its
67// 'always shown' mode.
68static const int kToolbarOverlap = 3;
69
70// Margins around the content.
71static const int kDetachedTopMargin = 1;  // When attached, we use 0 and let the
72                                          // toolbar above serve as the margin.
73static const int kBottomMargin = 2;
74static const int kLeftMargin = 1;
75static const int kRightMargin = 1;
76
77// Preferred height of the bookmarks bar.
78static const int kBarHeight = 28;
79
80// Preferred height of the bookmarks bar when only shown on the new tab page.
81const int BookmarkBarView::kNewtabBarHeight = 57;
82
83// Padding between buttons.
84static const int kButtonPadding = 0;
85
86// Command ids used in the menu allowing the user to choose when we're visible.
87static const int kAlwaysShowCommandID = 1;
88
89// Icon to display when one isn't found for the page.
90static SkBitmap* kDefaultFavicon = NULL;
91
92// Icon used for folders.
93static SkBitmap* kFolderIcon = NULL;
94
95// Offset for where the menu is shown relative to the bottom of the
96// BookmarkBarView.
97static const int kMenuOffset = 3;
98
99// Color of the drop indicator.
100static const SkColor kDropIndicatorColor = SK_ColorBLACK;
101
102// Width of the drop indicator.
103static const int kDropIndicatorWidth = 2;
104
105// Distance between the bottom of the bar and the separator.
106static const int kSeparatorMargin = 1;
107
108// Width of the separator between the recently bookmarked button and the
109// overflow indicator.
110static const int kSeparatorWidth = 4;
111
112// Starting x-coordinate of the separator line within a separator.
113static const int kSeparatorStartX = 2;
114
115// Left-padding for the instructional text.
116static const int kInstructionsPadding = 6;
117
118// Tag for the 'Other bookmarks' button.
119static const int kOtherFolderButtonTag = 1;
120
121// Tag for the sync error button.
122static const int kSyncErrorButtonTag = 2;
123
124namespace {
125
126// BookmarkButton -------------------------------------------------------------
127
128// Buttons used for the bookmarks on the bookmark bar.
129
130class BookmarkButton : public views::TextButton {
131 public:
132  BookmarkButton(views::ButtonListener* listener,
133                 const GURL& url,
134                 const std::wstring& title,
135                 Profile* profile)
136      : TextButton(listener, title),
137        url_(url),
138        profile_(profile) {
139    show_animation_.reset(new ui::SlideAnimation(this));
140    if (BookmarkBarView::testing_) {
141      // For some reason during testing the events generated by animating
142      // throw off the test. So, don't animate while testing.
143      show_animation_->Reset(1);
144    } else {
145      show_animation_->Show();
146    }
147  }
148
149  bool GetTooltipText(const gfx::Point& p, std::wstring* tooltip) {
150    gfx::Point location(p);
151    ConvertPointToScreen(this, &location);
152    *tooltip = BookmarkBarView::CreateToolTipForURLAndTitle(location, url_,
153        text(), profile_);
154    return !tooltip->empty();
155  }
156
157  virtual bool IsTriggerableEvent(const views::MouseEvent& e) {
158    return event_utils::IsPossibleDispositionEvent(e);
159  }
160
161 private:
162  const GURL& url_;
163  Profile* profile_;
164  scoped_ptr<ui::SlideAnimation> show_animation_;
165
166  DISALLOW_COPY_AND_ASSIGN(BookmarkButton);
167};
168
169// BookmarkFolderButton -------------------------------------------------------
170
171// Buttons used for folders on the bookmark bar, including the 'other folders'
172// button.
173class BookmarkFolderButton : public views::MenuButton {
174 public:
175  BookmarkFolderButton(views::ButtonListener* listener,
176                       const std::wstring& title,
177                       views::ViewMenuDelegate* menu_delegate,
178                       bool show_menu_marker)
179      : MenuButton(listener, title, menu_delegate, show_menu_marker) {
180    show_animation_.reset(new ui::SlideAnimation(this));
181    if (BookmarkBarView::testing_) {
182      // For some reason during testing the events generated by animating
183      // throw off the test. So, don't animate while testing.
184      show_animation_->Reset(1);
185    } else {
186      show_animation_->Show();
187    }
188  }
189
190  virtual bool IsTriggerableEvent(const views::MouseEvent& e) {
191    // Left clicks should show the menu contents and right clicks should show
192    // the context menu. They should not trigger the opening of underlying urls.
193    if (e.flags() == ui::EF_LEFT_BUTTON_DOWN ||
194        e.flags() == ui::EF_RIGHT_BUTTON_DOWN)
195      return false;
196
197    WindowOpenDisposition disposition(
198        event_utils::DispositionFromEventFlags(e.flags()));
199    return disposition != CURRENT_TAB;
200  }
201
202  virtual void OnPaint(gfx::Canvas* canvas) {
203    views::MenuButton::PaintButton(canvas, views::MenuButton::PB_NORMAL);
204  }
205
206 private:
207  scoped_ptr<ui::SlideAnimation> show_animation_;
208
209  DISALLOW_COPY_AND_ASSIGN(BookmarkFolderButton);
210};
211
212// OverFlowButton (chevron) --------------------------------------------------
213
214class OverFlowButton : public views::MenuButton {
215 public:
216  explicit OverFlowButton(BookmarkBarView* owner)
217      : MenuButton(NULL, std::wstring(), owner, false),
218        owner_(owner) {}
219
220  virtual bool OnMousePressed(const views::MouseEvent& e) {
221    owner_->StopThrobbing(true);
222    return views::MenuButton::OnMousePressed(e);
223  }
224
225 private:
226  BookmarkBarView* owner_;
227
228  DISALLOW_COPY_AND_ASSIGN(OverFlowButton);
229};
230
231void RecordAppLaunch(Profile* profile, GURL url) {
232  DCHECK(profile->GetExtensionService());
233  if (!profile->GetExtensionService()->IsInstalledApp(url))
234    return;
235
236  UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
237                            extension_misc::APP_LAUNCH_BOOKMARK_BAR,
238                            extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
239}
240
241}  // namespace
242
243// DropInfo -------------------------------------------------------------------
244
245// Tracks drops on the BookmarkBarView.
246
247struct BookmarkBarView::DropInfo {
248  DropInfo()
249      : valid(false),
250        drop_index(-1),
251        is_menu_showing(false),
252        drop_on(false),
253        is_over_overflow(false),
254        is_over_other(false),
255        x(0),
256        y(0),
257        drag_operation(0) {
258  }
259
260  // Whether the data is valid.
261  bool valid;
262
263  // Index into the model the drop is over. This is relative to the root node.
264  int drop_index;
265
266  // If true, the menu is being shown.
267  bool is_menu_showing;
268
269  // If true, the user is dropping on a node. This is only used for folder
270  // nodes.
271  bool drop_on;
272
273  // If true, the user is over the overflow button.
274  bool is_over_overflow;
275
276  // If true, the user is over the other button.
277  bool is_over_other;
278
279  // Coordinates of the drag (in terms of the BookmarkBarView).
280  int x;
281  int y;
282
283  // The current drag operation.
284  int drag_operation;
285
286  // DropData for the drop.
287  BookmarkNodeData data;
288};
289
290// ButtonSeparatorView  --------------------------------------------------------
291
292class BookmarkBarView::ButtonSeparatorView : public views::View {
293 public:
294  ButtonSeparatorView() {}
295  virtual ~ButtonSeparatorView() {}
296
297  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
298    DetachableToolbarView::PaintVerticalDivider(
299        canvas, kSeparatorStartX, height(), 1,
300        DetachableToolbarView::kEdgeDividerColor,
301        DetachableToolbarView::kMiddleDividerColor,
302        GetThemeProvider()->GetColor(ThemeService::COLOR_TOOLBAR));
303  }
304
305  virtual gfx::Size GetPreferredSize() OVERRIDE {
306    // We get the full height of the bookmark bar, so that the height returned
307    // here doesn't matter.
308    return gfx::Size(kSeparatorWidth, 1);
309  }
310
311  virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
312    state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_SEPARATOR);
313    state->role = ui::AccessibilityTypes::ROLE_SEPARATOR;
314  }
315
316 private:
317  DISALLOW_COPY_AND_ASSIGN(ButtonSeparatorView);
318};
319
320// BookmarkBarView ------------------------------------------------------------
321
322// static
323const int BookmarkBarView::kMaxButtonWidth = 150;
324const int BookmarkBarView::kNewtabHorizontalPadding = 8;
325const int BookmarkBarView::kNewtabVerticalPadding = 12;
326
327// static
328bool BookmarkBarView::testing_ = false;
329
330// Returns the bitmap to use for starred folders.
331static const SkBitmap& GetFolderIcon() {
332  if (!kFolderIcon) {
333    kFolderIcon = ResourceBundle::GetSharedInstance().
334        GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER);
335  }
336  return *kFolderIcon;
337}
338
339BookmarkBarView::BookmarkBarView(Profile* profile, Browser* browser)
340    : profile_(NULL),
341      page_navigator_(NULL),
342      model_(NULL),
343      bookmark_menu_(NULL),
344      bookmark_drop_menu_(NULL),
345      other_bookmarked_button_(NULL),
346      model_changed_listener_(NULL),
347      show_folder_drop_menu_task_(NULL),
348      sync_error_button_(NULL),
349      sync_service_(NULL),
350      overflow_button_(NULL),
351      instructions_(NULL),
352      bookmarks_separator_view_(NULL),
353      browser_(browser),
354      infobar_visible_(false),
355      throbbing_view_(NULL) {
356  if (profile->GetProfileSyncService()) {
357    // Obtain a pointer to the profile sync service and add our instance as an
358    // observer.
359    sync_service_ = profile->GetProfileSyncService();
360    sync_service_->AddObserver(this);
361  }
362
363  SetID(VIEW_ID_BOOKMARK_BAR);
364  Init();
365  SetProfile(profile);
366
367  size_animation_->Reset(IsAlwaysShown() ? 1 : 0);
368}
369
370BookmarkBarView::~BookmarkBarView() {
371  NotifyModelChanged();
372  if (model_)
373    model_->RemoveObserver(this);
374
375  // It's possible for the menu to outlive us, reset the observer to make sure
376  // it doesn't have a reference to us.
377  if (bookmark_menu_)
378    bookmark_menu_->set_observer(NULL);
379
380  StopShowFolderDropMenuTimer();
381
382  if (sync_service_)
383    sync_service_->RemoveObserver(this);
384}
385
386void BookmarkBarView::SetProfile(Profile* profile) {
387  DCHECK(profile);
388  if (profile_ == profile)
389    return;
390
391  StopThrobbing(true);
392
393  // Cancels the current cancelable.
394  NotifyModelChanged();
395  registrar_.RemoveAll();
396
397  profile_ = profile;
398
399  if (model_)
400    model_->RemoveObserver(this);
401
402  // Disable the other bookmarked button, we'll re-enable when the model is
403  // loaded.
404  other_bookmarked_button_->SetEnabled(false);
405
406  Source<Profile> ns_source(profile_->GetOriginalProfile());
407  registrar_.Add(this, NotificationType::BOOKMARK_BUBBLE_SHOWN, ns_source);
408  registrar_.Add(this, NotificationType::BOOKMARK_BUBBLE_HIDDEN, ns_source);
409  registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
410                 NotificationService::AllSources());
411
412  // Remove any existing bookmark buttons.
413  while (GetBookmarkButtonCount())
414    delete GetChildViewAt(0);
415
416  model_ = profile_->GetBookmarkModel();
417  if (model_) {
418    model_->AddObserver(this);
419    if (model_->IsLoaded())
420      Loaded(model_);
421    // else case: we'll receive notification back from the BookmarkModel when
422    // done loading, then we'll populate the bar.
423  }
424}
425
426void BookmarkBarView::SetPageNavigator(PageNavigator* navigator) {
427  page_navigator_ = navigator;
428}
429
430gfx::Size BookmarkBarView::GetPreferredSize() {
431  return LayoutItems(true);
432}
433
434gfx::Size BookmarkBarView::GetMinimumSize() {
435  // The minimum width of the bookmark bar should at least contain the overflow
436  // button, by which one can access all the Bookmark Bar items, and the "Other
437  // Bookmarks" folder, along with appropriate margins and button padding.
438  int width = kLeftMargin;
439
440  if (OnNewTabPage()) {
441    double current_state = 1 - size_animation_->GetCurrentValue();
442    width += 2 * static_cast<int>(kNewtabHorizontalPadding * current_state);
443  }
444
445  int sync_error_total_width = 0;
446  gfx::Size sync_error_button_pref = sync_error_button_->GetPreferredSize();
447  if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_))
448    sync_error_total_width += kButtonPadding + sync_error_button_pref.width();
449
450  gfx::Size other_bookmarked_pref =
451      other_bookmarked_button_->GetPreferredSize();
452  gfx::Size overflow_pref = overflow_button_->GetPreferredSize();
453  gfx::Size bookmarks_separator_pref =
454      bookmarks_separator_view_->GetPreferredSize();
455
456  width += (other_bookmarked_pref.width() + kButtonPadding +
457      overflow_pref.width() + kButtonPadding +
458      bookmarks_separator_pref.width() + sync_error_total_width);
459
460  return gfx::Size(width, kBarHeight);
461}
462
463void BookmarkBarView::Layout() {
464  LayoutItems(false);
465}
466
467void BookmarkBarView::ViewHierarchyChanged(bool is_add,
468                                           View* parent,
469                                           View* child) {
470  if (is_add && child == this) {
471    // We may get inserted into a hierarchy with a profile - this typically
472    // occurs when the bar's contents get populated fast enough that the
473    // buttons are created before the bar is attached to a frame.
474    UpdateColors();
475
476    if (height() > 0) {
477      // We only layout while parented. When we become parented, if our bounds
478      // haven't changed, OnBoundsChanged() won't get invoked and we won't
479      // layout. Therefore we always force a layout when added.
480      Layout();
481    }
482  }
483}
484
485void BookmarkBarView::PaintChildren(gfx::Canvas* canvas) {
486  View::PaintChildren(canvas);
487
488  if (drop_info_.get() && drop_info_->valid &&
489      drop_info_->drag_operation != 0 && drop_info_->drop_index != -1 &&
490      !drop_info_->is_over_overflow && !drop_info_->drop_on) {
491    int index = drop_info_->drop_index;
492    DCHECK(index <= GetBookmarkButtonCount());
493    int x = 0;
494    int y = 0;
495    int h = height();
496    if (index == GetBookmarkButtonCount()) {
497      if (index == 0) {
498        x = kLeftMargin;
499      } else {
500        x = GetBookmarkButton(index - 1)->x() +
501            GetBookmarkButton(index - 1)->width();
502      }
503    } else {
504      x = GetBookmarkButton(index)->x();
505    }
506    if (GetBookmarkButtonCount() > 0 && GetBookmarkButton(0)->IsVisible()) {
507      y = GetBookmarkButton(0)->y();
508      h = GetBookmarkButton(0)->height();
509    }
510
511    // Since the drop indicator is painted directly onto the canvas, we must
512    // make sure it is painted in the right location if the locale is RTL.
513    gfx::Rect indicator_bounds(x - kDropIndicatorWidth / 2,
514                               y,
515                               kDropIndicatorWidth,
516                               h);
517    indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
518
519    // TODO(sky/glen): make me pretty!
520    canvas->FillRectInt(kDropIndicatorColor, indicator_bounds.x(),
521                        indicator_bounds.y(), indicator_bounds.width(),
522                        indicator_bounds.height());
523  }
524}
525
526bool BookmarkBarView::GetDropFormats(
527      int* formats,
528      std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
529  if (!model_ || !model_->IsLoaded())
530    return false;
531  *formats = ui::OSExchangeData::URL;
532  custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
533  return true;
534}
535
536bool BookmarkBarView::AreDropTypesRequired() {
537  return true;
538}
539
540bool BookmarkBarView::CanDrop(const ui::OSExchangeData& data) {
541  if (!model_ || !model_->IsLoaded() ||
542      !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
543    return false;
544
545  if (!drop_info_.get())
546    drop_info_.reset(new DropInfo());
547
548  // Only accept drops of 1 node, which is the case for all data dragged from
549  // bookmark bar and menus.
550  return drop_info_->data.Read(data) && drop_info_->data.size() == 1;
551}
552
553void BookmarkBarView::OnDragEntered(const DropTargetEvent& event) {
554}
555
556int BookmarkBarView::OnDragUpdated(const DropTargetEvent& event) {
557  if (!drop_info_.get())
558    return 0;
559
560  if (drop_info_->valid &&
561      (drop_info_->x == event.x() && drop_info_->y == event.y())) {
562    // The location of the mouse didn't change, return the last operation.
563    return drop_info_->drag_operation;
564  }
565
566  drop_info_->x = event.x();
567  drop_info_->y = event.y();
568
569  int drop_index;
570  bool drop_on;
571  bool is_over_overflow;
572  bool is_over_other;
573
574  drop_info_->drag_operation = CalculateDropOperation(
575      event, drop_info_->data, &drop_index, &drop_on, &is_over_overflow,
576      &is_over_other);
577
578  if (drop_info_->valid && drop_info_->drop_index == drop_index &&
579      drop_info_->drop_on == drop_on &&
580      drop_info_->is_over_overflow == is_over_overflow &&
581      drop_info_->is_over_other == is_over_other) {
582    // The position we're going to drop didn't change, return the last drag
583    // operation we calculated.
584    return drop_info_->drag_operation;
585  }
586
587  drop_info_->valid = true;
588
589  StopShowFolderDropMenuTimer();
590
591  // TODO(sky): Optimize paint region.
592  SchedulePaint();
593  drop_info_->drop_index = drop_index;
594  drop_info_->drop_on = drop_on;
595  drop_info_->is_over_overflow = is_over_overflow;
596  drop_info_->is_over_other = is_over_other;
597
598  if (drop_info_->is_menu_showing) {
599    if (bookmark_drop_menu_)
600      bookmark_drop_menu_->Cancel();
601    drop_info_->is_menu_showing = false;
602  }
603
604  if (drop_on || is_over_overflow || is_over_other) {
605    const BookmarkNode* node;
606    if (is_over_other)
607      node = model_->other_node();
608    else if (is_over_overflow)
609      node = model_->GetBookmarkBarNode();
610    else
611      node = model_->GetBookmarkBarNode()->GetChild(drop_index);
612    StartShowFolderDropMenuTimer(node);
613  }
614
615  return drop_info_->drag_operation;
616}
617
618void BookmarkBarView::OnDragExited() {
619  StopShowFolderDropMenuTimer();
620
621  // NOTE: we don't hide the menu on exit as it's possible the user moved the
622  // mouse over the menu, which triggers an exit on us.
623
624  drop_info_->valid = false;
625
626  if (drop_info_->drop_index != -1) {
627    // TODO(sky): optimize the paint region.
628    SchedulePaint();
629  }
630  drop_info_.reset();
631}
632
633int BookmarkBarView::OnPerformDrop(const DropTargetEvent& event) {
634  StopShowFolderDropMenuTimer();
635
636  if (bookmark_drop_menu_)
637    bookmark_drop_menu_->Cancel();
638
639  if (!drop_info_.get() || !drop_info_->drag_operation)
640    return ui::DragDropTypes::DRAG_NONE;
641
642  const BookmarkNode* root =
643      drop_info_->is_over_other ? model_->other_node() :
644                                  model_->GetBookmarkBarNode();
645  int index = drop_info_->drop_index;
646  const bool drop_on = drop_info_->drop_on;
647  const BookmarkNodeData data = drop_info_->data;
648  const bool is_over_other = drop_info_->is_over_other;
649  DCHECK(data.is_valid());
650
651  if (drop_info_->drop_index != -1) {
652    // TODO(sky): optimize the SchedulePaint region.
653    SchedulePaint();
654  }
655  drop_info_.reset();
656
657  const BookmarkNode* parent_node;
658  if (is_over_other) {
659    parent_node = root;
660    index = parent_node->child_count();
661  } else if (drop_on) {
662    parent_node = root->GetChild(index);
663    index = parent_node->child_count();
664  } else {
665    parent_node = root;
666  }
667  return bookmark_utils::PerformBookmarkDrop(profile_, data, parent_node,
668                                             index);
669}
670
671void BookmarkBarView::ShowContextMenu(const gfx::Point& p,
672                                      bool is_mouse_gesture) {
673  ShowContextMenuForView(this, p, is_mouse_gesture);
674}
675
676void BookmarkBarView::GetAccessibleState(ui::AccessibleViewState* state) {
677  state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
678  state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS);
679}
680
681void BookmarkBarView::OnStateChanged() {
682  // When the sync state changes, it is sufficient to invoke View::Layout since
683  // during layout we query the profile sync service and determine whether the
684  // new state requires showing the sync error button so that the user can
685  // re-enter her password. If extension shelf appears along with the bookmark
686  // shelf, it too needs to be layed out. Since both have the same parent, it is
687  // enough to let the parent layout both of these children.
688  // TODO(sky): This should not require Layout() and SchedulePaint(). Needs
689  //            some cleanup.
690  PreferredSizeChanged();
691  Layout();
692  SchedulePaint();
693}
694
695void BookmarkBarView::OnFullscreenToggled(bool fullscreen) {
696  if (!fullscreen)
697    size_animation_->Reset(IsAlwaysShown() ? 1 : 0);
698  else if (IsAlwaysShown())
699    size_animation_->Reset(0);
700}
701
702bool BookmarkBarView::IsDetached() const {
703  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewTabPage4))
704    return false;
705
706  return OnNewTabPage() && (size_animation_->GetCurrentValue() != 1);
707}
708
709bool BookmarkBarView::IsOnTop() const {
710  return true;
711}
712
713double BookmarkBarView::GetAnimationValue() const {
714  return size_animation_->GetCurrentValue();
715}
716
717int BookmarkBarView::GetToolbarOverlap() const {
718  return GetToolbarOverlap(false);
719}
720
721bool BookmarkBarView::IsAlwaysShown() const {
722  return (profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) &&
723          profile_->GetPrefs()->GetBoolean(prefs::kEnableBookmarkBar));
724}
725
726bool BookmarkBarView::OnNewTabPage() const {
727  return (browser_ && browser_->GetSelectedTabContents() &&
728          browser_->GetSelectedTabContents()->ShouldShowBookmarkBar());
729}
730
731int BookmarkBarView::GetToolbarOverlap(bool return_max) const {
732  // When not on the New Tab Page, always overlap by the full amount.
733  if (return_max || !OnNewTabPage())
734    return kToolbarOverlap;
735  // When on the New Tab Page with an infobar, overlap by 0 whenever the infobar
736  // is above us (i.e. when we're detached), since drawing over the infobar
737  // looks weird.
738  if (IsDetached() && infobar_visible_)
739    return 0;
740  // When on the New Tab Page with no infobar, animate the overlap between the
741  // attached and detached states.
742  return static_cast<int>(kToolbarOverlap * size_animation_->GetCurrentValue());
743}
744
745bool BookmarkBarView::is_animating() {
746  return size_animation_->is_animating();
747}
748
749void BookmarkBarView::AnimationProgressed(const ui::Animation* animation) {
750  if (browser_)
751    browser_->BookmarkBarSizeChanged(true);
752}
753
754void BookmarkBarView::AnimationEnded(const ui::Animation* animation) {
755  if (browser_)
756    browser_->BookmarkBarSizeChanged(false);
757
758  SchedulePaint();
759}
760
761void BookmarkBarView::BookmarkMenuDeleted(BookmarkMenuController* controller) {
762  if (controller == bookmark_menu_)
763    bookmark_menu_ = NULL;
764  else if (controller == bookmark_drop_menu_)
765    bookmark_drop_menu_ = NULL;
766}
767
768views::TextButton* BookmarkBarView::GetBookmarkButton(int index) {
769  DCHECK(index >= 0 && index < GetBookmarkButtonCount());
770  return static_cast<views::TextButton*>(GetChildViewAt(index));
771}
772
773views::MenuItemView* BookmarkBarView::GetMenu() {
774  return bookmark_menu_ ? bookmark_menu_->menu() : NULL;
775}
776
777views::MenuItemView* BookmarkBarView::GetContextMenu() {
778  return bookmark_menu_ ? bookmark_menu_->context_menu() : NULL;
779}
780
781views::MenuItemView* BookmarkBarView::GetDropMenu() {
782  return bookmark_drop_menu_ ? bookmark_drop_menu_->menu() : NULL;
783}
784
785const BookmarkNode* BookmarkBarView::GetNodeForButtonAt(const gfx::Point& loc,
786                                                        int* start_index) {
787  *start_index = 0;
788
789  if (loc.x() < 0 || loc.x() >= width() || loc.y() < 0 || loc.y() >= height())
790    return NULL;
791
792  gfx::Point adjusted_loc(GetMirroredXInView(loc.x()), loc.y());
793
794  // Check the buttons first.
795  for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
796    views::View* child = GetChildViewAt(i);
797    if (!child->IsVisible())
798      break;
799    if (child->bounds().Contains(adjusted_loc))
800      return model_->GetBookmarkBarNode()->GetChild(i);
801  }
802
803  // Then the overflow button.
804  if (overflow_button_->IsVisible() &&
805      overflow_button_->bounds().Contains(adjusted_loc)) {
806    *start_index = GetFirstHiddenNodeIndex();
807    return model_->GetBookmarkBarNode();
808  }
809
810  // And finally the other folder.
811  if (other_bookmarked_button_->IsVisible() &&
812      other_bookmarked_button_->bounds().Contains(adjusted_loc)) {
813    return model_->other_node();
814  }
815
816  return NULL;
817}
818
819views::MenuButton* BookmarkBarView::GetMenuButtonForNode(
820    const BookmarkNode* node) {
821  if (node == model_->other_node())
822    return other_bookmarked_button_;
823  if (node == model_->GetBookmarkBarNode())
824    return overflow_button_;
825  int index = model_->GetBookmarkBarNode()->GetIndexOf(node);
826  if (index == -1 || !node->is_folder())
827    return NULL;
828  return static_cast<views::MenuButton*>(GetChildViewAt(index));
829}
830
831void BookmarkBarView::GetAnchorPositionAndStartIndexForButton(
832    views::MenuButton* button,
833    MenuItemView::AnchorPosition* anchor,
834    int* start_index) {
835  if (button == other_bookmarked_button_ || button == overflow_button_)
836    *anchor = MenuItemView::TOPRIGHT;
837  else
838    *anchor = MenuItemView::TOPLEFT;
839
840  // Invert orientation if right to left.
841  if (base::i18n::IsRTL()) {
842    if (*anchor == MenuItemView::TOPRIGHT)
843      *anchor = MenuItemView::TOPLEFT;
844    else
845      *anchor = MenuItemView::TOPRIGHT;
846  }
847
848  if (button == overflow_button_)
849    *start_index = GetFirstHiddenNodeIndex();
850  else
851    *start_index = 0;
852}
853
854void BookmarkBarView::ShowImportDialog() {
855  browser_->OpenImportSettingsDialog();
856}
857
858void BookmarkBarView::Init() {
859  // Note that at this point we're not in a hierarchy so GetThemeProvider() will
860  // return NULL.  When we're inserted into a hierarchy, we'll call
861  // UpdateColors(), which will set the appropriate colors for all the objects
862  // added in this function.
863
864  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
865
866  if (!kDefaultFavicon)
867    kDefaultFavicon = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
868
869  // Child views are traversed in the order they are added. Make sure the order
870  // they are added matches the visual order.
871  sync_error_button_ = CreateSyncErrorButton();
872  AddChildView(sync_error_button_);
873
874  overflow_button_ = CreateOverflowButton();
875  AddChildView(overflow_button_);
876
877  other_bookmarked_button_ = CreateOtherBookmarkedButton();
878  AddChildView(other_bookmarked_button_);
879
880  bookmarks_separator_view_ = new ButtonSeparatorView();
881  AddChildView(bookmarks_separator_view_);
882
883  instructions_ = new BookmarkBarInstructionsView(this);
884  AddChildView(instructions_);
885
886  SetContextMenuController(this);
887
888  size_animation_.reset(new ui::SlideAnimation(this));
889}
890
891MenuButton* BookmarkBarView::CreateOtherBookmarkedButton() {
892  MenuButton* button = new BookmarkFolderButton(
893      this,
894      UTF16ToWide(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_BOOKMARKED)),
895      this,
896      false);
897  button->SetID(VIEW_ID_OTHER_BOOKMARKS);
898  button->SetIcon(GetFolderIcon());
899  button->SetContextMenuController(this);
900  button->set_tag(kOtherFolderButtonTag);
901  button->SetAccessibleName(
902      l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_BOOKMARKED));
903  return button;
904}
905
906MenuButton* BookmarkBarView::CreateOverflowButton() {
907  MenuButton* button = new OverFlowButton(this);
908  button->SetIcon(*ResourceBundle::GetSharedInstance().
909                  GetBitmapNamed(IDR_BOOKMARK_BAR_CHEVRONS));
910
911  // The overflow button's image contains an arrow and therefore it is a
912  // direction sensitive image and we need to flip it if the UI layout is
913  // right-to-left.
914  //
915  // By default, menu buttons are not flipped because they generally contain
916  // text and flipping the gfx::Canvas object will break text rendering. Since
917  // the overflow button does not contain text, we can safely flip it.
918  button->EnableCanvasFlippingForRTLUI(true);
919
920  // Make visible as necessary.
921  button->SetVisible(false);
922  // Set accessibility name.
923  button->SetAccessibleName(
924      l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS_CHEVRON));
925  return button;
926}
927
928void BookmarkBarView::Loaded(BookmarkModel* model) {
929  volatile int button_count = GetBookmarkButtonCount();
930  DCHECK(button_count == 0);  // If non-zero it means Load was invoked more than
931                              // once, or we didn't properly clear things.
932                              // Either of which shouldn't happen
933  const BookmarkNode* node = model_->GetBookmarkBarNode();
934  DCHECK(node && model_->other_node());
935  // Create a button for each of the children on the bookmark bar.
936  for (int i = 0, child_count = node->child_count(); i < child_count; ++i)
937    AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i);
938  UpdateColors();
939  UpdateOtherBookmarksVisibility();
940  other_bookmarked_button_->SetEnabled(true);
941
942  Layout();
943  SchedulePaint();
944}
945
946void BookmarkBarView::BookmarkModelBeingDeleted(BookmarkModel* model) {
947  // In normal shutdown The bookmark model should never be deleted before us.
948  // When X exits suddenly though, it can happen, This code exists
949  // to check for regressions in shutdown code and not crash.
950  if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers())
951    NOTREACHED();
952
953  // Do minimal cleanup, presumably we'll be deleted shortly.
954  NotifyModelChanged();
955  model_->RemoveObserver(this);
956  model_ = NULL;
957}
958
959void BookmarkBarView::BookmarkNodeMoved(BookmarkModel* model,
960                                        const BookmarkNode* old_parent,
961                                        int old_index,
962                                        const BookmarkNode* new_parent,
963                                        int new_index) {
964  bool was_throbbing = throbbing_view_ &&
965      throbbing_view_ == DetermineViewToThrobFromRemove(old_parent, old_index);
966  if (was_throbbing)
967    throbbing_view_->StopThrobbing();
968  BookmarkNodeRemovedImpl(model, old_parent, old_index);
969  BookmarkNodeAddedImpl(model, new_parent, new_index);
970  if (was_throbbing)
971    StartThrobbing(new_parent->GetChild(new_index), false);
972}
973
974void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model,
975                                        const BookmarkNode* parent,
976                                        int index) {
977  BookmarkNodeAddedImpl(model, parent, index);
978}
979
980void BookmarkBarView::BookmarkNodeAddedImpl(BookmarkModel* model,
981                                            const BookmarkNode* parent,
982                                            int index) {
983  UpdateOtherBookmarksVisibility();
984  NotifyModelChanged();
985  if (parent != model_->GetBookmarkBarNode()) {
986    // We only care about nodes on the bookmark bar.
987    return;
988  }
989  DCHECK(index >= 0 && index <= GetBookmarkButtonCount());
990  const BookmarkNode* node = parent->GetChild(index);
991  if (!throbbing_view_ && sync_service_ && sync_service_->SetupInProgress()) {
992    StartThrobbing(node, true);
993  }
994  AddChildViewAt(CreateBookmarkButton(node), index);
995  UpdateColors();
996  Layout();
997  SchedulePaint();
998}
999
1000void BookmarkBarView::BookmarkNodeRemoved(BookmarkModel* model,
1001                                          const BookmarkNode* parent,
1002                                          int old_index,
1003                                          const BookmarkNode* node) {
1004  BookmarkNodeRemovedImpl(model, parent, old_index);
1005}
1006
1007void BookmarkBarView::BookmarkNodeRemovedImpl(BookmarkModel* model,
1008                                              const BookmarkNode* parent,
1009                                              int index) {
1010  UpdateOtherBookmarksVisibility();
1011
1012  StopThrobbing(true);
1013  // No need to start throbbing again as the bookmark bubble can't be up at
1014  // the same time as the user reorders.
1015
1016  NotifyModelChanged();
1017  if (parent != model_->GetBookmarkBarNode()) {
1018    // We only care about nodes on the bookmark bar.
1019    return;
1020  }
1021  DCHECK(index >= 0 && index < GetBookmarkButtonCount());
1022  views::View* button = GetChildViewAt(index);
1023  RemoveChildView(button);
1024  MessageLoop::current()->DeleteSoon(FROM_HERE, button);
1025  Layout();
1026  SchedulePaint();
1027}
1028
1029void BookmarkBarView::BookmarkNodeChanged(BookmarkModel* model,
1030                                          const BookmarkNode* node) {
1031  NotifyModelChanged();
1032  BookmarkNodeChangedImpl(model, node);
1033}
1034
1035void BookmarkBarView::BookmarkNodeChangedImpl(BookmarkModel* model,
1036                                              const BookmarkNode* node) {
1037  if (node->parent() != model_->GetBookmarkBarNode()) {
1038    // We only care about nodes on the bookmark bar.
1039    return;
1040  }
1041  int index = model_->GetBookmarkBarNode()->GetIndexOf(node);
1042  DCHECK_NE(-1, index);
1043  views::TextButton* button = GetBookmarkButton(index);
1044  gfx::Size old_pref = button->GetPreferredSize();
1045  ConfigureButton(node, button);
1046  gfx::Size new_pref = button->GetPreferredSize();
1047  if (old_pref.width() != new_pref.width()) {
1048    Layout();
1049    SchedulePaint();
1050  } else if (button->IsVisible()) {
1051    button->SchedulePaint();
1052  }
1053}
1054
1055void BookmarkBarView::BookmarkNodeChildrenReordered(BookmarkModel* model,
1056                                                    const BookmarkNode* node) {
1057  NotifyModelChanged();
1058  if (node != model_->GetBookmarkBarNode())
1059    return;  // We only care about reordering of the bookmark bar node.
1060
1061  // Remove the existing buttons.
1062  while (GetBookmarkButtonCount()) {
1063    views::View* button = GetChildViewAt(0);
1064    RemoveChildView(button);
1065    MessageLoop::current()->DeleteSoon(FROM_HERE, button);
1066  }
1067
1068  // Create the new buttons.
1069  for (int i = 0, child_count = node->child_count(); i < child_count; ++i)
1070    AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i);
1071  UpdateColors();
1072
1073  Layout();
1074  SchedulePaint();
1075}
1076
1077void BookmarkBarView::BookmarkNodeFaviconLoaded(BookmarkModel* model,
1078                                                const BookmarkNode* node) {
1079  BookmarkNodeChangedImpl(model, node);
1080}
1081
1082void BookmarkBarView::WriteDragDataForView(View* sender,
1083                                           const gfx::Point& press_pt,
1084                                           ui::OSExchangeData* data) {
1085  UserMetrics::RecordAction(UserMetricsAction("BookmarkBar_DragButton"),
1086                            profile_);
1087
1088  for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1089    if (sender == GetBookmarkButton(i)) {
1090      views::TextButton* button = GetBookmarkButton(i);
1091      gfx::CanvasSkia canvas(button->width(), button->height(), false);
1092      button->PaintButton(&canvas, views::TextButton::PB_FOR_DRAG);
1093      drag_utils::SetDragImageOnDataObject(canvas, button->size(), press_pt,
1094                                           data);
1095      WriteBookmarkDragData(model_->GetBookmarkBarNode()->GetChild(i), data);
1096      return;
1097    }
1098  }
1099  NOTREACHED();
1100}
1101
1102int BookmarkBarView::GetDragOperationsForView(View* sender,
1103                                              const gfx::Point& p) {
1104  if (size_animation_->is_animating() ||
1105      (size_animation_->GetCurrentValue() == 0 && !OnNewTabPage())) {
1106    // Don't let the user drag while animating open or we're closed (and not on
1107    // the new tab page, on the new tab page size_animation_ is always 0). This
1108    // typically is only hit if the user does something to inadvertanty trigger
1109    // dnd, such as pressing the mouse and hitting control-b.
1110    return ui::DragDropTypes::DRAG_NONE;
1111  }
1112
1113  for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1114    if (sender == GetBookmarkButton(i)) {
1115      return bookmark_utils::BookmarkDragOperation(
1116          profile_, model_->GetBookmarkBarNode()->GetChild(i));
1117    }
1118  }
1119  NOTREACHED();
1120  return ui::DragDropTypes::DRAG_NONE;
1121}
1122
1123bool BookmarkBarView::CanStartDragForView(views::View* sender,
1124                                          const gfx::Point& press_pt,
1125                                          const gfx::Point& p) {
1126  // Check if we have not moved enough horizontally but we have moved downward
1127  // vertically - downward drag.
1128  if (!View::ExceededDragThreshold(press_pt.x() - p.x(), 0) &&
1129      press_pt.y() < p.y()) {
1130    for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1131      if (sender == GetBookmarkButton(i)) {
1132        const BookmarkNode* node = model_->GetBookmarkBarNode()->GetChild(i);
1133        // If the folder button was dragged, show the menu instead.
1134        if (node && node->is_folder()) {
1135          views::MenuButton* menu_button =
1136              static_cast<views::MenuButton*>(sender);
1137          menu_button->Activate();
1138          return false;
1139        }
1140        break;
1141      }
1142    }
1143  }
1144  return true;
1145}
1146
1147void BookmarkBarView::WriteBookmarkDragData(const BookmarkNode* node,
1148                                            ui::OSExchangeData* data) {
1149  DCHECK(node && data);
1150  BookmarkNodeData drag_data(node);
1151  drag_data.Write(profile_, data);
1152}
1153
1154void BookmarkBarView::RunMenu(views::View* view, const gfx::Point& pt) {
1155  const BookmarkNode* node;
1156
1157  int start_index = 0;
1158  if (view == other_bookmarked_button_) {
1159    node = model_->other_node();
1160  } else if (view == overflow_button_) {
1161    node = model_->GetBookmarkBarNode();
1162    start_index = GetFirstHiddenNodeIndex();
1163  } else {
1164    int button_index = GetIndexOf(view);
1165    DCHECK_NE(-1, button_index);
1166    node = model_->GetBookmarkBarNode()->GetChild(button_index);
1167  }
1168
1169  bookmark_menu_ = new BookmarkMenuController(browser_, profile_,
1170      page_navigator_, GetWindow()->GetNativeWindow(), node, start_index);
1171  bookmark_menu_->set_observer(this);
1172  bookmark_menu_->RunMenuAt(this, false);
1173}
1174
1175void BookmarkBarView::ButtonPressed(views::Button* sender,
1176                                    const views::Event& event) {
1177  // Show the login wizard if the user clicked the re-login button.
1178  if (sender->tag() == kSyncErrorButtonTag) {
1179    DCHECK(sender == sync_error_button_);
1180    DCHECK(sync_service_ && !sync_service_->IsManaged());
1181    sync_service_->ShowErrorUI(GetWindow()->GetNativeWindow());
1182    return;
1183  }
1184
1185  const BookmarkNode* node;
1186  if (sender->tag() == kOtherFolderButtonTag) {
1187    node = model_->other_node();
1188  } else {
1189    int index = GetIndexOf(sender);
1190    DCHECK_NE(-1, index);
1191    node = model_->GetBookmarkBarNode()->GetChild(index);
1192  }
1193  DCHECK(page_navigator_);
1194
1195  WindowOpenDisposition disposition_from_event_flags =
1196      event_utils::DispositionFromEventFlags(sender->mouse_event_flags());
1197
1198  if (node->is_url()) {
1199    RecordAppLaunch(profile_, node->GetURL());
1200    page_navigator_->OpenURL(node->GetURL(), GURL(),
1201        disposition_from_event_flags, PageTransition::AUTO_BOOKMARK);
1202  } else {
1203    bookmark_utils::OpenAll(GetWindow()->GetNativeWindow(), profile_,
1204        GetPageNavigator(), node, disposition_from_event_flags);
1205  }
1206  UserMetrics::RecordAction(UserMetricsAction("ClickedBookmarkBarURLButton"),
1207                            profile_);
1208}
1209
1210void BookmarkBarView::ShowContextMenuForView(View* source,
1211                                             const gfx::Point& p,
1212                                             bool is_mouse_gesture) {
1213  if (!model_->IsLoaded()) {
1214    // Don't do anything if the model isn't loaded.
1215    return;
1216  }
1217
1218  const BookmarkNode* parent = NULL;
1219  std::vector<const BookmarkNode*> nodes;
1220  if (source == other_bookmarked_button_) {
1221    parent = model_->other_node();
1222    // Do this so the user can open all bookmarks. BookmarkContextMenu makes
1223    // sure the user can edit/delete the node in this case.
1224    nodes.push_back(parent);
1225  } else if (source != this) {
1226    // User clicked on one of the bookmark buttons, find which one they
1227    // clicked on.
1228    int bookmark_button_index = GetIndexOf(source);
1229    DCHECK(bookmark_button_index != -1 &&
1230           bookmark_button_index < GetBookmarkButtonCount());
1231    const BookmarkNode* node =
1232        model_->GetBookmarkBarNode()->GetChild(bookmark_button_index);
1233    nodes.push_back(node);
1234    parent = node->parent();
1235  } else {
1236    parent = model_->GetBookmarkBarNode();
1237    nodes.push_back(parent);
1238  }
1239  // Browser may be null during testing.
1240  PageNavigator* navigator =
1241      browser() ? browser()->GetSelectedTabContents() : NULL;
1242  BookmarkContextMenu controller(GetWindow()->GetNativeWindow(), GetProfile(),
1243                                 navigator, parent, nodes);
1244  controller.RunMenuAt(p);
1245}
1246
1247views::View* BookmarkBarView::CreateBookmarkButton(const BookmarkNode* node) {
1248  if (node->is_url()) {
1249    BookmarkButton* button = new BookmarkButton(this, node->GetURL(),
1250        UTF16ToWide(node->GetTitle()), GetProfile());
1251    ConfigureButton(node, button);
1252    return button;
1253  } else {
1254    views::MenuButton* button = new BookmarkFolderButton(this,
1255        UTF16ToWide(node->GetTitle()), this, false);
1256    button->SetIcon(GetFolderIcon());
1257    ConfigureButton(node, button);
1258    return button;
1259  }
1260}
1261
1262void BookmarkBarView::ConfigureButton(const BookmarkNode* node,
1263                                      views::TextButton* button) {
1264  button->SetText(UTF16ToWide(node->GetTitle()));
1265  button->SetAccessibleName(node->GetTitle());
1266  button->SetID(VIEW_ID_BOOKMARK_BAR_ELEMENT);
1267  // We don't always have a theme provider (ui tests, for example).
1268  if (GetThemeProvider()) {
1269    button->SetEnabledColor(GetThemeProvider()->GetColor(
1270        ThemeService::COLOR_BOOKMARK_TEXT));
1271  }
1272
1273  button->ClearMaxTextSize();
1274  button->SetContextMenuController(this);
1275  button->SetDragController(this);
1276  if (node->is_url()) {
1277    if (model_->GetFavicon(node).width() != 0)
1278      button->SetIcon(model_->GetFavicon(node));
1279    else
1280      button->SetIcon(*kDefaultFavicon);
1281  }
1282  button->set_max_width(kMaxButtonWidth);
1283}
1284
1285bool BookmarkBarView::IsItemChecked(int id) const {
1286  DCHECK(id == kAlwaysShowCommandID);
1287  return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
1288}
1289
1290void BookmarkBarView::ExecuteCommand(int id) {
1291  bookmark_utils::ToggleWhenVisible(profile_);
1292}
1293
1294void BookmarkBarView::Observe(NotificationType type,
1295                              const NotificationSource& source,
1296                              const NotificationDetails& details) {
1297  DCHECK(profile_);
1298  switch (type.value) {
1299    case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED:
1300      if (IsAlwaysShown()) {
1301        size_animation_->Show();
1302      } else {
1303        size_animation_->Hide();
1304      }
1305      break;
1306
1307    case NotificationType::BOOKMARK_BUBBLE_SHOWN: {
1308      StopThrobbing(true);
1309      GURL url = *(Details<GURL>(details).ptr());
1310      const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url);
1311      if (!node)
1312        return;  // Generally shouldn't happen.
1313      StartThrobbing(node, false);
1314      break;
1315    }
1316    case NotificationType::BOOKMARK_BUBBLE_HIDDEN:
1317      StopThrobbing(false);
1318      break;
1319
1320    default:
1321      NOTREACHED();
1322      break;
1323  }
1324}
1325
1326void BookmarkBarView::OnThemeChanged() {
1327  UpdateColors();
1328}
1329
1330void BookmarkBarView::NotifyModelChanged() {
1331  if (model_changed_listener_)
1332    model_changed_listener_->ModelChanged();
1333}
1334
1335void BookmarkBarView::ShowDropFolderForNode(const BookmarkNode* node) {
1336  if (bookmark_drop_menu_) {
1337    if (bookmark_drop_menu_->node() == node) {
1338      // Already showing for the specified node.
1339      return;
1340    }
1341    bookmark_drop_menu_->Cancel();
1342  }
1343
1344  views::MenuButton* menu_button = GetMenuButtonForNode(node);
1345  if (!menu_button)
1346    return;
1347
1348  int start_index = 0;
1349  if (node == model_->GetBookmarkBarNode())
1350    start_index = GetFirstHiddenNodeIndex();
1351
1352  drop_info_->is_menu_showing = true;
1353  bookmark_drop_menu_ = new BookmarkMenuController(browser_, profile_,
1354      page_navigator_, GetWindow()->GetNativeWindow(), node, start_index);
1355  bookmark_drop_menu_->set_observer(this);
1356  bookmark_drop_menu_->RunMenuAt(this, true);
1357}
1358
1359void BookmarkBarView::StopShowFolderDropMenuTimer() {
1360  if (show_folder_drop_menu_task_)
1361    show_folder_drop_menu_task_->Cancel();
1362}
1363
1364void BookmarkBarView::StartShowFolderDropMenuTimer(const BookmarkNode* node) {
1365  if (testing_) {
1366    // So that tests can run as fast as possible disable the delay during
1367    // testing.
1368    ShowDropFolderForNode(node);
1369    return;
1370  }
1371  DCHECK(!show_folder_drop_menu_task_);
1372  show_folder_drop_menu_task_ = new ShowFolderDropMenuTask(this, node);
1373  int delay = views::GetMenuShowDelay();
1374  MessageLoop::current()->PostDelayedTask(FROM_HERE,
1375                                          show_folder_drop_menu_task_, delay);
1376}
1377
1378int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event,
1379                                            const BookmarkNodeData& data,
1380                                            int* index,
1381                                            bool* drop_on,
1382                                            bool* is_over_overflow,
1383                                            bool* is_over_other) {
1384  DCHECK(model_);
1385  DCHECK(model_->IsLoaded());
1386  DCHECK(data.is_valid());
1387
1388  // The drop event uses the screen coordinates while the child Views are
1389  // always laid out from left to right (even though they are rendered from
1390  // right-to-left on RTL locales). Thus, in order to make sure the drop
1391  // coordinates calculation works, we mirror the event's X coordinate if the
1392  // locale is RTL.
1393  int mirrored_x = GetMirroredXInView(event.x());
1394
1395  *index = -1;
1396  *drop_on = false;
1397  *is_over_other = *is_over_overflow = false;
1398
1399  bool found = false;
1400  const int other_delta_x = mirrored_x - other_bookmarked_button_->x();
1401  if (other_bookmarked_button_->IsVisible() && other_delta_x >= 0 &&
1402      other_delta_x < other_bookmarked_button_->width()) {
1403    // Mouse is over 'other' folder.
1404    *is_over_other = true;
1405    *drop_on = true;
1406    found = true;
1407  } else if (!GetBookmarkButtonCount()) {
1408    // No bookmarks, accept the drop.
1409    *index = 0;
1410    int ops = data.GetFirstNode(profile_)
1411        ? ui::DragDropTypes::DRAG_MOVE
1412        : ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
1413    return bookmark_utils::PreferredDropOperation(event.source_operations(),
1414                                                  ops);
1415  }
1416
1417  for (int i = 0; i < GetBookmarkButtonCount() &&
1418       GetBookmarkButton(i)->IsVisible() && !found; i++) {
1419    views::TextButton* button = GetBookmarkButton(i);
1420    int button_x = mirrored_x - button->x();
1421    int button_w = button->width();
1422    if (button_x < button_w) {
1423      found = true;
1424      const BookmarkNode* node = model_->GetBookmarkBarNode()->GetChild(i);
1425      if (node->is_folder()) {
1426        if (button_x <= views::kDropBetweenPixels) {
1427          *index = i;
1428        } else if (button_x < button_w - views::kDropBetweenPixels) {
1429          *index = i;
1430          *drop_on = true;
1431        } else {
1432          *index = i + 1;
1433        }
1434      } else if (button_x < button_w / 2) {
1435        *index = i;
1436      } else {
1437        *index = i + 1;
1438      }
1439      break;
1440    }
1441  }
1442
1443  if (!found) {
1444    if (overflow_button_->IsVisible()) {
1445      // Are we over the overflow button?
1446      int overflow_delta_x = mirrored_x - overflow_button_->x();
1447      if (overflow_delta_x >= 0 &&
1448          overflow_delta_x < overflow_button_->width()) {
1449        // Mouse is over overflow button.
1450        *index = GetFirstHiddenNodeIndex();
1451        *is_over_overflow = true;
1452      } else if (overflow_delta_x < 0) {
1453        // Mouse is after the last visible button but before overflow button;
1454        // use the last visible index.
1455        *index = GetFirstHiddenNodeIndex();
1456      } else {
1457        return ui::DragDropTypes::DRAG_NONE;
1458      }
1459    } else if (!other_bookmarked_button_->IsVisible() ||
1460               mirrored_x < other_bookmarked_button_->x()) {
1461      // Mouse is after the last visible button but before more recently
1462      // bookmarked; use the last visible index.
1463      *index = GetFirstHiddenNodeIndex();
1464    } else {
1465      return ui::DragDropTypes::DRAG_NONE;
1466    }
1467  }
1468
1469  if (*drop_on) {
1470    const BookmarkNode* parent =
1471        *is_over_other ? model_->other_node() :
1472                         model_->GetBookmarkBarNode()->GetChild(*index);
1473    int operation =
1474        bookmark_utils::BookmarkDropOperation(profile_, event, data, parent,
1475                                              parent->child_count());
1476    if (!operation && !data.has_single_url() &&
1477        data.GetFirstNode(profile_) == parent) {
1478      // Don't open a menu if the node being dragged is the the menu to
1479      // open.
1480      *drop_on = false;
1481    }
1482    return operation;
1483  }
1484  return bookmark_utils::BookmarkDropOperation(profile_, event, data,
1485                                               model_->GetBookmarkBarNode(),
1486                                               *index);
1487}
1488
1489int BookmarkBarView::GetFirstHiddenNodeIndex() {
1490  const int bb_count = GetBookmarkButtonCount();
1491  for (int i = 0; i < bb_count; ++i) {
1492    if (!GetBookmarkButton(i)->IsVisible())
1493      return i;
1494  }
1495  return bb_count;
1496}
1497
1498void BookmarkBarView::StartThrobbing(const BookmarkNode* node,
1499                                     bool overflow_only) {
1500  DCHECK(!throbbing_view_);
1501
1502  // Determine which visible button is showing the bookmark (or is an ancestor
1503  // of the bookmark).
1504  const BookmarkNode* bbn = model_->GetBookmarkBarNode();
1505  const BookmarkNode* parent_on_bb = node;
1506  while (parent_on_bb) {
1507    const BookmarkNode* parent = parent_on_bb->parent();
1508    if (parent == bbn)
1509      break;
1510    parent_on_bb = parent;
1511  }
1512  if (parent_on_bb) {
1513    int index = bbn->GetIndexOf(parent_on_bb);
1514    if (index >= GetFirstHiddenNodeIndex()) {
1515      // Node is hidden, animate the overflow button.
1516      throbbing_view_ = overflow_button_;
1517    } else if (!overflow_only) {
1518      throbbing_view_ = static_cast<CustomButton*>(GetChildViewAt(index));
1519    }
1520  } else if (!overflow_only) {
1521    throbbing_view_ = other_bookmarked_button_;
1522  }
1523
1524  // Use a large number so that the button continues to throb.
1525  if (throbbing_view_)
1526    throbbing_view_->StartThrobbing(std::numeric_limits<int>::max());
1527}
1528
1529views::CustomButton* BookmarkBarView::DetermineViewToThrobFromRemove(
1530    const BookmarkNode* parent,
1531    int old_index) {
1532  const BookmarkNode* bbn = model_->GetBookmarkBarNode();
1533  const BookmarkNode* old_node = parent;
1534  int old_index_on_bb = old_index;
1535  while (old_node && old_node != bbn) {
1536    const BookmarkNode* parent = old_node->parent();
1537    if (parent == bbn) {
1538      old_index_on_bb = bbn->GetIndexOf(old_node);
1539      break;
1540    }
1541    old_node = parent;
1542  }
1543  if (old_node) {
1544    if (old_index_on_bb >= GetFirstHiddenNodeIndex()) {
1545      // Node is hidden, animate the overflow button.
1546      return overflow_button_;
1547    }
1548    return static_cast<CustomButton*>(GetChildViewAt(old_index_on_bb));
1549  }
1550  // Node wasn't on the bookmark bar, use the other bookmark button.
1551  return other_bookmarked_button_;
1552}
1553
1554int BookmarkBarView::GetBookmarkButtonCount() {
1555  // We contain five non-bookmark button views: other bookmarks, bookmarks
1556  // separator, chevrons (for overflow), the instruction label and the sync
1557  // error button.
1558  return child_count() - 5;
1559}
1560
1561void BookmarkBarView::StopThrobbing(bool immediate) {
1562  if (!throbbing_view_)
1563    return;
1564
1565  // If not immediate, cycle through 2 more complete cycles.
1566  throbbing_view_->StartThrobbing(immediate ? 0 : 4);
1567  throbbing_view_ = NULL;
1568}
1569
1570// static
1571std::wstring BookmarkBarView::CreateToolTipForURLAndTitle(
1572    const gfx::Point& screen_loc,
1573    const GURL& url,
1574    const std::wstring& title,
1575    Profile* profile) {
1576  int max_width = views::TooltipManager::GetMaxWidth(screen_loc.x(),
1577                                                     screen_loc.y());
1578  gfx::Font tt_font = views::TooltipManager::GetDefaultFont();
1579  std::wstring result;
1580
1581  // First the title.
1582  if (!title.empty()) {
1583    std::wstring localized_title = title;
1584    base::i18n::AdjustStringForLocaleDirection(&localized_title);
1585    result.append(UTF16ToWideHack(ui::ElideText(WideToUTF16Hack(
1586        localized_title), tt_font, max_width, false)));
1587  }
1588
1589  // Only show the URL if the url and title differ.
1590  if (title != UTF8ToWide(url.spec())) {
1591    if (!result.empty())
1592      result.append(views::TooltipManager::GetLineSeparator());
1593
1594    // We need to explicitly specify the directionality of the URL's text to
1595    // make sure it is treated as an LTR string when the context is RTL. For
1596    // example, the URL "http://www.yahoo.com/" appears as
1597    // "/http://www.yahoo.com" when rendered, as is, in an RTL context since
1598    // the Unicode BiDi algorithm puts certain characters on the left by
1599    // default.
1600    std::string languages = profile->GetPrefs()->GetString(
1601        prefs::kAcceptLanguages);
1602    string16 elided_url(ui::ElideUrl(url, tt_font, max_width, languages));
1603    elided_url = base::i18n::GetDisplayStringInLTRDirectionality(elided_url);
1604    result.append(UTF16ToWideHack(elided_url));
1605  }
1606  return result;
1607}
1608
1609void BookmarkBarView::UpdateColors() {
1610  // We don't always have a theme provider (ui tests, for example).
1611  const ui::ThemeProvider* theme_provider = GetThemeProvider();
1612  if (!theme_provider)
1613    return;
1614  SkColor text_color =
1615      theme_provider->GetColor(ThemeService::COLOR_BOOKMARK_TEXT);
1616  for (int i = 0; i < GetBookmarkButtonCount(); ++i)
1617    GetBookmarkButton(i)->SetEnabledColor(text_color);
1618  other_bookmarked_button()->SetEnabledColor(text_color);
1619}
1620
1621void BookmarkBarView::UpdateOtherBookmarksVisibility() {
1622  bool has_other_children = model_->other_node()->child_count() > 0;
1623  if (has_other_children == other_bookmarked_button_->IsVisible())
1624    return;
1625  other_bookmarked_button_->SetVisible(has_other_children);
1626  bookmarks_separator_view_->SetVisible(has_other_children);
1627  Layout();
1628  SchedulePaint();
1629}
1630
1631gfx::Size BookmarkBarView::LayoutItems(bool compute_bounds_only) {
1632  gfx::Size prefsize;
1633  if (!parent() && !compute_bounds_only)
1634    return prefsize;
1635
1636  int x = kLeftMargin;
1637  int top_margin = IsDetached() ? kDetachedTopMargin : 0;
1638  int y = top_margin;
1639  int width = View::width() - kRightMargin - kLeftMargin;
1640  int height = -top_margin - kBottomMargin;
1641  int separator_margin = kSeparatorMargin;
1642
1643  if (OnNewTabPage()) {
1644    double current_state = 1 - size_animation_->GetCurrentValue();
1645    x += static_cast<int>(kNewtabHorizontalPadding * current_state);
1646    y += static_cast<int>(kNewtabVerticalPadding * current_state);
1647    width -= static_cast<int>(kNewtabHorizontalPadding * current_state);
1648    height += View::height() -
1649        static_cast<int>(kNewtabVerticalPadding * 2 * current_state);
1650    separator_margin -= static_cast<int>(kSeparatorMargin * current_state);
1651  } else {
1652    // For the attached appearance, pin the content to the bottom of the bar
1653    // when animating in/out, as shrinking its height instead looks weird.  This
1654    // also matches how we layout infobars.
1655    y += View::height() - kBarHeight;
1656    height += kBarHeight;
1657  }
1658
1659  gfx::Size other_bookmarked_pref =
1660      other_bookmarked_button_->IsVisible() ?
1661      other_bookmarked_button_->GetPreferredSize() : gfx::Size();
1662  gfx::Size overflow_pref = overflow_button_->GetPreferredSize();
1663  gfx::Size bookmarks_separator_pref =
1664      bookmarks_separator_view_->GetPreferredSize();
1665
1666  int sync_error_total_width = 0;
1667  gfx::Size sync_error_button_pref = sync_error_button_->GetPreferredSize();
1668  if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_)) {
1669    sync_error_total_width += kButtonPadding + sync_error_button_pref.width();
1670  }
1671  int max_x = width - overflow_pref.width() - kButtonPadding -
1672      bookmarks_separator_pref.width() - sync_error_total_width;
1673  if (other_bookmarked_button_->IsVisible())
1674    max_x -= other_bookmarked_pref.width() + kButtonPadding;
1675
1676  // Next, layout out the buttons. Any buttons that are placed beyond the
1677  // visible region and made invisible.
1678  if (GetBookmarkButtonCount() == 0 && model_ && model_->IsLoaded()) {
1679    gfx::Size pref = instructions_->GetPreferredSize();
1680    if (!compute_bounds_only) {
1681      instructions_->SetBounds(
1682          x + kInstructionsPadding, y,
1683          std::min(static_cast<int>(pref.width()),
1684          max_x - x),
1685          height);
1686      instructions_->SetVisible(true);
1687    }
1688  } else {
1689    if (!compute_bounds_only)
1690      instructions_->SetVisible(false);
1691
1692    for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1693      views::View* child = GetChildViewAt(i);
1694      gfx::Size pref = child->GetPreferredSize();
1695      int next_x = x + pref.width() + kButtonPadding;
1696      if (!compute_bounds_only) {
1697        child->SetVisible(next_x < max_x);
1698        child->SetBounds(x, y, pref.width(), height);
1699      }
1700      x = next_x;
1701    }
1702  }
1703
1704  // Layout the right side of the bar.
1705  const bool all_visible =
1706      (GetBookmarkButtonCount() == 0 ||
1707       GetChildViewAt(GetBookmarkButtonCount() - 1)->IsVisible());
1708
1709  // Layout the right side buttons.
1710  if (!compute_bounds_only)
1711    x = max_x + kButtonPadding;
1712  else
1713    x += kButtonPadding;
1714
1715  // The overflow button.
1716  if (!compute_bounds_only) {
1717    overflow_button_->SetBounds(x, y, overflow_pref.width(), height);
1718    overflow_button_->SetVisible(!all_visible);
1719  }
1720  x += overflow_pref.width();
1721
1722  // Separator.
1723  if (bookmarks_separator_view_->IsVisible()) {
1724    if (!compute_bounds_only) {
1725      bookmarks_separator_view_->SetBounds(x,
1726                                           y - top_margin,
1727                                           bookmarks_separator_pref.width(),
1728                                           height + top_margin + kBottomMargin -
1729                                           separator_margin);
1730    }
1731
1732    x += bookmarks_separator_pref.width();
1733  }
1734
1735  // The other bookmarks button.
1736  if (other_bookmarked_button_->IsVisible()) {
1737    if (!compute_bounds_only) {
1738      other_bookmarked_button_->SetBounds(x, y, other_bookmarked_pref.width(),
1739                                          height);
1740    }
1741    x += other_bookmarked_pref.width() + kButtonPadding;
1742  }
1743
1744  // Set the real bounds of the sync error button only if it needs to appear on
1745  // the bookmarks bar.
1746  if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_)) {
1747    x += kButtonPadding;
1748    if (!compute_bounds_only) {
1749      sync_error_button_->SetBounds(
1750          x, y, sync_error_button_pref.width(), height);
1751      sync_error_button_->SetVisible(true);
1752    }
1753    x += sync_error_button_pref.width();
1754  } else if (!compute_bounds_only) {
1755    sync_error_button_->SetBounds(x, y, 0, height);
1756    sync_error_button_->SetVisible(false);
1757  }
1758
1759  // Set the preferred size computed so far.
1760  if (compute_bounds_only) {
1761    x += kRightMargin;
1762    prefsize.set_width(x);
1763    if (OnNewTabPage()) {
1764      x += static_cast<int>(
1765          kNewtabHorizontalPadding * (1 - size_animation_->GetCurrentValue()));
1766      prefsize.set_height(kBarHeight +
1767          static_cast<int>((kNewtabBarHeight - kBarHeight) *
1768              (1 - size_animation_->GetCurrentValue())));
1769    } else {
1770      prefsize.set_height(
1771          static_cast<int>(kBarHeight * size_animation_->GetCurrentValue()));
1772    }
1773  }
1774  return prefsize;
1775}
1776
1777views::TextButton* BookmarkBarView::CreateSyncErrorButton() {
1778  views::TextButton* sync_error_button =
1779      new views::TextButton(this, UTF16ToWide(
1780          l10n_util::GetStringUTF16(IDS_SYNC_BOOKMARK_BAR_ERROR)));
1781  sync_error_button->set_tag(kSyncErrorButtonTag);
1782
1783  // The tooltip is the only way we have to display text explaining the error
1784  // to the user.
1785  sync_error_button->SetTooltipText(
1786      UTF16ToWide(l10n_util::GetStringUTF16(IDS_SYNC_BOOKMARK_BAR_ERROR_DESC)));
1787  sync_error_button->SetAccessibleName(
1788      l10n_util::GetStringUTF16(IDS_ACCNAME_SYNC_ERROR_BUTTON));
1789  sync_error_button->SetIcon(
1790      *ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING));
1791  return sync_error_button;
1792}
1793