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