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