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