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