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