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