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.
5 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
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_factory.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/defaults.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/search/search.h"
24 #include "chrome/browser/sync/profile_sync_service.h"
25 #include "chrome/browser/sync/profile_sync_service_factory.h"
26 #include "chrome/browser/themes/theme_properties.h"
27 #include "chrome/browser/ui/bookmarks/bookmark_bar_constants.h"
28 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
29 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
30 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
31 #include "chrome/browser/ui/browser.h"
32 #include "chrome/browser/ui/chrome_pages.h"
33 #include "chrome/browser/ui/elide_url.h"
34 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
35 #include "chrome/browser/ui/omnibox/omnibox_view.h"
36 #include "chrome/browser/ui/tabs/tab_strip_model.h"
37 #include "chrome/browser/ui/view_ids.h"
38 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_instructions_view.h"
39 #include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h"
40 #include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h"
41 #include "chrome/browser/ui/views/bookmarks/bookmark_menu_controller_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/core_app_launcher_handler.h"
46 #include "chrome/common/chrome_switches.h"
47 #include "chrome/common/extensions/extension_constants.h"
48 #include "chrome/common/pref_names.h"
49 #include "chrome/common/url_constants.h"
50 #include "components/bookmarks/browser/bookmark_model.h"
51 #include "content/public/browser/notification_details.h"
52 #include "content/public/browser/notification_source.h"
53 #include "content/public/browser/page_navigator.h"
54 #include "content/public/browser/render_view_host.h"
55 #include "content/public/browser/render_widget_host_view.h"
56 #include "content/public/browser/user_metrics.h"
57 #include "content/public/browser/web_contents.h"
58 #include "content/public/common/page_transition_types.h"
59 #include "extensions/browser/extension_registry.h"
60 #include "extensions/common/extension.h"
61 #include "extensions/common/extension_set.h"
62 #include "grit/generated_resources.h"
63 #include "grit/theme_resources.h"
64 #include "grit/ui_resources.h"
65 #include "ui/accessibility/ax_view_state.h"
66 #include "ui/base/dragdrop/drag_utils.h"
67 #include "ui/base/dragdrop/os_exchange_data.h"
68 #include "ui/base/l10n/l10n_util.h"
69 #include "ui/base/resource/resource_bundle.h"
70 #include "ui/base/theme_provider.h"
71 #include "ui/base/window_open_disposition.h"
72 #include "ui/gfx/animation/slide_animation.h"
73 #include "ui/gfx/canvas.h"
74 #include "ui/gfx/text_elider.h"
75 #include "ui/views/button_drag_utils.h"
76 #include "ui/views/controls/button/menu_button.h"
77 #include "ui/views/controls/label.h"
78 #include "ui/views/drag_utils.h"
79 #include "ui/views/metrics.h"
80 #include "ui/views/view_constants.h"
81 #include "ui/views/widget/tooltip_manager.h"
82 #include "ui/views/widget/widget.h"
83 #include "ui/views/window/non_client_view.h"
85 using base::UserMetricsAction
;
86 using content::OpenURLParams
;
87 using content::PageNavigator
;
88 using content::Referrer
;
89 using ui::DropTargetEvent
;
90 using views::CustomButton
;
91 using views::MenuButton
;
94 // Margins around the content.
95 static const int kDetachedTopMargin
= 1; // When attached, we use 0 and let the
96 // toolbar above serve as the margin.
97 static const int kBottomMargin
= 2;
98 static const int kLeftMargin
= 1;
99 static const int kRightMargin
= 1;
102 const char BookmarkBarView::kViewClassName
[] = "BookmarkBarView";
104 // Padding between buttons.
105 static const int kButtonPadding
= 0;
107 // Icon to display when one isn't found for the page.
108 static gfx::ImageSkia
* kDefaultFavicon
= NULL
;
110 // Icon used for folders.
111 static gfx::ImageSkia
* kFolderIcon
= NULL
;
113 // Color of the drop indicator.
114 static const SkColor kDropIndicatorColor
= SK_ColorBLACK
;
116 // Width of the drop indicator.
117 static const int kDropIndicatorWidth
= 2;
119 // Distance between the bottom of the bar and the separator.
120 static const int kSeparatorMargin
= 1;
122 // Width of the separator between the recently bookmarked button and the
123 // overflow indicator.
124 static const int kSeparatorWidth
= 4;
126 // Starting x-coordinate of the separator line within a separator.
127 static const int kSeparatorStartX
= 2;
129 // Left-padding for the instructional text.
130 static const int kInstructionsPadding
= 6;
132 // Tag for the 'Other bookmarks' button.
133 static const int kOtherFolderButtonTag
= 1;
135 // Tag for the 'Apps Shortcut' button.
136 static const int kAppsShortcutButtonTag
= 2;
140 // To enable/disable BookmarkBar animations during testing. In production
141 // animations are enabled by default.
142 bool animations_enabled
= true;
144 // BookmarkButtonBase -----------------------------------------------
146 // Base class for text buttons used on the bookmark bar.
148 class BookmarkButtonBase
: public views::TextButton
{
150 BookmarkButtonBase(views::ButtonListener
* listener
,
151 const base::string16
& title
)
152 : TextButton(listener
, title
) {
153 show_animation_
.reset(new gfx::SlideAnimation(this));
154 if (!animations_enabled
) {
155 // For some reason during testing the events generated by animating
156 // throw off the test. So, don't animate while testing.
157 show_animation_
->Reset(1);
159 show_animation_
->Show();
163 virtual bool IsTriggerableEvent(const ui::Event
& e
) OVERRIDE
{
164 return e
.type() == ui::ET_GESTURE_TAP
||
165 e
.type() == ui::ET_GESTURE_TAP_DOWN
||
166 event_utils::IsPossibleDispositionEvent(e
);
170 scoped_ptr
<gfx::SlideAnimation
> show_animation_
;
172 DISALLOW_COPY_AND_ASSIGN(BookmarkButtonBase
);
175 // BookmarkButton -------------------------------------------------------------
177 // Buttons used for the bookmarks on the bookmark bar.
179 class BookmarkButton
: public BookmarkButtonBase
{
181 // The internal view class name.
182 static const char kViewClassName
[];
184 BookmarkButton(views::ButtonListener
* listener
,
186 const base::string16
& title
,
188 : BookmarkButtonBase(listener
, title
),
193 virtual bool GetTooltipText(const gfx::Point
& p
,
194 base::string16
* tooltip
) const OVERRIDE
{
195 gfx::Point
location(p
);
196 ConvertPointToScreen(this, &location
);
197 *tooltip
= BookmarkBarView::CreateToolTipForURLAndTitle(
198 GetWidget(), location
, url_
, text(), profile_
);
199 return !tooltip
->empty();
202 virtual const char* GetClassName() const OVERRIDE
{
203 return kViewClassName
;
210 DISALLOW_COPY_AND_ASSIGN(BookmarkButton
);
214 const char BookmarkButton::kViewClassName
[] = "BookmarkButton";
216 // ShortcutButton -------------------------------------------------------------
218 // Buttons used for the shortcuts on the bookmark bar.
220 class ShortcutButton
: public BookmarkButtonBase
{
222 // The internal view class name.
223 static const char kViewClassName
[];
225 ShortcutButton(views::ButtonListener
* listener
,
226 const base::string16
& title
)
227 : BookmarkButtonBase(listener
, title
) {
230 virtual const char* GetClassName() const OVERRIDE
{
231 return kViewClassName
;
236 DISALLOW_COPY_AND_ASSIGN(ShortcutButton
);
240 const char ShortcutButton::kViewClassName
[] = "ShortcutButton";
242 // BookmarkFolderButton -------------------------------------------------------
244 // Buttons used for folders on the bookmark bar, including the 'other folders'
246 class BookmarkFolderButton
: public views::MenuButton
{
248 BookmarkFolderButton(views::ButtonListener
* listener
,
249 const base::string16
& title
,
250 views::MenuButtonListener
* menu_button_listener
,
251 bool show_menu_marker
)
252 : MenuButton(listener
, title
, menu_button_listener
, show_menu_marker
) {
253 show_animation_
.reset(new gfx::SlideAnimation(this));
254 if (!animations_enabled
) {
255 // For some reason during testing the events generated by animating
256 // throw off the test. So, don't animate while testing.
257 show_animation_
->Reset(1);
259 show_animation_
->Show();
263 virtual bool GetTooltipText(const gfx::Point
& p
,
264 base::string16
* tooltip
) const OVERRIDE
{
265 if (text_size_
.width() > GetTextBounds().width())
267 return !tooltip
->empty();
270 virtual bool IsTriggerableEvent(const ui::Event
& e
) OVERRIDE
{
271 // Left clicks and taps should show the menu contents and right clicks
272 // should show the context menu. They should not trigger the opening of
274 if (e
.type() == ui::ET_GESTURE_TAP
||
275 (e
.IsMouseEvent() && (e
.flags() &
276 (ui::EF_LEFT_MOUSE_BUTTON
| ui::EF_RIGHT_MOUSE_BUTTON
))))
279 if (e
.IsMouseEvent())
280 return ui::DispositionFromEventFlags(e
.flags()) != CURRENT_TAB
;
284 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
285 views::MenuButton::PaintButton(canvas
, views::MenuButton::PB_NORMAL
);
289 scoped_ptr
<gfx::SlideAnimation
> show_animation_
;
291 DISALLOW_COPY_AND_ASSIGN(BookmarkFolderButton
);
294 // OverFlowButton (chevron) --------------------------------------------------
296 class OverFlowButton
: public views::MenuButton
{
298 explicit OverFlowButton(BookmarkBarView
* owner
)
299 : MenuButton(NULL
, base::string16(), owner
, false),
302 virtual bool OnMousePressed(const ui::MouseEvent
& e
) OVERRIDE
{
303 owner_
->StopThrobbing(true);
304 return views::MenuButton::OnMousePressed(e
);
308 BookmarkBarView
* owner_
;
310 DISALLOW_COPY_AND_ASSIGN(OverFlowButton
);
313 void RecordAppLaunch(Profile
* profile
, GURL url
) {
314 const extensions::Extension
* extension
=
315 extensions::ExtensionRegistry::Get(profile
)
316 ->enabled_extensions().GetAppByURL(url
);
320 CoreAppLauncherHandler::RecordAppLaunchType(
321 extension_misc::APP_LAUNCH_BOOKMARK_BAR
,
322 extension
->GetType());
327 // DropLocation ---------------------------------------------------------------
329 struct BookmarkBarView::DropLocation
{
332 operation(ui::DragDropTypes::DRAG_NONE
),
334 button_type(DROP_BOOKMARK
) {
337 bool Equals(const DropLocation
& other
) {
338 return ((other
.index
== index
) && (other
.on
== on
) &&
339 (other
.button_type
== button_type
));
342 // Index into the model the drop is over. This is relative to the root node.
348 // If true, the user is dropping on a folder.
352 DropButtonType button_type
;
355 // DropInfo -------------------------------------------------------------------
357 // Tracks drops on the BookmarkBarView.
359 struct BookmarkBarView::DropInfo
{
362 is_menu_showing(false),
367 // Whether the data is valid.
370 // If true, the menu is being shown.
371 bool is_menu_showing
;
373 // Coordinates of the drag (in terms of the BookmarkBarView).
377 // DropData for the drop.
378 BookmarkNodeData data
;
380 DropLocation location
;
383 // ButtonSeparatorView --------------------------------------------------------
385 class BookmarkBarView::ButtonSeparatorView
: public views::View
{
387 ButtonSeparatorView() {}
388 virtual ~ButtonSeparatorView() {}
390 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
391 DetachableToolbarView::PaintVerticalDivider(
392 canvas
, kSeparatorStartX
, height(), 1,
393 DetachableToolbarView::kEdgeDividerColor
,
394 DetachableToolbarView::kMiddleDividerColor
,
395 GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR
));
398 virtual gfx::Size
GetPreferredSize() const OVERRIDE
{
399 // We get the full height of the bookmark bar, so that the height returned
400 // here doesn't matter.
401 return gfx::Size(kSeparatorWidth
, 1);
404 virtual void GetAccessibleState(ui::AXViewState
* state
) OVERRIDE
{
405 state
->name
= l10n_util::GetStringUTF16(IDS_ACCNAME_SEPARATOR
);
406 state
->role
= ui::AX_ROLE_SPLITTER
;
410 DISALLOW_COPY_AND_ASSIGN(ButtonSeparatorView
);
413 // BookmarkBarView ------------------------------------------------------------
416 const int BookmarkBarView::kMaxButtonWidth
= 150;
417 const int BookmarkBarView::kNewtabHorizontalPadding
= 2;
418 const int BookmarkBarView::kToolbarAttachedBookmarkBarOverlap
= 3;
420 static const gfx::ImageSkia
& GetDefaultFavicon() {
421 if (!kDefaultFavicon
) {
422 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
423 kDefaultFavicon
= rb
->GetImageSkiaNamed(IDR_DEFAULT_FAVICON
);
425 return *kDefaultFavicon
;
428 static const gfx::ImageSkia
& GetFolderIcon() {
430 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
431 kFolderIcon
= rb
->GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER
);
436 BookmarkBarView::BookmarkBarView(Browser
* browser
, BrowserView
* browser_view
)
437 : page_navigator_(NULL
),
439 bookmark_menu_(NULL
),
440 bookmark_drop_menu_(NULL
),
441 other_bookmarked_button_(NULL
),
442 apps_page_shortcut_(NULL
),
443 show_folder_method_factory_(this),
444 overflow_button_(NULL
),
446 bookmarks_separator_view_(NULL
),
448 browser_view_(browser_view
),
449 infobar_visible_(false),
450 throbbing_view_(NULL
),
451 bookmark_bar_state_(BookmarkBar::SHOW
),
452 animating_detached_(false) {
453 set_id(VIEW_ID_BOOKMARK_BAR
);
456 size_animation_
->Reset(1);
459 BookmarkBarView::~BookmarkBarView() {
461 model_
->RemoveObserver(this);
463 // It's possible for the menu to outlive us, reset the observer to make sure
464 // it doesn't have a reference to us.
465 if (bookmark_menu_
) {
466 bookmark_menu_
->set_observer(NULL
);
467 bookmark_menu_
->SetPageNavigator(NULL
);
468 bookmark_menu_
->clear_bookmark_bar();
470 if (context_menu_
.get())
471 context_menu_
->SetPageNavigator(NULL
);
473 StopShowFolderDropMenuTimer();
477 void BookmarkBarView::DisableAnimationsForTesting(bool disabled
) {
478 animations_enabled
= !disabled
;
481 void BookmarkBarView::SetPageNavigator(PageNavigator
* navigator
) {
482 page_navigator_
= navigator
;
484 bookmark_menu_
->SetPageNavigator(navigator
);
485 if (context_menu_
.get())
486 context_menu_
->SetPageNavigator(navigator
);
489 void BookmarkBarView::SetBookmarkBarState(
490 BookmarkBar::State state
,
491 BookmarkBar::AnimateChangeType animate_type
) {
492 if (animate_type
== BookmarkBar::ANIMATE_STATE_CHANGE
&&
493 animations_enabled
) {
494 animating_detached_
= (state
== BookmarkBar::DETACHED
||
495 bookmark_bar_state_
== BookmarkBar::DETACHED
);
496 if (state
== BookmarkBar::SHOW
)
497 size_animation_
->Show();
499 size_animation_
->Hide();
501 size_animation_
->Reset(state
== BookmarkBar::SHOW
? 1 : 0);
503 bookmark_bar_state_
= state
;
506 int BookmarkBarView::GetFullyDetachedToolbarOverlap() const {
507 if (!infobar_visible_
&& browser_
->window()->IsFullscreen()) {
508 // There is no client edge to overlap when detached in fullscreen with no
512 return views::NonClientFrameView::kClientEdgeThickness
;
515 bool BookmarkBarView::is_animating() {
516 return size_animation_
->is_animating();
519 const BookmarkNode
* BookmarkBarView::GetNodeForButtonAtModelIndex(
520 const gfx::Point
& loc
,
521 int* model_start_index
) {
522 *model_start_index
= 0;
524 if (loc
.x() < 0 || loc
.x() >= width() || loc
.y() < 0 || loc
.y() >= height())
527 gfx::Point
adjusted_loc(GetMirroredXInView(loc
.x()), loc
.y());
529 // Check the buttons first.
530 for (int i
= 0; i
< GetBookmarkButtonCount(); ++i
) {
531 views::View
* child
= child_at(i
);
532 if (!child
->visible())
534 if (child
->bounds().Contains(adjusted_loc
))
535 return model_
->bookmark_bar_node()->GetChild(i
);
538 // Then the overflow button.
539 if (overflow_button_
->visible() &&
540 overflow_button_
->bounds().Contains(adjusted_loc
)) {
541 *model_start_index
= GetFirstHiddenNodeIndex();
542 return model_
->bookmark_bar_node();
545 // And finally the other folder.
546 if (other_bookmarked_button_
->visible() &&
547 other_bookmarked_button_
->bounds().Contains(adjusted_loc
)) {
548 return model_
->other_node();
554 views::MenuButton
* BookmarkBarView::GetMenuButtonForNode(
555 const BookmarkNode
* node
) {
556 if (node
== model_
->other_node())
557 return other_bookmarked_button_
;
558 if (node
== model_
->bookmark_bar_node())
559 return overflow_button_
;
560 int index
= model_
->bookmark_bar_node()->GetIndexOf(node
);
561 if (index
== -1 || !node
->is_folder())
563 return static_cast<views::MenuButton
*>(child_at(index
));
566 void BookmarkBarView::GetAnchorPositionForButton(
567 views::MenuButton
* button
,
568 views::MenuAnchorPosition
* anchor
) {
569 if (button
== other_bookmarked_button_
|| button
== overflow_button_
)
570 *anchor
= views::MENU_ANCHOR_TOPRIGHT
;
572 *anchor
= views::MENU_ANCHOR_TOPLEFT
;
575 views::MenuItemView
* BookmarkBarView::GetMenu() {
576 return bookmark_menu_
? bookmark_menu_
->menu() : NULL
;
579 views::MenuItemView
* BookmarkBarView::GetContextMenu() {
580 return bookmark_menu_
? bookmark_menu_
->context_menu() : NULL
;
583 views::MenuItemView
* BookmarkBarView::GetDropMenu() {
584 return bookmark_drop_menu_
? bookmark_drop_menu_
->menu() : NULL
;
587 void BookmarkBarView::StopThrobbing(bool immediate
) {
588 if (!throbbing_view_
)
591 // If not immediate, cycle through 2 more complete cycles.
592 throbbing_view_
->StartThrobbing(immediate
? 0 : 4);
593 throbbing_view_
= NULL
;
597 base::string16
BookmarkBarView::CreateToolTipForURLAndTitle(
598 const views::Widget
* widget
,
599 const gfx::Point
& screen_loc
,
601 const base::string16
& title
,
603 int max_width
= views::TooltipManager::GetMaxWidth(
606 widget
->GetNativeView());
607 const gfx::FontList tt_fonts
= widget
->GetTooltipManager()->GetFontList();
608 base::string16 result
;
611 if (!title
.empty()) {
612 base::string16 localized_title
= title
;
613 base::i18n::AdjustStringForLocaleDirection(&localized_title
);
614 result
.append(ElideText(localized_title
, tt_fonts
, max_width
,
618 // Only show the URL if the url and title differ.
619 if (title
!= base::UTF8ToUTF16(url
.spec())) {
621 result
.push_back('\n');
623 // We need to explicitly specify the directionality of the URL's text to
624 // make sure it is treated as an LTR string when the context is RTL. For
625 // example, the URL "http://www.yahoo.com/" appears as
626 // "/http://www.yahoo.com" when rendered, as is, in an RTL context since
627 // the Unicode BiDi algorithm puts certain characters on the left by
629 std::string languages
= profile
->GetPrefs()->GetString(
630 prefs::kAcceptLanguages
);
631 base::string16
elided_url(ElideUrl(url
, tt_fonts
, max_width
, languages
));
632 elided_url
= base::i18n::GetDisplayStringInLTRDirectionality(elided_url
);
633 result
.append(elided_url
);
638 bool BookmarkBarView::IsDetached() const {
639 return (bookmark_bar_state_
== BookmarkBar::DETACHED
) ||
640 (animating_detached_
&& size_animation_
->is_animating());
643 double BookmarkBarView::GetAnimationValue() const {
644 return size_animation_
->GetCurrentValue();
647 int BookmarkBarView::GetToolbarOverlap() const {
648 int attached_overlap
= kToolbarAttachedBookmarkBarOverlap
+
649 views::NonClientFrameView::kClientEdgeThickness
;
651 return attached_overlap
;
653 int detached_overlap
= GetFullyDetachedToolbarOverlap();
655 // Do not animate the overlap when the infobar is above us (i.e. when we're
656 // detached), since drawing over the infobar looks weird.
657 if (infobar_visible_
)
658 return detached_overlap
;
660 // When detached with no infobar, animate the overlap between the attached and
662 return detached_overlap
+ static_cast<int>(
663 (attached_overlap
- detached_overlap
) *
664 size_animation_
->GetCurrentValue());
667 gfx::Size
BookmarkBarView::GetPreferredSize() const {
671 chrome::kBookmarkBarHeight
+
673 (chrome::kNTPBookmarkBarHeight
- chrome::kBookmarkBarHeight
) *
674 (1 - size_animation_
->GetCurrentValue())));
676 prefsize
.set_height(static_cast<int>(chrome::kBookmarkBarHeight
*
677 size_animation_
->GetCurrentValue()));
682 bool BookmarkBarView::HitTestRect(const gfx::Rect
& rect
) const {
683 // If bookmark bar is attached and omnibox popup is open (on top of the bar),
684 // force hit-testing to fail. This prevents hovers/clicks just above the
685 // omnibox popup from activating the top few pixels of items on the bookmark
687 if (!IsDetached() && browser_view_
&&
688 browser_view_
->GetLocationBar()->GetOmniboxView()->model()->
689 popup_model()->IsOpen()) {
692 return DetachableToolbarView::HitTestRect(rect
);
695 gfx::Size
BookmarkBarView::GetMinimumSize() const {
696 // The minimum width of the bookmark bar should at least contain the overflow
697 // button, by which one can access all the Bookmark Bar items, and the "Other
698 // Bookmarks" folder, along with appropriate margins and button padding.
699 int width
= kLeftMargin
;
701 int height
= chrome::kBookmarkBarHeight
;
703 double current_state
= 1 - size_animation_
->GetCurrentValue();
704 width
+= 2 * static_cast<int>(kNewtabHorizontalPadding
* current_state
);
705 height
+= static_cast<int>(
706 (chrome::kNTPBookmarkBarHeight
- chrome::kBookmarkBarHeight
) *
710 gfx::Size other_bookmarked_pref
;
711 if (other_bookmarked_button_
->visible())
712 other_bookmarked_pref
= other_bookmarked_button_
->GetPreferredSize();
713 gfx::Size overflow_pref
;
714 if (overflow_button_
->visible())
715 overflow_pref
= overflow_button_
->GetPreferredSize();
716 gfx::Size bookmarks_separator_pref
;
717 if (bookmarks_separator_view_
->visible())
718 bookmarks_separator_pref
= bookmarks_separator_view_
->GetPreferredSize();
720 gfx::Size apps_page_shortcut_pref
;
721 if (apps_page_shortcut_
->visible())
722 apps_page_shortcut_pref
= apps_page_shortcut_
->GetPreferredSize();
723 width
+= other_bookmarked_pref
.width() + kButtonPadding
+
724 apps_page_shortcut_pref
.width() + kButtonPadding
+
725 overflow_pref
.width() + kButtonPadding
+
726 bookmarks_separator_pref
.width();
728 return gfx::Size(width
, height
);
731 void BookmarkBarView::Layout() {
735 void BookmarkBarView::ViewHierarchyChanged(
736 const ViewHierarchyChangedDetails
& details
) {
737 if (details
.is_add
&& details
.child
== this) {
738 // We may get inserted into a hierarchy with a profile - this typically
739 // occurs when the bar's contents get populated fast enough that the
740 // buttons are created before the bar is attached to a frame.
744 // We only layout while parented. When we become parented, if our bounds
745 // haven't changed, OnBoundsChanged() won't get invoked and we won't
746 // layout. Therefore we always force a layout when added.
752 void BookmarkBarView::PaintChildren(gfx::Canvas
* canvas
,
753 const views::CullSet
& cull_set
) {
754 View::PaintChildren(canvas
, cull_set
);
756 if (drop_info_
.get() && drop_info_
->valid
&&
757 drop_info_
->location
.operation
!= 0 && drop_info_
->location
.index
!= -1 &&
758 drop_info_
->location
.button_type
!= DROP_OVERFLOW
&&
759 !drop_info_
->location
.on
) {
760 int index
= drop_info_
->location
.index
;
761 DCHECK(index
<= GetBookmarkButtonCount());
765 if (index
== GetBookmarkButtonCount()) {
769 x
= GetBookmarkButton(index
- 1)->x() +
770 GetBookmarkButton(index
- 1)->width();
773 x
= GetBookmarkButton(index
)->x();
775 if (GetBookmarkButtonCount() > 0 && GetBookmarkButton(0)->visible()) {
776 y
= GetBookmarkButton(0)->y();
777 h
= GetBookmarkButton(0)->height();
780 // Since the drop indicator is painted directly onto the canvas, we must
781 // make sure it is painted in the right location if the locale is RTL.
782 gfx::Rect
indicator_bounds(x
- kDropIndicatorWidth
/ 2,
786 indicator_bounds
.set_x(GetMirroredXForRect(indicator_bounds
));
788 // TODO(sky/glen): make me pretty!
789 canvas
->FillRect(indicator_bounds
, kDropIndicatorColor
);
793 bool BookmarkBarView::GetDropFormats(
795 std::set
<ui::OSExchangeData::CustomFormat
>* custom_formats
) {
796 if (!model_
|| !model_
->loaded())
798 *formats
= ui::OSExchangeData::URL
;
799 custom_formats
->insert(BookmarkNodeData::GetBookmarkCustomFormat());
803 bool BookmarkBarView::AreDropTypesRequired() {
807 bool BookmarkBarView::CanDrop(const ui::OSExchangeData
& data
) {
808 if (!model_
|| !model_
->loaded() ||
809 !browser_
->profile()->GetPrefs()->GetBoolean(
810 prefs::kEditBookmarksEnabled
))
813 if (!drop_info_
.get())
814 drop_info_
.reset(new DropInfo());
816 // Only accept drops of 1 node, which is the case for all data dragged from
817 // bookmark bar and menus.
818 return drop_info_
->data
.Read(data
) && drop_info_
->data
.size() == 1;
821 void BookmarkBarView::OnDragEntered(const DropTargetEvent
& event
) {
824 int BookmarkBarView::OnDragUpdated(const DropTargetEvent
& event
) {
825 if (!drop_info_
.get())
828 if (drop_info_
->valid
&&
829 (drop_info_
->x
== event
.x() && drop_info_
->y
== event
.y())) {
830 // The location of the mouse didn't change, return the last operation.
831 return drop_info_
->location
.operation
;
834 drop_info_
->x
= event
.x();
835 drop_info_
->y
= event
.y();
837 DropLocation location
;
838 CalculateDropLocation(event
, drop_info_
->data
, &location
);
840 if (drop_info_
->valid
&& drop_info_
->location
.Equals(location
)) {
841 // The position we're going to drop didn't change, return the last drag
842 // operation we calculated. Copy of the operation in case it changed.
843 drop_info_
->location
.operation
= location
.operation
;
844 return drop_info_
->location
.operation
;
847 StopShowFolderDropMenuTimer();
849 // TODO(sky): Optimize paint region.
852 drop_info_
->location
= location
;
853 drop_info_
->valid
= true;
855 if (drop_info_
->is_menu_showing
) {
856 if (bookmark_drop_menu_
)
857 bookmark_drop_menu_
->Cancel();
858 drop_info_
->is_menu_showing
= false;
861 if (location
.on
|| location
.button_type
== DROP_OVERFLOW
||
862 location
.button_type
== DROP_OTHER_FOLDER
) {
863 const BookmarkNode
* node
;
864 if (location
.button_type
== DROP_OTHER_FOLDER
)
865 node
= model_
->other_node();
866 else if (location
.button_type
== DROP_OVERFLOW
)
867 node
= model_
->bookmark_bar_node();
869 node
= model_
->bookmark_bar_node()->GetChild(location
.index
);
870 StartShowFolderDropMenuTimer(node
);
873 return drop_info_
->location
.operation
;
876 void BookmarkBarView::OnDragExited() {
877 StopShowFolderDropMenuTimer();
879 // NOTE: we don't hide the menu on exit as it's possible the user moved the
880 // mouse over the menu, which triggers an exit on us.
882 drop_info_
->valid
= false;
884 if (drop_info_
->location
.index
!= -1) {
885 // TODO(sky): optimize the paint region.
891 int BookmarkBarView::OnPerformDrop(const DropTargetEvent
& event
) {
892 StopShowFolderDropMenuTimer();
894 if (bookmark_drop_menu_
)
895 bookmark_drop_menu_
->Cancel();
897 if (!drop_info_
.get() || !drop_info_
->location
.operation
)
898 return ui::DragDropTypes::DRAG_NONE
;
900 const BookmarkNode
* root
=
901 (drop_info_
->location
.button_type
== DROP_OTHER_FOLDER
) ?
902 model_
->other_node() : model_
->bookmark_bar_node();
903 int index
= drop_info_
->location
.index
;
906 // TODO(sky): optimize the SchedulePaint region.
909 const BookmarkNode
* parent_node
;
910 if (drop_info_
->location
.button_type
== DROP_OTHER_FOLDER
) {
912 index
= parent_node
->child_count();
913 } else if (drop_info_
->location
.on
) {
914 parent_node
= root
->GetChild(index
);
915 index
= parent_node
->child_count();
919 const BookmarkNodeData data
= drop_info_
->data
;
920 DCHECK(data
.is_valid());
922 return chrome::DropBookmarks(browser_
->profile(), data
, parent_node
, index
);
925 void BookmarkBarView::OnThemeChanged() {
929 const char* BookmarkBarView::GetClassName() const {
930 return kViewClassName
;
933 void BookmarkBarView::GetAccessibleState(ui::AXViewState
* state
) {
934 state
->role
= ui::AX_ROLE_TOOLBAR
;
935 state
->name
= l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS
);
938 void BookmarkBarView::AnimationProgressed(const gfx::Animation
* animation
) {
939 // |browser_view_| can be NULL during tests.
941 browser_view_
->ToolbarSizeChanged(true);
944 void BookmarkBarView::AnimationEnded(const gfx::Animation
* animation
) {
945 // |browser_view_| can be NULL during tests.
947 browser_view_
->ToolbarSizeChanged(false);
952 void BookmarkBarView::BookmarkMenuControllerDeleted(
953 BookmarkMenuController
* controller
) {
954 if (controller
== bookmark_menu_
)
955 bookmark_menu_
= NULL
;
956 else if (controller
== bookmark_drop_menu_
)
957 bookmark_drop_menu_
= NULL
;
960 void BookmarkBarView::ShowImportDialog() {
962 g_browser_process
->local_state()->GetInt64(prefs::kInstallDate
);
963 int64 time_from_install
= base::Time::Now().ToTimeT() - install_time
;
964 if (bookmark_bar_state_
== BookmarkBar::SHOW
) {
965 UMA_HISTOGRAM_COUNTS("Import.ShowDialog.FromBookmarkBarView",
967 } else if (bookmark_bar_state_
== BookmarkBar::DETACHED
) {
968 UMA_HISTOGRAM_COUNTS("Import.ShowDialog.FromFloatingBookmarkBarView",
972 chrome::ShowImportDialog(browser_
);
975 void BookmarkBarView::OnBookmarkBubbleShown(const GURL
& url
) {
977 const BookmarkNode
* node
= model_
->GetMostRecentlyAddedNodeForURL(url
);
979 return; // Generally shouldn't happen.
980 StartThrobbing(node
, false);
983 void BookmarkBarView::OnBookmarkBubbleHidden() {
984 StopThrobbing(false);
987 void BookmarkBarView::BookmarkModelLoaded(BookmarkModel
* model
,
988 bool ids_reassigned
) {
989 // There should be no buttons. If non-zero it means Load was invoked more than
990 // once, or we didn't properly clear things. Either of which shouldn't happen.
991 DCHECK_EQ(0, GetBookmarkButtonCount());
992 const BookmarkNode
* node
= model_
->bookmark_bar_node();
994 // Create a button for each of the children on the bookmark bar.
995 for (int i
= 0, child_count
= node
->child_count(); i
< child_count
; ++i
)
996 AddChildViewAt(CreateBookmarkButton(node
->GetChild(i
)), i
);
997 DCHECK(model_
->other_node());
998 other_bookmarked_button_
->SetAccessibleName(model_
->other_node()->GetTitle());
999 other_bookmarked_button_
->SetText(model_
->other_node()->GetTitle());
1001 UpdateOtherBookmarksVisibility();
1002 other_bookmarked_button_
->SetEnabled(true);
1008 void BookmarkBarView::BookmarkModelBeingDeleted(BookmarkModel
* model
) {
1010 // Do minimal cleanup, presumably we'll be deleted shortly.
1011 model_
->RemoveObserver(this);
1015 void BookmarkBarView::BookmarkNodeMoved(BookmarkModel
* model
,
1016 const BookmarkNode
* old_parent
,
1018 const BookmarkNode
* new_parent
,
1020 bool was_throbbing
= throbbing_view_
&&
1021 throbbing_view_
== DetermineViewToThrobFromRemove(old_parent
, old_index
);
1023 throbbing_view_
->StopThrobbing();
1024 BookmarkNodeRemovedImpl(model
, old_parent
, old_index
);
1025 BookmarkNodeAddedImpl(model
, new_parent
, new_index
);
1027 StartThrobbing(new_parent
->GetChild(new_index
), false);
1030 void BookmarkBarView::BookmarkNodeAdded(BookmarkModel
* model
,
1031 const BookmarkNode
* parent
,
1033 BookmarkNodeAddedImpl(model
, parent
, index
);
1036 void BookmarkBarView::BookmarkNodeRemoved(BookmarkModel
* model
,
1037 const BookmarkNode
* parent
,
1039 const BookmarkNode
* node
,
1040 const std::set
<GURL
>& removed_urls
) {
1041 // Close the menu if the menu is showing for the deleted node.
1042 if (bookmark_menu_
&& bookmark_menu_
->node() == node
)
1043 bookmark_menu_
->Cancel();
1044 BookmarkNodeRemovedImpl(model
, parent
, old_index
);
1047 void BookmarkBarView::BookmarkAllNodesRemoved(
1048 BookmarkModel
* model
,
1049 const std::set
<GURL
>& removed_urls
) {
1050 UpdateOtherBookmarksVisibility();
1052 StopThrobbing(true);
1054 // Remove the existing buttons.
1055 while (GetBookmarkButtonCount()) {
1056 delete GetBookmarkButton(0);
1063 void BookmarkBarView::BookmarkNodeChanged(BookmarkModel
* model
,
1064 const BookmarkNode
* node
) {
1065 BookmarkNodeChangedImpl(model
, node
);
1068 void BookmarkBarView::BookmarkNodeChildrenReordered(BookmarkModel
* model
,
1069 const BookmarkNode
* node
) {
1070 if (node
!= model_
->bookmark_bar_node())
1071 return; // We only care about reordering of the bookmark bar node.
1073 // Remove the existing buttons.
1074 while (GetBookmarkButtonCount()) {
1075 views::View
* button
= child_at(0);
1076 RemoveChildView(button
);
1077 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, button
);
1080 // Create the new buttons.
1081 for (int i
= 0, child_count
= node
->child_count(); i
< child_count
; ++i
)
1082 AddChildViewAt(CreateBookmarkButton(node
->GetChild(i
)), i
);
1089 void BookmarkBarView::BookmarkNodeFaviconChanged(BookmarkModel
* model
,
1090 const BookmarkNode
* node
) {
1091 BookmarkNodeChangedImpl(model
, node
);
1094 void BookmarkBarView::WriteDragDataForView(View
* sender
,
1095 const gfx::Point
& press_pt
,
1096 ui::OSExchangeData
* data
) {
1097 content::RecordAction(UserMetricsAction("BookmarkBar_DragButton"));
1099 for (int i
= 0; i
< GetBookmarkButtonCount(); ++i
) {
1100 if (sender
== GetBookmarkButton(i
)) {
1101 views::TextButton
* button
= GetBookmarkButton(i
);
1102 scoped_ptr
<gfx::Canvas
> canvas(
1103 views::GetCanvasForDragImage(button
->GetWidget(), button
->size()));
1104 button
->PaintButton(canvas
.get(), views::TextButton::PB_FOR_DRAG
);
1105 drag_utils::SetDragImageOnDataObject(*canvas
, button
->size(),
1106 press_pt
.OffsetFromOrigin(),
1108 WriteBookmarkDragData(model_
->bookmark_bar_node()->GetChild(i
), data
);
1115 int BookmarkBarView::GetDragOperationsForView(View
* sender
,
1116 const gfx::Point
& p
) {
1117 if (size_animation_
->is_animating() ||
1118 (size_animation_
->GetCurrentValue() == 0 &&
1119 bookmark_bar_state_
!= BookmarkBar::DETACHED
)) {
1120 // Don't let the user drag while animating open or we're closed (and not
1121 // detached, when detached size_animation_ is always 0). This typically is
1122 // only hit if the user does something to inadvertently trigger DnD such as
1123 // pressing the mouse and hitting control-b.
1124 return ui::DragDropTypes::DRAG_NONE
;
1127 for (int i
= 0; i
< GetBookmarkButtonCount(); ++i
) {
1128 if (sender
== GetBookmarkButton(i
)) {
1129 return chrome::GetBookmarkDragOperation(
1130 browser_
->profile(), model_
->bookmark_bar_node()->GetChild(i
));
1134 return ui::DragDropTypes::DRAG_NONE
;
1137 bool BookmarkBarView::CanStartDragForView(views::View
* sender
,
1138 const gfx::Point
& press_pt
,
1139 const gfx::Point
& p
) {
1140 // Check if we have not moved enough horizontally but we have moved downward
1141 // vertically - downward drag.
1142 gfx::Vector2d move_offset
= p
- press_pt
;
1143 gfx::Vector2d
horizontal_offset(move_offset
.x(), 0);
1144 if (!View::ExceededDragThreshold(horizontal_offset
) && move_offset
.y() > 0) {
1145 for (int i
= 0; i
< GetBookmarkButtonCount(); ++i
) {
1146 if (sender
== GetBookmarkButton(i
)) {
1147 const BookmarkNode
* node
= model_
->bookmark_bar_node()->GetChild(i
);
1148 // If the folder button was dragged, show the menu instead.
1149 if (node
&& node
->is_folder()) {
1150 views::MenuButton
* menu_button
=
1151 static_cast<views::MenuButton
*>(sender
);
1152 menu_button
->Activate();
1162 void BookmarkBarView::OnMenuButtonClicked(views::View
* view
,
1163 const gfx::Point
& point
) {
1164 const BookmarkNode
* node
;
1166 int start_index
= 0;
1167 if (view
== other_bookmarked_button_
) {
1168 node
= model_
->other_node();
1169 } else if (view
== overflow_button_
) {
1170 node
= model_
->bookmark_bar_node();
1171 start_index
= GetFirstHiddenNodeIndex();
1173 int button_index
= GetIndexOf(view
);
1174 DCHECK_NE(-1, button_index
);
1175 node
= model_
->bookmark_bar_node()->GetChild(button_index
);
1178 RecordBookmarkFolderOpen(GetBookmarkLaunchLocation());
1179 bookmark_menu_
= new BookmarkMenuController(
1180 browser_
, page_navigator_
, GetWidget(), node
, start_index
);
1181 bookmark_menu_
->set_observer(this);
1182 bookmark_menu_
->RunMenuAt(this, false);
1185 void BookmarkBarView::ButtonPressed(views::Button
* sender
,
1186 const ui::Event
& event
) {
1187 WindowOpenDisposition disposition_from_event_flags
=
1188 ui::DispositionFromEventFlags(event
.flags());
1190 if (sender
->tag() == kAppsShortcutButtonTag
) {
1191 OpenURLParams
params(GURL(chrome::kChromeUIAppsURL
),
1193 disposition_from_event_flags
,
1194 content::PAGE_TRANSITION_AUTO_BOOKMARK
,
1196 page_navigator_
->OpenURL(params
);
1197 RecordBookmarkAppsPageOpen(GetBookmarkLaunchLocation());
1201 const BookmarkNode
* node
;
1202 if (sender
->tag() == kOtherFolderButtonTag
) {
1203 node
= model_
->other_node();
1205 int index
= GetIndexOf(sender
);
1206 DCHECK_NE(-1, index
);
1207 node
= model_
->bookmark_bar_node()->GetChild(index
);
1209 DCHECK(page_navigator_
);
1211 if (node
->is_url()) {
1212 RecordAppLaunch(browser_
->profile(), node
->url());
1213 OpenURLParams
params(
1214 node
->url(), Referrer(), disposition_from_event_flags
,
1215 content::PAGE_TRANSITION_AUTO_BOOKMARK
, false);
1216 page_navigator_
->OpenURL(params
);
1218 chrome::OpenAll(GetWidget()->GetNativeWindow(), page_navigator_
, node
,
1219 disposition_from_event_flags
, browser_
->profile());
1222 RecordBookmarkLaunch(node
, GetBookmarkLaunchLocation());
1225 void BookmarkBarView::ShowContextMenuForView(views::View
* source
,
1226 const gfx::Point
& point
,
1227 ui::MenuSourceType source_type
) {
1228 if (!model_
->loaded()) {
1229 // Don't do anything if the model isn't loaded.
1233 const BookmarkNode
* parent
= NULL
;
1234 std::vector
<const BookmarkNode
*> nodes
;
1235 if (source
== other_bookmarked_button_
) {
1236 parent
= model_
->other_node();
1237 // Do this so the user can open all bookmarks. BookmarkContextMenu makes
1238 // sure the user can't edit/delete the node in this case.
1239 nodes
.push_back(parent
);
1240 } else if (source
!= this && source
!= apps_page_shortcut_
) {
1241 // User clicked on one of the bookmark buttons, find which one they
1242 // clicked on, except for the apps page shortcut, which must behave as if
1243 // the user clicked on the bookmark bar background.
1244 int bookmark_button_index
= GetIndexOf(source
);
1245 DCHECK(bookmark_button_index
!= -1 &&
1246 bookmark_button_index
< GetBookmarkButtonCount());
1247 const BookmarkNode
* node
=
1248 model_
->bookmark_bar_node()->GetChild(bookmark_button_index
);
1249 nodes
.push_back(node
);
1250 parent
= node
->parent();
1252 parent
= model_
->bookmark_bar_node();
1253 nodes
.push_back(parent
);
1255 bool close_on_remove
=
1256 (parent
== model_
->other_node()) && (parent
->child_count() == 1);
1258 context_menu_
.reset(new BookmarkContextMenu(
1259 GetWidget(), browser_
, browser_
->profile(),
1260 browser_
->tab_strip_model()->GetActiveWebContents(),
1261 parent
, nodes
, close_on_remove
));
1262 context_menu_
->RunMenuAt(point
, source_type
);
1265 void BookmarkBarView::Init() {
1266 // Note that at this point we're not in a hierarchy so GetThemeProvider() will
1267 // return NULL. When we're inserted into a hierarchy, we'll call
1268 // UpdateColors(), which will set the appropriate colors for all the objects
1269 // added in this function.
1271 // Child views are traversed in the order they are added. Make sure the order
1272 // they are added matches the visual order.
1273 overflow_button_
= CreateOverflowButton();
1274 AddChildView(overflow_button_
);
1276 other_bookmarked_button_
= CreateOtherBookmarkedButton();
1277 // We'll re-enable when the model is loaded.
1278 other_bookmarked_button_
->SetEnabled(false);
1279 AddChildView(other_bookmarked_button_
);
1281 apps_page_shortcut_
= CreateAppsPageShortcutButton();
1282 AddChildView(apps_page_shortcut_
);
1283 profile_pref_registrar_
.Init(browser_
->profile()->GetPrefs());
1284 profile_pref_registrar_
.Add(
1285 prefs::kShowAppsShortcutInBookmarkBar
,
1286 base::Bind(&BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged
,
1287 base::Unretained(this)));
1288 apps_page_shortcut_
->SetVisible(
1289 chrome::ShouldShowAppsShortcutInBookmarkBar(
1290 browser_
->profile(), browser_
->host_desktop_type()));
1292 bookmarks_separator_view_
= new ButtonSeparatorView();
1293 AddChildView(bookmarks_separator_view_
);
1294 UpdateBookmarksSeparatorVisibility();
1296 instructions_
= new BookmarkBarInstructionsView(this);
1297 AddChildView(instructions_
);
1299 set_context_menu_controller(this);
1301 size_animation_
.reset(new gfx::SlideAnimation(this));
1303 model_
= BookmarkModelFactory::GetForProfile(browser_
->profile());
1305 model_
->AddObserver(this);
1306 if (model_
->loaded())
1307 BookmarkModelLoaded(model_
, false);
1308 // else case: we'll receive notification back from the BookmarkModel when
1309 // done loading, then we'll populate the bar.
1313 int BookmarkBarView::GetBookmarkButtonCount() const {
1314 // We contain four non-bookmark button views: other bookmarks, bookmarks
1315 // separator, chevrons (for overflow), apps page, and the instruction label.
1316 return child_count() - 5;
1319 views::TextButton
* BookmarkBarView::GetBookmarkButton(int index
) {
1320 DCHECK(index
>= 0 && index
< GetBookmarkButtonCount());
1321 return static_cast<views::TextButton
*>(child_at(index
));
1324 BookmarkLaunchLocation
BookmarkBarView::GetBookmarkLaunchLocation() const {
1325 return IsDetached() ? BOOKMARK_LAUNCH_LOCATION_DETACHED_BAR
:
1326 BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR
;
1329 int BookmarkBarView::GetFirstHiddenNodeIndex() {
1330 const int bb_count
= GetBookmarkButtonCount();
1331 for (int i
= 0; i
< bb_count
; ++i
) {
1332 if (!GetBookmarkButton(i
)->visible())
1338 MenuButton
* BookmarkBarView::CreateOtherBookmarkedButton() {
1339 // Title is set in Loaded.
1340 MenuButton
* button
=
1341 new BookmarkFolderButton(this, base::string16(), this, false);
1342 button
->set_id(VIEW_ID_OTHER_BOOKMARKS
);
1343 button
->SetIcon(GetFolderIcon());
1344 button
->set_context_menu_controller(this);
1345 button
->set_tag(kOtherFolderButtonTag
);
1349 MenuButton
* BookmarkBarView::CreateOverflowButton() {
1350 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
1351 MenuButton
* button
= new OverFlowButton(this);
1352 button
->SetIcon(*rb
->GetImageSkiaNamed(IDR_BOOKMARK_BAR_CHEVRONS
));
1354 // The overflow button's image contains an arrow and therefore it is a
1355 // direction sensitive image and we need to flip it if the UI layout is
1358 // By default, menu buttons are not flipped because they generally contain
1359 // text and flipping the gfx::Canvas object will break text rendering. Since
1360 // the overflow button does not contain text, we can safely flip it.
1361 button
->EnableCanvasFlippingForRTLUI(true);
1363 // Make visible as necessary.
1364 button
->SetVisible(false);
1365 // Set accessibility name.
1366 button
->SetAccessibleName(
1367 l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS_CHEVRON
));
1371 views::View
* BookmarkBarView::CreateBookmarkButton(const BookmarkNode
* node
) {
1372 if (node
->is_url()) {
1373 BookmarkButton
* button
= new BookmarkButton(
1374 this, node
->url(), node
->GetTitle(), browser_
->profile());
1375 ConfigureButton(node
, button
);
1378 views::MenuButton
* button
= new BookmarkFolderButton(
1379 this, node
->GetTitle(), this, false);
1380 button
->SetIcon(GetFolderIcon());
1381 ConfigureButton(node
, button
);
1386 views::TextButton
* BookmarkBarView::CreateAppsPageShortcutButton() {
1387 views::TextButton
* button
= new ShortcutButton(
1388 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME
));
1389 button
->SetTooltipText(l10n_util::GetStringUTF16(
1390 IDS_BOOKMARK_BAR_APPS_SHORTCUT_TOOLTIP
));
1391 button
->set_id(VIEW_ID_BOOKMARK_BAR_ELEMENT
);
1392 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
1393 button
->SetIcon(*rb
->GetImageSkiaNamed(IDR_BOOKMARK_BAR_APPS_SHORTCUT
));
1394 button
->set_context_menu_controller(this);
1395 button
->set_tag(kAppsShortcutButtonTag
);
1399 void BookmarkBarView::ConfigureButton(const BookmarkNode
* node
,
1400 views::TextButton
* button
) {
1401 button
->SetText(node
->GetTitle());
1402 button
->SetAccessibleName(node
->GetTitle());
1403 button
->set_id(VIEW_ID_BOOKMARK_BAR_ELEMENT
);
1404 // We don't always have a theme provider (ui tests, for example).
1405 if (GetThemeProvider()) {
1406 button
->SetEnabledColor(GetThemeProvider()->GetColor(
1407 ThemeProperties::COLOR_BOOKMARK_TEXT
));
1410 button
->ClearMaxTextSize();
1411 button
->set_context_menu_controller(this);
1412 button
->set_drag_controller(this);
1413 if (node
->is_url()) {
1414 const gfx::Image
& favicon
= model_
->GetFavicon(node
);
1415 if (!favicon
.IsEmpty())
1416 button
->SetIcon(*favicon
.ToImageSkia());
1418 button
->SetIcon(GetDefaultFavicon());
1420 button
->set_max_width(kMaxButtonWidth
);
1423 void BookmarkBarView::BookmarkNodeAddedImpl(BookmarkModel
* model
,
1424 const BookmarkNode
* parent
,
1426 UpdateOtherBookmarksVisibility();
1427 if (parent
!= model_
->bookmark_bar_node()) {
1428 // We only care about nodes on the bookmark bar.
1431 DCHECK(index
>= 0 && index
<= GetBookmarkButtonCount());
1432 const BookmarkNode
* node
= parent
->GetChild(index
);
1433 ProfileSyncService
* sync_service(ProfileSyncServiceFactory::
1434 GetInstance()->GetForProfile(browser_
->profile()));
1435 if (!throbbing_view_
&& sync_service
&& sync_service
->FirstSetupInProgress())
1436 StartThrobbing(node
, true);
1437 AddChildViewAt(CreateBookmarkButton(node
), index
);
1443 void BookmarkBarView::BookmarkNodeRemovedImpl(BookmarkModel
* model
,
1444 const BookmarkNode
* parent
,
1446 UpdateOtherBookmarksVisibility();
1448 StopThrobbing(true);
1449 // No need to start throbbing again as the bookmark bubble can't be up at
1450 // the same time as the user reorders.
1452 if (parent
!= model_
->bookmark_bar_node()) {
1453 // We only care about nodes on the bookmark bar.
1456 DCHECK(index
>= 0 && index
< GetBookmarkButtonCount());
1457 views::View
* button
= child_at(index
);
1458 RemoveChildView(button
);
1459 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, button
);
1464 void BookmarkBarView::BookmarkNodeChangedImpl(BookmarkModel
* model
,
1465 const BookmarkNode
* node
) {
1466 if (node
->parent() != model_
->bookmark_bar_node()) {
1467 // We only care about nodes on the bookmark bar.
1470 int index
= model_
->bookmark_bar_node()->GetIndexOf(node
);
1471 DCHECK_NE(-1, index
);
1472 views::TextButton
* button
= GetBookmarkButton(index
);
1473 gfx::Size old_pref
= button
->GetPreferredSize();
1474 ConfigureButton(node
, button
);
1475 gfx::Size new_pref
= button
->GetPreferredSize();
1476 if (old_pref
.width() != new_pref
.width()) {
1479 } else if (button
->visible()) {
1480 button
->SchedulePaint();
1484 void BookmarkBarView::ShowDropFolderForNode(const BookmarkNode
* node
) {
1485 if (bookmark_drop_menu_
) {
1486 if (bookmark_drop_menu_
->node() == node
) {
1487 // Already showing for the specified node.
1490 bookmark_drop_menu_
->Cancel();
1493 views::MenuButton
* menu_button
= GetMenuButtonForNode(node
);
1497 int start_index
= 0;
1498 if (node
== model_
->bookmark_bar_node())
1499 start_index
= GetFirstHiddenNodeIndex();
1501 drop_info_
->is_menu_showing
= true;
1502 bookmark_drop_menu_
= new BookmarkMenuController(browser_
,
1503 page_navigator_
, GetWidget(), node
, start_index
);
1504 bookmark_drop_menu_
->set_observer(this);
1505 bookmark_drop_menu_
->RunMenuAt(this, true);
1508 void BookmarkBarView::StopShowFolderDropMenuTimer() {
1509 show_folder_method_factory_
.InvalidateWeakPtrs();
1512 void BookmarkBarView::StartShowFolderDropMenuTimer(const BookmarkNode
* node
) {
1513 if (!animations_enabled
) {
1514 // So that tests can run as fast as possible disable the delay during
1516 ShowDropFolderForNode(node
);
1519 show_folder_method_factory_
.InvalidateWeakPtrs();
1520 base::MessageLoop::current()->PostDelayedTask(
1522 base::Bind(&BookmarkBarView::ShowDropFolderForNode
,
1523 show_folder_method_factory_
.GetWeakPtr(),
1525 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
1528 void BookmarkBarView::CalculateDropLocation(const DropTargetEvent
& event
,
1529 const BookmarkNodeData
& data
,
1530 DropLocation
* location
) {
1532 DCHECK(model_
->loaded());
1533 DCHECK(data
.is_valid());
1535 *location
= DropLocation();
1537 // The drop event uses the screen coordinates while the child Views are
1538 // always laid out from left to right (even though they are rendered from
1539 // right-to-left on RTL locales). Thus, in order to make sure the drop
1540 // coordinates calculation works, we mirror the event's X coordinate if the
1542 int mirrored_x
= GetMirroredXInView(event
.x());
1545 const int other_delta_x
= mirrored_x
- other_bookmarked_button_
->x();
1546 Profile
* profile
= browser_
->profile();
1547 if (other_bookmarked_button_
->visible() && other_delta_x
>= 0 &&
1548 other_delta_x
< other_bookmarked_button_
->width()) {
1549 // Mouse is over 'other' folder.
1550 location
->button_type
= DROP_OTHER_FOLDER
;
1551 location
->on
= true;
1553 } else if (!GetBookmarkButtonCount()) {
1554 // No bookmarks, accept the drop.
1555 location
->index
= 0;
1556 int ops
= data
.GetFirstNode(model_
, profile
->GetPath()) ?
1557 ui::DragDropTypes::DRAG_MOVE
:
1558 ui::DragDropTypes::DRAG_COPY
| ui::DragDropTypes::DRAG_LINK
;
1559 location
->operation
= chrome::GetPreferredBookmarkDropOperation(
1560 event
.source_operations(), ops
);
1564 for (int i
= 0; i
< GetBookmarkButtonCount() &&
1565 GetBookmarkButton(i
)->visible() && !found
; i
++) {
1566 views::TextButton
* button
= GetBookmarkButton(i
);
1567 int button_x
= mirrored_x
- button
->x();
1568 int button_w
= button
->width();
1569 if (button_x
< button_w
) {
1571 const BookmarkNode
* node
= model_
->bookmark_bar_node()->GetChild(i
);
1572 if (node
->is_folder()) {
1573 if (button_x
<= views::kDropBetweenPixels
) {
1574 location
->index
= i
;
1575 } else if (button_x
< button_w
- views::kDropBetweenPixels
) {
1576 location
->index
= i
;
1577 location
->on
= true;
1579 location
->index
= i
+ 1;
1581 } else if (button_x
< button_w
/ 2) {
1582 location
->index
= i
;
1584 location
->index
= i
+ 1;
1591 if (overflow_button_
->visible()) {
1592 // Are we over the overflow button?
1593 int overflow_delta_x
= mirrored_x
- overflow_button_
->x();
1594 if (overflow_delta_x
>= 0 &&
1595 overflow_delta_x
< overflow_button_
->width()) {
1596 // Mouse is over overflow button.
1597 location
->index
= GetFirstHiddenNodeIndex();
1598 location
->button_type
= DROP_OVERFLOW
;
1599 } else if (overflow_delta_x
< 0) {
1600 // Mouse is after the last visible button but before overflow button;
1601 // use the last visible index.
1602 location
->index
= GetFirstHiddenNodeIndex();
1606 } else if (!other_bookmarked_button_
->visible() ||
1607 mirrored_x
< other_bookmarked_button_
->x()) {
1608 // Mouse is after the last visible button but before more recently
1609 // bookmarked; use the last visible index.
1610 location
->index
= GetFirstHiddenNodeIndex();
1617 const BookmarkNode
* parent
= (location
->button_type
== DROP_OTHER_FOLDER
) ?
1618 model_
->other_node() :
1619 model_
->bookmark_bar_node()->GetChild(location
->index
);
1620 location
->operation
= chrome::GetBookmarkDropOperation(
1621 profile
, event
, data
, parent
, parent
->child_count());
1622 if (!location
->operation
&& !data
.has_single_url() &&
1623 data
.GetFirstNode(model_
, profile
->GetPath()) == parent
) {
1624 // Don't open a menu if the node being dragged is the menu to open.
1625 location
->on
= false;
1628 location
->operation
= chrome::GetBookmarkDropOperation(
1629 profile
, event
, data
, model_
->bookmark_bar_node(), location
->index
);
1633 void BookmarkBarView::WriteBookmarkDragData(const BookmarkNode
* node
,
1634 ui::OSExchangeData
* data
) {
1635 DCHECK(node
&& data
);
1636 BookmarkNodeData
drag_data(node
);
1637 drag_data
.Write(browser_
->profile()->GetPath(), data
);
1640 void BookmarkBarView::StartThrobbing(const BookmarkNode
* node
,
1641 bool overflow_only
) {
1642 DCHECK(!throbbing_view_
);
1644 // Determine which visible button is showing the bookmark (or is an ancestor
1645 // of the bookmark).
1646 const BookmarkNode
* bbn
= model_
->bookmark_bar_node();
1647 const BookmarkNode
* parent_on_bb
= node
;
1648 while (parent_on_bb
) {
1649 const BookmarkNode
* parent
= parent_on_bb
->parent();
1652 parent_on_bb
= parent
;
1655 int index
= bbn
->GetIndexOf(parent_on_bb
);
1656 if (index
>= GetFirstHiddenNodeIndex()) {
1657 // Node is hidden, animate the overflow button.
1658 throbbing_view_
= overflow_button_
;
1659 } else if (!overflow_only
) {
1660 throbbing_view_
= static_cast<CustomButton
*>(child_at(index
));
1662 } else if (!overflow_only
) {
1663 throbbing_view_
= other_bookmarked_button_
;
1666 // Use a large number so that the button continues to throb.
1667 if (throbbing_view_
)
1668 throbbing_view_
->StartThrobbing(std::numeric_limits
<int>::max());
1671 views::CustomButton
* BookmarkBarView::DetermineViewToThrobFromRemove(
1672 const BookmarkNode
* parent
,
1674 const BookmarkNode
* bbn
= model_
->bookmark_bar_node();
1675 const BookmarkNode
* old_node
= parent
;
1676 int old_index_on_bb
= old_index
;
1677 while (old_node
&& old_node
!= bbn
) {
1678 const BookmarkNode
* parent
= old_node
->parent();
1679 if (parent
== bbn
) {
1680 old_index_on_bb
= bbn
->GetIndexOf(old_node
);
1686 if (old_index_on_bb
>= GetFirstHiddenNodeIndex()) {
1687 // Node is hidden, animate the overflow button.
1688 return overflow_button_
;
1690 return static_cast<CustomButton
*>(child_at(old_index_on_bb
));
1692 // Node wasn't on the bookmark bar, use the other bookmark button.
1693 return other_bookmarked_button_
;
1696 void BookmarkBarView::UpdateColors() {
1697 // We don't always have a theme provider (ui tests, for example).
1698 const ui::ThemeProvider
* theme_provider
= GetThemeProvider();
1699 if (!theme_provider
)
1701 SkColor text_color
=
1702 theme_provider
->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT
);
1703 for (int i
= 0; i
< GetBookmarkButtonCount(); ++i
)
1704 GetBookmarkButton(i
)->SetEnabledColor(text_color
);
1705 other_bookmarked_button()->SetEnabledColor(text_color
);
1706 if (apps_page_shortcut_
->visible())
1707 apps_page_shortcut_
->SetEnabledColor(text_color
);
1710 void BookmarkBarView::UpdateOtherBookmarksVisibility() {
1711 bool has_other_children
= !model_
->other_node()->empty();
1712 if (has_other_children
== other_bookmarked_button_
->visible())
1714 other_bookmarked_button_
->SetVisible(has_other_children
);
1715 UpdateBookmarksSeparatorVisibility();
1720 void BookmarkBarView::UpdateBookmarksSeparatorVisibility() {
1721 // Ash does not paint the bookmarks separator line because it looks odd on
1722 // the flat background. We keep it present for layout, but don't draw it.
1723 bookmarks_separator_view_
->SetVisible(
1724 browser_
->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH
&&
1725 other_bookmarked_button_
->visible());
1728 void BookmarkBarView::LayoutItems() {
1732 int x
= kLeftMargin
;
1733 int top_margin
= IsDetached() ? kDetachedTopMargin
: 0;
1735 int width
= View::width() - kRightMargin
- kLeftMargin
;
1736 int height
= chrome::kBookmarkBarHeight
- kBottomMargin
;
1737 int separator_margin
= kSeparatorMargin
;
1740 double current_state
= 1 - size_animation_
->GetCurrentValue();
1741 x
+= static_cast<int>(kNewtabHorizontalPadding
* current_state
);
1742 y
+= (View::height() - chrome::kBookmarkBarHeight
) / 2;
1743 width
-= static_cast<int>(kNewtabHorizontalPadding
* current_state
);
1744 separator_margin
-= static_cast<int>(kSeparatorMargin
* current_state
);
1746 // For the attached appearance, pin the content to the bottom of the bar
1747 // when animating in/out, as shrinking its height instead looks weird. This
1748 // also matches how we layout infobars.
1749 y
+= View::height() - chrome::kBookmarkBarHeight
;
1752 gfx::Size other_bookmarked_pref
= other_bookmarked_button_
->visible() ?
1753 other_bookmarked_button_
->GetPreferredSize() : gfx::Size();
1754 gfx::Size overflow_pref
= overflow_button_
->GetPreferredSize();
1755 gfx::Size bookmarks_separator_pref
=
1756 bookmarks_separator_view_
->GetPreferredSize();
1757 gfx::Size apps_page_shortcut_pref
= apps_page_shortcut_
->visible() ?
1758 apps_page_shortcut_
->GetPreferredSize() : gfx::Size();
1760 int max_x
= width
- overflow_pref
.width() - kButtonPadding
-
1761 bookmarks_separator_pref
.width();
1762 if (other_bookmarked_button_
->visible())
1763 max_x
-= other_bookmarked_pref
.width() + kButtonPadding
;
1765 // Next, layout out the buttons. Any buttons that are placed beyond the
1766 // visible region and made invisible.
1768 // Start with the apps page shortcut button.
1769 if (apps_page_shortcut_
->visible()) {
1770 apps_page_shortcut_
->SetBounds(x
, y
, apps_page_shortcut_pref
.width(),
1772 x
+= apps_page_shortcut_pref
.width() + kButtonPadding
;
1775 // Then go through the bookmark buttons.
1776 if (GetBookmarkButtonCount() == 0 && model_
&& model_
->loaded()) {
1777 gfx::Size pref
= instructions_
->GetPreferredSize();
1778 instructions_
->SetBounds(
1779 x
+ kInstructionsPadding
, y
,
1780 std::min(static_cast<int>(pref
.width()),
1783 instructions_
->SetVisible(true);
1785 instructions_
->SetVisible(false);
1787 for (int i
= 0; i
< GetBookmarkButtonCount(); ++i
) {
1788 views::View
* child
= child_at(i
);
1789 gfx::Size pref
= child
->GetPreferredSize();
1790 int next_x
= x
+ pref
.width() + kButtonPadding
;
1791 child
->SetVisible(next_x
< max_x
);
1792 child
->SetBounds(x
, y
, pref
.width(), height
);
1797 // Layout the right side of the bar.
1798 const bool all_visible
= (GetBookmarkButtonCount() == 0 ||
1799 child_at(GetBookmarkButtonCount() - 1)->visible());
1801 // Layout the right side buttons.
1802 x
= max_x
+ kButtonPadding
;
1804 // The overflow button.
1805 overflow_button_
->SetBounds(x
, y
, overflow_pref
.width(), height
);
1806 overflow_button_
->SetVisible(!all_visible
);
1807 x
+= overflow_pref
.width();
1810 if (bookmarks_separator_view_
->visible()) {
1811 bookmarks_separator_view_
->SetBounds(x
,
1813 bookmarks_separator_pref
.width(),
1814 height
+ top_margin
+ kBottomMargin
-
1817 x
+= bookmarks_separator_pref
.width();
1820 // The other bookmarks button.
1821 if (other_bookmarked_button_
->visible()) {
1822 other_bookmarked_button_
->SetBounds(x
, y
, other_bookmarked_pref
.width(),
1824 x
+= other_bookmarked_pref
.width() + kButtonPadding
;
1828 void BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged() {
1829 DCHECK(apps_page_shortcut_
);
1830 // Only perform layout if required.
1831 bool visible
= chrome::ShouldShowAppsShortcutInBookmarkBar(
1832 browser_
->profile(), browser_
->host_desktop_type());
1833 if (apps_page_shortcut_
->visible() == visible
)
1835 apps_page_shortcut_
->SetVisible(visible
);
1836 UpdateBookmarksSeparatorVisibility();