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