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