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