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