Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / bookmarks / bookmark_bar_view.cc
blob41421175661ee03e47ebf78fb7557f04aeb436c2
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"
7 #include <algorithm>
8 #include <limits>
9 #include <set>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/i18n/rtl.h"
14 #include "base/metrics/histogram.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/browser/bookmarks/bookmark_model.h"
19 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/browser_shutdown.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/defaults.h"
24 #include "chrome/browser/extensions/extension_service.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/search/search.h"
27 #include "chrome/browser/sync/profile_sync_service.h"
28 #include "chrome/browser/sync/profile_sync_service_factory.h"
29 #include "chrome/browser/themes/theme_properties.h"
30 #include "chrome/browser/ui/bookmarks/bookmark_bar_constants.h"
31 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
32 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
33 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
34 #include "chrome/browser/ui/browser.h"
35 #include "chrome/browser/ui/chrome_pages.h"
36 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
37 #include "chrome/browser/ui/omnibox/omnibox_view.h"
38 #include "chrome/browser/ui/tabs/tab_strip_model.h"
39 #include "chrome/browser/ui/view_ids.h"
40 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_instructions_view.h"
41 #include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h"
42 #include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h"
43 #include "chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h"
44 #include "chrome/browser/ui/views/event_utils.h"
45 #include "chrome/browser/ui/views/frame/browser_view.h"
46 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
47 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
48 #include "chrome/common/chrome_switches.h"
49 #include "chrome/common/extensions/extension_constants.h"
50 #include "chrome/common/pref_names.h"
51 #include "chrome/common/url_constants.h"
52 #include "content/public/browser/notification_details.h"
53 #include "content/public/browser/notification_source.h"
54 #include "content/public/browser/page_navigator.h"
55 #include "content/public/browser/render_view_host.h"
56 #include "content/public/browser/render_widget_host_view.h"
57 #include "content/public/browser/user_metrics.h"
58 #include "content/public/browser/web_contents.h"
59 #include "content/public/common/page_transition_types.h"
60 #include "grit/generated_resources.h"
61 #include "grit/theme_resources.h"
62 #include "grit/ui_resources.h"
63 #include "ui/base/accessibility/accessible_view_state.h"
64 #include "ui/base/dragdrop/drag_utils.h"
65 #include "ui/base/dragdrop/os_exchange_data.h"
66 #include "ui/base/l10n/l10n_util.h"
67 #include "ui/base/resource/resource_bundle.h"
68 #include "ui/base/theme_provider.h"
69 #include "ui/base/window_open_disposition.h"
70 #include "ui/gfx/animation/slide_animation.h"
71 #include "ui/gfx/canvas.h"
72 #include "ui/gfx/text_elider.h"
73 #include "ui/views/button_drag_utils.h"
74 #include "ui/views/controls/button/menu_button.h"
75 #include "ui/views/controls/label.h"
76 #include "ui/views/controls/menu/menu_item_view.h"
77 #include "ui/views/drag_utils.h"
78 #include "ui/views/metrics.h"
79 #include "ui/views/view_constants.h"
80 #include "ui/views/widget/tooltip_manager.h"
81 #include "ui/views/widget/widget.h"
82 #include "ui/views/window/non_client_view.h"
84 using base::UserMetricsAction;
85 using content::OpenURLParams;
86 using content::PageNavigator;
87 using content::Referrer;
88 using ui::DropTargetEvent;
89 using views::CustomButton;
90 using views::MenuButton;
91 using views::MenuItemView;
92 using views::View;
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;
101 // static
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;
138 namespace {
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 {
149 public:
150 BookmarkButtonBase(views::ButtonListener* listener,
151 const base::string16& title)
152 : TextButton(listener, title) {
153 show_animation_.reset(new gfx::SlideAnimation(this));
154 if (!animations_enabled) {
155 // For some reason during testing the events generated by animating
156 // throw off the test. So, don't animate while testing.
157 show_animation_->Reset(1);
158 } else {
159 show_animation_->Show();
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);
169 private:
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 {
180 public:
181 // The internal view class name.
182 static const char kViewClassName[];
184 BookmarkButton(views::ButtonListener* listener,
185 const GURL& url,
186 const base::string16& title,
187 Profile* profile)
188 : BookmarkButtonBase(listener, title),
189 url_(url),
190 profile_(profile) {
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;
206 private:
207 const GURL& url_;
208 Profile* profile_;
210 DISALLOW_COPY_AND_ASSIGN(BookmarkButton);
213 // static
214 const char BookmarkButton::kViewClassName[] = "BookmarkButton";
216 // ShortcutButton -------------------------------------------------------------
218 // Buttons used for the shortcuts on the bookmark bar.
220 class ShortcutButton : public BookmarkButtonBase {
221 public:
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;
234 private:
236 DISALLOW_COPY_AND_ASSIGN(ShortcutButton);
239 // static
240 const char ShortcutButton::kViewClassName[] = "ShortcutButton";
242 // BookmarkFolderButton -------------------------------------------------------
244 // Buttons used for folders on the bookmark bar, including the 'other folders'
245 // button.
246 class BookmarkFolderButton : public views::MenuButton {
247 public:
248 BookmarkFolderButton(views::ButtonListener* listener,
249 const base::string16& title,
250 views::MenuButtonListener* menu_button_listener,
251 bool show_menu_marker)
252 : MenuButton(listener, title, menu_button_listener, show_menu_marker) {
253 show_animation_.reset(new gfx::SlideAnimation(this));
254 if (!animations_enabled) {
255 // For some reason during testing the events generated by animating
256 // throw off the test. So, don't animate while testing.
257 show_animation_->Reset(1);
258 } else {
259 show_animation_->Show();
263 virtual bool GetTooltipText(const gfx::Point& p,
264 base::string16* tooltip) const OVERRIDE {
265 if (text_size_.width() > GetTextBounds().width())
266 *tooltip = text_;
267 return !tooltip->empty();
270 virtual bool IsTriggerableEvent(const ui::Event& e) OVERRIDE {
271 // Left clicks and taps should show the menu contents and right clicks
272 // should show the context menu. They should not trigger the opening of
273 // underlying urls.
274 if (e.type() == ui::ET_GESTURE_TAP ||
275 (e.IsMouseEvent() && (e.flags() &
276 (ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON))))
277 return false;
279 if (e.IsMouseEvent())
280 return ui::DispositionFromEventFlags(e.flags()) != CURRENT_TAB;
281 return false;
284 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
285 views::MenuButton::PaintButton(canvas, views::MenuButton::PB_NORMAL);
288 private:
289 scoped_ptr<gfx::SlideAnimation> show_animation_;
291 DISALLOW_COPY_AND_ASSIGN(BookmarkFolderButton);
294 // OverFlowButton (chevron) --------------------------------------------------
296 class OverFlowButton : public views::MenuButton {
297 public:
298 explicit OverFlowButton(BookmarkBarView* owner)
299 : MenuButton(NULL, base::string16(), owner, false),
300 owner_(owner) {}
302 virtual bool OnMousePressed(const ui::MouseEvent& e) OVERRIDE {
303 owner_->StopThrobbing(true);
304 return views::MenuButton::OnMousePressed(e);
307 private:
308 BookmarkBarView* owner_;
310 DISALLOW_COPY_AND_ASSIGN(OverFlowButton);
313 void RecordAppLaunch(Profile* profile, GURL url) {
314 DCHECK(profile->GetExtensionService());
315 const extensions::Extension* extension =
316 profile->GetExtensionService()->GetInstalledApp(url);
317 if (!extension)
318 return;
320 CoreAppLauncherHandler::RecordAppLaunchType(
321 extension_misc::APP_LAUNCH_BOOKMARK_BAR,
322 extension->GetType());
325 } // namespace
327 // DropLocation ---------------------------------------------------------------
329 struct BookmarkBarView::DropLocation {
330 DropLocation()
331 : index(-1),
332 operation(ui::DragDropTypes::DRAG_NONE),
333 on(false),
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.
343 int index;
345 // Drop constants.
346 int operation;
348 // If true, the user is dropping on a folder.
349 bool on;
351 // Type of button.
352 DropButtonType button_type;
355 // DropInfo -------------------------------------------------------------------
357 // Tracks drops on the BookmarkBarView.
359 struct BookmarkBarView::DropInfo {
360 DropInfo()
361 : valid(false),
362 is_menu_showing(false),
363 x(0),
364 y(0) {
367 // Whether the data is valid.
368 bool valid;
370 // If true, the menu is being shown.
371 bool is_menu_showing;
373 // Coordinates of the drag (in terms of the BookmarkBarView).
374 int x;
375 int y;
377 // DropData for the drop.
378 BookmarkNodeData data;
380 DropLocation location;
383 // ButtonSeparatorView --------------------------------------------------------
385 class BookmarkBarView::ButtonSeparatorView : public views::View {
386 public:
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() 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::AccessibleViewState* state) OVERRIDE {
405 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_SEPARATOR);
406 state->role = ui::AccessibilityTypes::ROLE_SEPARATOR;
409 private:
410 DISALLOW_COPY_AND_ASSIGN(ButtonSeparatorView);
413 // BookmarkBarView ------------------------------------------------------------
415 // static
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() {
429 if (!kFolderIcon) {
430 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
431 kFolderIcon = rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER);
433 return *kFolderIcon;
436 BookmarkBarView::BookmarkBarView(Browser* browser, BrowserView* browser_view)
437 : page_navigator_(NULL),
438 model_(NULL),
439 bookmark_menu_(NULL),
440 bookmark_drop_menu_(NULL),
441 other_bookmarked_button_(NULL),
442 apps_page_shortcut_(NULL),
443 show_folder_method_factory_(this),
444 overflow_button_(NULL),
445 instructions_(NULL),
446 bookmarks_separator_view_(NULL),
447 browser_(browser),
448 browser_view_(browser_view),
449 infobar_visible_(false),
450 throbbing_view_(NULL),
451 bookmark_bar_state_(BookmarkBar::SHOW),
452 animating_detached_(false) {
453 set_id(VIEW_ID_BOOKMARK_BAR);
454 Init();
456 size_animation_->Reset(1);
459 BookmarkBarView::~BookmarkBarView() {
460 if (model_)
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();
476 // static
477 void BookmarkBarView::DisableAnimationsForTesting(bool disabled) {
478 animations_enabled = !disabled;
481 void BookmarkBarView::SetPageNavigator(PageNavigator* navigator) {
482 page_navigator_ = navigator;
483 if (bookmark_menu_)
484 bookmark_menu_->SetPageNavigator(navigator);
485 if (context_menu_.get())
486 context_menu_->SetPageNavigator(navigator);
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();
498 else
499 size_animation_->Hide();
500 } else {
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
509 // infobars visible.
510 return 0;
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())
525 return NULL;
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())
533 break;
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();
551 return NULL;
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())
562 return NULL;
563 return static_cast<views::MenuButton*>(child_at(index));
566 void BookmarkBarView::GetAnchorPositionForButton(
567 views::MenuButton* button,
568 MenuItemView::AnchorPosition* anchor) {
569 if (button == other_bookmarked_button_ || button == overflow_button_)
570 *anchor = MenuItemView::TOPRIGHT;
571 else
572 *anchor = MenuItemView::TOPLEFT;
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_)
589 return;
591 // If not immediate, cycle through 2 more complete cycles.
592 throbbing_view_->StartThrobbing(immediate ? 0 : 4);
593 throbbing_view_ = NULL;
596 // static
597 base::string16 BookmarkBarView::CreateToolTipForURLAndTitle(
598 const views::Widget* widget,
599 const gfx::Point& screen_loc,
600 const GURL& url,
601 const base::string16& title,
602 Profile* profile) {
603 int max_width = views::TooltipManager::GetMaxWidth(
604 screen_loc.x(),
605 screen_loc.y(),
606 widget->GetNativeView());
607 const gfx::FontList tt_fonts = widget->GetTooltipManager()->GetFontList();
608 base::string16 result;
610 // First the title.
611 if (!title.empty()) {
612 base::string16 localized_title = title;
613 base::i18n::AdjustStringForLocaleDirection(&localized_title);
614 result.append(gfx::ElideText(localized_title, tt_fonts, max_width,
615 gfx::ELIDE_AT_END));
618 // Only show the URL if the url and title differ.
619 if (title != base::UTF8ToUTF16(url.spec())) {
620 if (!result.empty())
621 result.push_back('\n');
623 // We need to explicitly specify the directionality of the URL's text to
624 // make sure it is treated as an LTR string when the context is RTL. For
625 // example, the URL "http://www.yahoo.com/" appears as
626 // "/http://www.yahoo.com" when rendered, as is, in an RTL context since
627 // the Unicode BiDi algorithm puts certain characters on the left by
628 // default.
629 std::string languages = profile->GetPrefs()->GetString(
630 prefs::kAcceptLanguages);
631 base::string16 elided_url(
632 gfx::ElideUrl(url, tt_fonts, max_width, languages));
633 elided_url = base::i18n::GetDisplayStringInLTRDirectionality(elided_url);
634 result.append(elided_url);
636 return result;
639 bool BookmarkBarView::IsDetached() const {
640 return (bookmark_bar_state_ == BookmarkBar::DETACHED) ||
641 (animating_detached_ && size_animation_->is_animating());
644 double BookmarkBarView::GetAnimationValue() const {
645 return size_animation_->GetCurrentValue();
648 int BookmarkBarView::GetToolbarOverlap() const {
649 int attached_overlap = kToolbarAttachedBookmarkBarOverlap +
650 views::NonClientFrameView::kClientEdgeThickness;
651 if (!IsDetached())
652 return attached_overlap;
654 int detached_overlap = GetFullyDetachedToolbarOverlap();
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;
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());
668 gfx::Size BookmarkBarView::GetPreferredSize() {
669 return LayoutItems(true);
672 bool BookmarkBarView::HitTestRect(const gfx::Rect& rect) const {
673 // If bookmark bar is attached and omnibox popup is open (on top of the bar),
674 // force hit-testing to fail. This prevents hovers/clicks just above the
675 // omnibox popup from activating the top few pixels of items on the bookmark
676 // bar.
677 if (!IsDetached() && browser_view_ &&
678 browser_view_->GetLocationBar()->GetOmniboxView()->model()->
679 popup_model()->IsOpen()) {
680 return false;
682 return DetachableToolbarView::HitTestRect(rect);
685 gfx::Size BookmarkBarView::GetMinimumSize() {
686 // The minimum width of the bookmark bar should at least contain the overflow
687 // button, by which one can access all the Bookmark Bar items, and the "Other
688 // Bookmarks" folder, along with appropriate margins and button padding.
689 int width = kLeftMargin;
691 int height = chrome::kBookmarkBarHeight;
692 if (IsDetached()) {
693 double current_state = 1 - size_animation_->GetCurrentValue();
694 width += 2 * static_cast<int>(kNewtabHorizontalPadding * current_state);
695 height += static_cast<int>(
696 (chrome::kNTPBookmarkBarHeight - chrome::kBookmarkBarHeight) *
697 current_state);
700 gfx::Size other_bookmarked_pref;
701 if (other_bookmarked_button_->visible())
702 other_bookmarked_pref = other_bookmarked_button_->GetPreferredSize();
703 gfx::Size overflow_pref;
704 if (overflow_button_->visible())
705 overflow_pref = overflow_button_->GetPreferredSize();
706 gfx::Size bookmarks_separator_pref;
707 if (bookmarks_separator_view_->visible())
708 bookmarks_separator_pref = bookmarks_separator_view_->GetPreferredSize();
710 gfx::Size apps_page_shortcut_pref;
711 if (apps_page_shortcut_->visible())
712 apps_page_shortcut_pref = apps_page_shortcut_->GetPreferredSize();
713 width += other_bookmarked_pref.width() + kButtonPadding +
714 apps_page_shortcut_pref.width() + kButtonPadding +
715 overflow_pref.width() + kButtonPadding +
716 bookmarks_separator_pref.width();
718 return gfx::Size(width, height);
721 void BookmarkBarView::Layout() {
722 LayoutItems(false);
725 void BookmarkBarView::ViewHierarchyChanged(
726 const ViewHierarchyChangedDetails& details) {
727 if (details.is_add && details.child == this) {
728 // We may get inserted into a hierarchy with a profile - this typically
729 // occurs when the bar's contents get populated fast enough that the
730 // buttons are created before the bar is attached to a frame.
731 UpdateColors();
733 if (height() > 0) {
734 // We only layout while parented. When we become parented, if our bounds
735 // haven't changed, OnBoundsChanged() won't get invoked and we won't
736 // layout. Therefore we always force a layout when added.
737 Layout();
742 void BookmarkBarView::PaintChildren(gfx::Canvas* canvas) {
743 View::PaintChildren(canvas);
745 if (drop_info_.get() && drop_info_->valid &&
746 drop_info_->location.operation != 0 && drop_info_->location.index != -1 &&
747 drop_info_->location.button_type != DROP_OVERFLOW &&
748 !drop_info_->location.on) {
749 int index = drop_info_->location.index;
750 DCHECK(index <= GetBookmarkButtonCount());
751 int x = 0;
752 int y = 0;
753 int h = height();
754 if (index == GetBookmarkButtonCount()) {
755 if (index == 0) {
756 x = kLeftMargin;
757 } else {
758 x = GetBookmarkButton(index - 1)->x() +
759 GetBookmarkButton(index - 1)->width();
761 } else {
762 x = GetBookmarkButton(index)->x();
764 if (GetBookmarkButtonCount() > 0 && GetBookmarkButton(0)->visible()) {
765 y = GetBookmarkButton(0)->y();
766 h = GetBookmarkButton(0)->height();
769 // Since the drop indicator is painted directly onto the canvas, we must
770 // make sure it is painted in the right location if the locale is RTL.
771 gfx::Rect indicator_bounds(x - kDropIndicatorWidth / 2,
773 kDropIndicatorWidth,
775 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
777 // TODO(sky/glen): make me pretty!
778 canvas->FillRect(indicator_bounds, kDropIndicatorColor);
782 bool BookmarkBarView::GetDropFormats(
783 int* formats,
784 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
785 if (!model_ || !model_->loaded())
786 return false;
787 *formats = ui::OSExchangeData::URL;
788 custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
789 return true;
792 bool BookmarkBarView::AreDropTypesRequired() {
793 return true;
796 bool BookmarkBarView::CanDrop(const ui::OSExchangeData& data) {
797 if (!model_ || !model_->loaded() ||
798 !browser_->profile()->GetPrefs()->GetBoolean(
799 prefs::kEditBookmarksEnabled))
800 return false;
802 if (!drop_info_.get())
803 drop_info_.reset(new DropInfo());
805 // Only accept drops of 1 node, which is the case for all data dragged from
806 // bookmark bar and menus.
807 return drop_info_->data.Read(data) && drop_info_->data.size() == 1;
810 void BookmarkBarView::OnDragEntered(const DropTargetEvent& event) {
813 int BookmarkBarView::OnDragUpdated(const DropTargetEvent& event) {
814 if (!drop_info_.get())
815 return 0;
817 if (drop_info_->valid &&
818 (drop_info_->x == event.x() && drop_info_->y == event.y())) {
819 // The location of the mouse didn't change, return the last operation.
820 return drop_info_->location.operation;
823 drop_info_->x = event.x();
824 drop_info_->y = event.y();
826 DropLocation location;
827 CalculateDropLocation(event, drop_info_->data, &location);
829 if (drop_info_->valid && drop_info_->location.Equals(location)) {
830 // The position we're going to drop didn't change, return the last drag
831 // operation we calculated. Copy of the operation in case it changed.
832 drop_info_->location.operation = location.operation;
833 return drop_info_->location.operation;
836 StopShowFolderDropMenuTimer();
838 // TODO(sky): Optimize paint region.
839 SchedulePaint();
841 drop_info_->location = location;
842 drop_info_->valid = true;
844 if (drop_info_->is_menu_showing) {
845 if (bookmark_drop_menu_)
846 bookmark_drop_menu_->Cancel();
847 drop_info_->is_menu_showing = false;
850 if (location.on || location.button_type == DROP_OVERFLOW ||
851 location.button_type == DROP_OTHER_FOLDER) {
852 const BookmarkNode* node;
853 if (location.button_type == DROP_OTHER_FOLDER)
854 node = model_->other_node();
855 else if (location.button_type == DROP_OVERFLOW)
856 node = model_->bookmark_bar_node();
857 else
858 node = model_->bookmark_bar_node()->GetChild(location.index);
859 StartShowFolderDropMenuTimer(node);
862 return drop_info_->location.operation;
865 void BookmarkBarView::OnDragExited() {
866 StopShowFolderDropMenuTimer();
868 // NOTE: we don't hide the menu on exit as it's possible the user moved the
869 // mouse over the menu, which triggers an exit on us.
871 drop_info_->valid = false;
873 if (drop_info_->location.index != -1) {
874 // TODO(sky): optimize the paint region.
875 SchedulePaint();
877 drop_info_.reset();
880 int BookmarkBarView::OnPerformDrop(const DropTargetEvent& event) {
881 StopShowFolderDropMenuTimer();
883 if (bookmark_drop_menu_)
884 bookmark_drop_menu_->Cancel();
886 if (!drop_info_.get() || !drop_info_->location.operation)
887 return ui::DragDropTypes::DRAG_NONE;
889 const BookmarkNode* root =
890 (drop_info_->location.button_type == DROP_OTHER_FOLDER) ?
891 model_->other_node() : model_->bookmark_bar_node();
892 int index = drop_info_->location.index;
894 if (index != -1) {
895 // TODO(sky): optimize the SchedulePaint region.
896 SchedulePaint();
898 const BookmarkNode* parent_node;
899 if (drop_info_->location.button_type == DROP_OTHER_FOLDER) {
900 parent_node = root;
901 index = parent_node->child_count();
902 } else if (drop_info_->location.on) {
903 parent_node = root->GetChild(index);
904 index = parent_node->child_count();
905 } else {
906 parent_node = root;
908 const BookmarkNodeData data = drop_info_->data;
909 DCHECK(data.is_valid());
910 drop_info_.reset();
911 return chrome::DropBookmarks(browser_->profile(), data, parent_node, index);
914 void BookmarkBarView::OnThemeChanged() {
915 UpdateColors();
918 const char* BookmarkBarView::GetClassName() const {
919 return kViewClassName;
922 void BookmarkBarView::GetAccessibleState(ui::AccessibleViewState* state) {
923 state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
924 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS);
927 void BookmarkBarView::AnimationProgressed(const gfx::Animation* animation) {
928 // |browser_view_| can be NULL during tests.
929 if (browser_view_)
930 browser_view_->ToolbarSizeChanged(true);
933 void BookmarkBarView::AnimationEnded(const gfx::Animation* animation) {
934 // |browser_view_| can be NULL during tests.
935 if (browser_view_) {
936 browser_view_->ToolbarSizeChanged(false);
937 SchedulePaint();
941 void BookmarkBarView::BookmarkMenuControllerDeleted(
942 BookmarkMenuController* controller) {
943 if (controller == bookmark_menu_)
944 bookmark_menu_ = NULL;
945 else if (controller == bookmark_drop_menu_)
946 bookmark_drop_menu_ = NULL;
949 void BookmarkBarView::ShowImportDialog() {
950 int64 install_time =
951 g_browser_process->local_state()->GetInt64(prefs::kInstallDate);
952 int64 time_from_install = base::Time::Now().ToTimeT() - install_time;
953 if (bookmark_bar_state_ == BookmarkBar::SHOW) {
954 UMA_HISTOGRAM_COUNTS("Import.ShowDialog.FromBookmarkBarView",
955 time_from_install);
956 } else if (bookmark_bar_state_ == BookmarkBar::DETACHED) {
957 UMA_HISTOGRAM_COUNTS("Import.ShowDialog.FromFloatingBookmarkBarView",
958 time_from_install);
961 chrome::ShowImportDialog(browser_);
964 void BookmarkBarView::OnBookmarkBubbleShown(const GURL& url) {
965 StopThrobbing(true);
966 const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url);
967 if (!node)
968 return; // Generally shouldn't happen.
969 StartThrobbing(node, false);
972 void BookmarkBarView::OnBookmarkBubbleHidden() {
973 StopThrobbing(false);
976 void BookmarkBarView::BookmarkModelLoaded(BookmarkModel* model,
977 bool ids_reassigned) {
978 // There should be no buttons. If non-zero it means Load was invoked more than
979 // once, or we didn't properly clear things. Either of which shouldn't happen.
980 DCHECK_EQ(0, GetBookmarkButtonCount());
981 const BookmarkNode* node = model_->bookmark_bar_node();
982 DCHECK(node);
983 // Create a button for each of the children on the bookmark bar.
984 for (int i = 0, child_count = node->child_count(); i < child_count; ++i)
985 AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i);
986 DCHECK(model_->other_node());
987 other_bookmarked_button_->SetAccessibleName(model_->other_node()->GetTitle());
988 other_bookmarked_button_->SetText(model_->other_node()->GetTitle());
989 UpdateColors();
990 UpdateOtherBookmarksVisibility();
991 other_bookmarked_button_->SetEnabled(true);
993 Layout();
994 SchedulePaint();
997 void BookmarkBarView::BookmarkModelBeingDeleted(BookmarkModel* model) {
998 // In normal shutdown The bookmark model should never be deleted before us.
999 // When X exits suddenly though, it can happen, This code exists
1000 // to check for regressions in shutdown code and not crash.
1001 if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers())
1002 NOTREACHED();
1004 // Do minimal cleanup, presumably we'll be deleted shortly.
1005 model_->RemoveObserver(this);
1006 model_ = NULL;
1009 void BookmarkBarView::BookmarkNodeMoved(BookmarkModel* model,
1010 const BookmarkNode* old_parent,
1011 int old_index,
1012 const BookmarkNode* new_parent,
1013 int new_index) {
1014 bool was_throbbing = throbbing_view_ &&
1015 throbbing_view_ == DetermineViewToThrobFromRemove(old_parent, old_index);
1016 if (was_throbbing)
1017 throbbing_view_->StopThrobbing();
1018 BookmarkNodeRemovedImpl(model, old_parent, old_index);
1019 BookmarkNodeAddedImpl(model, new_parent, new_index);
1020 if (was_throbbing)
1021 StartThrobbing(new_parent->GetChild(new_index), false);
1024 void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model,
1025 const BookmarkNode* parent,
1026 int index) {
1027 BookmarkNodeAddedImpl(model, parent, index);
1030 void BookmarkBarView::BookmarkNodeRemoved(BookmarkModel* model,
1031 const BookmarkNode* parent,
1032 int old_index,
1033 const BookmarkNode* node) {
1034 // Close the menu if the menu is showing for the deleted node.
1035 if (bookmark_menu_ && bookmark_menu_->node() == node)
1036 bookmark_menu_->Cancel();
1037 BookmarkNodeRemovedImpl(model, parent, old_index);
1040 void BookmarkBarView::BookmarkAllNodesRemoved(BookmarkModel* model) {
1041 UpdateOtherBookmarksVisibility();
1043 StopThrobbing(true);
1045 // Remove the existing buttons.
1046 while (GetBookmarkButtonCount()) {
1047 delete GetBookmarkButton(0);
1050 Layout();
1051 SchedulePaint();
1054 void BookmarkBarView::BookmarkNodeChanged(BookmarkModel* model,
1055 const BookmarkNode* node) {
1056 BookmarkNodeChangedImpl(model, node);
1059 void BookmarkBarView::BookmarkNodeChildrenReordered(BookmarkModel* model,
1060 const BookmarkNode* node) {
1061 if (node != model_->bookmark_bar_node())
1062 return; // We only care about reordering of the bookmark bar node.
1064 // Remove the existing buttons.
1065 while (GetBookmarkButtonCount()) {
1066 views::View* button = child_at(0);
1067 RemoveChildView(button);
1068 base::MessageLoop::current()->DeleteSoon(FROM_HERE, button);
1071 // Create the new buttons.
1072 for (int i = 0, child_count = node->child_count(); i < child_count; ++i)
1073 AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i);
1074 UpdateColors();
1076 Layout();
1077 SchedulePaint();
1080 void BookmarkBarView::BookmarkNodeFaviconChanged(BookmarkModel* model,
1081 const BookmarkNode* node) {
1082 BookmarkNodeChangedImpl(model, node);
1085 void BookmarkBarView::WriteDragDataForView(View* sender,
1086 const gfx::Point& press_pt,
1087 ui::OSExchangeData* data) {
1088 content::RecordAction(UserMetricsAction("BookmarkBar_DragButton"));
1090 for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1091 if (sender == GetBookmarkButton(i)) {
1092 views::TextButton* button = GetBookmarkButton(i);
1093 scoped_ptr<gfx::Canvas> canvas(
1094 views::GetCanvasForDragImage(button->GetWidget(), button->size()));
1095 button->PaintButton(canvas.get(), views::TextButton::PB_FOR_DRAG);
1096 drag_utils::SetDragImageOnDataObject(*canvas, button->size(),
1097 press_pt.OffsetFromOrigin(),
1098 data);
1099 WriteBookmarkDragData(model_->bookmark_bar_node()->GetChild(i), data);
1100 return;
1103 NOTREACHED();
1106 int BookmarkBarView::GetDragOperationsForView(View* sender,
1107 const gfx::Point& p) {
1108 if (size_animation_->is_animating() ||
1109 (size_animation_->GetCurrentValue() == 0 &&
1110 bookmark_bar_state_ != BookmarkBar::DETACHED)) {
1111 // Don't let the user drag while animating open or we're closed (and not
1112 // detached, when detached size_animation_ is always 0). This typically is
1113 // only hit if the user does something to inadvertently trigger DnD such as
1114 // pressing the mouse and hitting control-b.
1115 return ui::DragDropTypes::DRAG_NONE;
1118 for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1119 if (sender == GetBookmarkButton(i)) {
1120 return chrome::GetBookmarkDragOperation(
1121 browser_->profile(), model_->bookmark_bar_node()->GetChild(i));
1124 NOTREACHED();
1125 return ui::DragDropTypes::DRAG_NONE;
1128 bool BookmarkBarView::CanStartDragForView(views::View* sender,
1129 const gfx::Point& press_pt,
1130 const gfx::Point& p) {
1131 // Check if we have not moved enough horizontally but we have moved downward
1132 // vertically - downward drag.
1133 gfx::Vector2d move_offset = p - press_pt;
1134 gfx::Vector2d horizontal_offset(move_offset.x(), 0);
1135 if (!View::ExceededDragThreshold(horizontal_offset) && move_offset.y() > 0) {
1136 for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1137 if (sender == GetBookmarkButton(i)) {
1138 const BookmarkNode* node = model_->bookmark_bar_node()->GetChild(i);
1139 // If the folder button was dragged, show the menu instead.
1140 if (node && node->is_folder()) {
1141 views::MenuButton* menu_button =
1142 static_cast<views::MenuButton*>(sender);
1143 menu_button->Activate();
1144 return false;
1146 break;
1150 return true;
1153 void BookmarkBarView::OnMenuButtonClicked(views::View* view,
1154 const gfx::Point& point) {
1155 const BookmarkNode* node;
1157 int start_index = 0;
1158 if (view == other_bookmarked_button_) {
1159 node = model_->other_node();
1160 } else if (view == overflow_button_) {
1161 node = model_->bookmark_bar_node();
1162 start_index = GetFirstHiddenNodeIndex();
1163 } else {
1164 int button_index = GetIndexOf(view);
1165 DCHECK_NE(-1, button_index);
1166 node = model_->bookmark_bar_node()->GetChild(button_index);
1169 RecordBookmarkFolderOpen(GetBookmarkLaunchLocation());
1170 bookmark_menu_ = new BookmarkMenuController(
1171 browser_, page_navigator_, GetWidget(), node, start_index);
1172 bookmark_menu_->set_observer(this);
1173 bookmark_menu_->RunMenuAt(this, false);
1176 void BookmarkBarView::ButtonPressed(views::Button* sender,
1177 const ui::Event& event) {
1178 WindowOpenDisposition disposition_from_event_flags =
1179 ui::DispositionFromEventFlags(event.flags());
1181 if (sender->tag() == kAppsShortcutButtonTag) {
1182 OpenURLParams params(GURL(chrome::kChromeUIAppsURL),
1183 Referrer(),
1184 disposition_from_event_flags,
1185 content::PAGE_TRANSITION_AUTO_BOOKMARK,
1186 false);
1187 page_navigator_->OpenURL(params);
1188 RecordBookmarkAppsPageOpen(GetBookmarkLaunchLocation());
1189 return;
1192 const BookmarkNode* node;
1193 if (sender->tag() == kOtherFolderButtonTag) {
1194 node = model_->other_node();
1195 } else {
1196 int index = GetIndexOf(sender);
1197 DCHECK_NE(-1, index);
1198 node = model_->bookmark_bar_node()->GetChild(index);
1200 DCHECK(page_navigator_);
1202 if (node->is_url()) {
1203 RecordAppLaunch(browser_->profile(), node->url());
1204 OpenURLParams params(
1205 node->url(), Referrer(), disposition_from_event_flags,
1206 content::PAGE_TRANSITION_AUTO_BOOKMARK, false);
1207 page_navigator_->OpenURL(params);
1208 } else {
1209 chrome::OpenAll(GetWidget()->GetNativeWindow(), page_navigator_, node,
1210 disposition_from_event_flags, browser_->profile());
1213 RecordBookmarkLaunch(node, GetBookmarkLaunchLocation());
1216 void BookmarkBarView::ShowContextMenuForView(views::View* source,
1217 const gfx::Point& point,
1218 ui::MenuSourceType source_type) {
1219 if (!model_->loaded()) {
1220 // Don't do anything if the model isn't loaded.
1221 return;
1224 const BookmarkNode* parent = NULL;
1225 std::vector<const BookmarkNode*> nodes;
1226 if (source == other_bookmarked_button_) {
1227 parent = model_->other_node();
1228 // Do this so the user can open all bookmarks. BookmarkContextMenu makes
1229 // sure the user can't edit/delete the node in this case.
1230 nodes.push_back(parent);
1231 } else if (source != this && source != apps_page_shortcut_) {
1232 // User clicked on one of the bookmark buttons, find which one they
1233 // clicked on, except for the apps page shortcut, which must behave as if
1234 // the user clicked on the bookmark bar background.
1235 int bookmark_button_index = GetIndexOf(source);
1236 DCHECK(bookmark_button_index != -1 &&
1237 bookmark_button_index < GetBookmarkButtonCount());
1238 const BookmarkNode* node =
1239 model_->bookmark_bar_node()->GetChild(bookmark_button_index);
1240 nodes.push_back(node);
1241 parent = node->parent();
1242 } else {
1243 parent = model_->bookmark_bar_node();
1244 nodes.push_back(parent);
1246 bool close_on_remove =
1247 (parent == model_->other_node()) && (parent->child_count() == 1);
1249 context_menu_.reset(new BookmarkContextMenu(
1250 GetWidget(), browser_, browser_->profile(),
1251 browser_->tab_strip_model()->GetActiveWebContents(),
1252 parent, nodes, close_on_remove));
1253 context_menu_->RunMenuAt(point, source_type);
1256 void BookmarkBarView::Init() {
1257 // Note that at this point we're not in a hierarchy so GetThemeProvider() will
1258 // return NULL. When we're inserted into a hierarchy, we'll call
1259 // UpdateColors(), which will set the appropriate colors for all the objects
1260 // added in this function.
1262 // Child views are traversed in the order they are added. Make sure the order
1263 // they are added matches the visual order.
1264 overflow_button_ = CreateOverflowButton();
1265 AddChildView(overflow_button_);
1267 other_bookmarked_button_ = CreateOtherBookmarkedButton();
1268 // We'll re-enable when the model is loaded.
1269 other_bookmarked_button_->SetEnabled(false);
1270 AddChildView(other_bookmarked_button_);
1272 apps_page_shortcut_ = CreateAppsPageShortcutButton();
1273 AddChildView(apps_page_shortcut_);
1274 profile_pref_registrar_.Init(browser_->profile()->GetPrefs());
1275 profile_pref_registrar_.Add(
1276 prefs::kShowAppsShortcutInBookmarkBar,
1277 base::Bind(&BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged,
1278 base::Unretained(this)));
1279 apps_page_shortcut_->SetVisible(
1280 chrome::ShouldShowAppsShortcutInBookmarkBar(
1281 browser_->profile(), browser_->host_desktop_type()));
1283 bookmarks_separator_view_ = new ButtonSeparatorView();
1284 AddChildView(bookmarks_separator_view_);
1285 UpdateBookmarksSeparatorVisibility();
1287 instructions_ = new BookmarkBarInstructionsView(this);
1288 AddChildView(instructions_);
1290 set_context_menu_controller(this);
1292 size_animation_.reset(new gfx::SlideAnimation(this));
1294 model_ = BookmarkModelFactory::GetForProfile(browser_->profile());
1295 if (model_) {
1296 model_->AddObserver(this);
1297 if (model_->loaded())
1298 BookmarkModelLoaded(model_, false);
1299 // else case: we'll receive notification back from the BookmarkModel when
1300 // done loading, then we'll populate the bar.
1304 int BookmarkBarView::GetBookmarkButtonCount() {
1305 // We contain four non-bookmark button views: other bookmarks, bookmarks
1306 // separator, chevrons (for overflow), apps page, and the instruction label.
1307 return child_count() - 5;
1310 views::TextButton* BookmarkBarView::GetBookmarkButton(int index) {
1311 DCHECK(index >= 0 && index < GetBookmarkButtonCount());
1312 return static_cast<views::TextButton*>(child_at(index));
1315 BookmarkLaunchLocation BookmarkBarView::GetBookmarkLaunchLocation() const {
1316 return IsDetached() ? BOOKMARK_LAUNCH_LOCATION_DETACHED_BAR :
1317 BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR;
1320 int BookmarkBarView::GetFirstHiddenNodeIndex() {
1321 const int bb_count = GetBookmarkButtonCount();
1322 for (int i = 0; i < bb_count; ++i) {
1323 if (!GetBookmarkButton(i)->visible())
1324 return i;
1326 return bb_count;
1329 MenuButton* BookmarkBarView::CreateOtherBookmarkedButton() {
1330 // Title is set in Loaded.
1331 MenuButton* button =
1332 new BookmarkFolderButton(this, base::string16(), this, false);
1333 button->set_id(VIEW_ID_OTHER_BOOKMARKS);
1334 button->SetIcon(GetFolderIcon());
1335 button->set_context_menu_controller(this);
1336 button->set_tag(kOtherFolderButtonTag);
1337 return button;
1340 MenuButton* BookmarkBarView::CreateOverflowButton() {
1341 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1342 MenuButton* button = new OverFlowButton(this);
1343 button->SetIcon(*rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_CHEVRONS));
1345 // The overflow button's image contains an arrow and therefore it is a
1346 // direction sensitive image and we need to flip it if the UI layout is
1347 // right-to-left.
1349 // By default, menu buttons are not flipped because they generally contain
1350 // text and flipping the gfx::Canvas object will break text rendering. Since
1351 // the overflow button does not contain text, we can safely flip it.
1352 button->EnableCanvasFlippingForRTLUI(true);
1354 // Make visible as necessary.
1355 button->SetVisible(false);
1356 // Set accessibility name.
1357 button->SetAccessibleName(
1358 l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS_CHEVRON));
1359 return button;
1362 views::View* BookmarkBarView::CreateBookmarkButton(const BookmarkNode* node) {
1363 if (node->is_url()) {
1364 BookmarkButton* button = new BookmarkButton(
1365 this, node->url(), node->GetTitle(), browser_->profile());
1366 ConfigureButton(node, button);
1367 return button;
1368 } else {
1369 views::MenuButton* button = new BookmarkFolderButton(
1370 this, node->GetTitle(), this, false);
1371 button->SetIcon(GetFolderIcon());
1372 ConfigureButton(node, button);
1373 return button;
1377 views::TextButton* BookmarkBarView::CreateAppsPageShortcutButton() {
1378 views::TextButton* button = new ShortcutButton(
1379 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME));
1380 button->SetTooltipText(l10n_util::GetStringUTF16(
1381 IDS_BOOKMARK_BAR_APPS_SHORTCUT_TOOLTIP));
1382 button->set_id(VIEW_ID_BOOKMARK_BAR_ELEMENT);
1383 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1384 button->SetIcon(*rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_APPS_SHORTCUT));
1385 button->set_context_menu_controller(this);
1386 button->set_tag(kAppsShortcutButtonTag);
1387 return button;
1390 void BookmarkBarView::ConfigureButton(const BookmarkNode* node,
1391 views::TextButton* button) {
1392 button->SetText(node->GetTitle());
1393 button->SetAccessibleName(node->GetTitle());
1394 button->set_id(VIEW_ID_BOOKMARK_BAR_ELEMENT);
1395 // We don't always have a theme provider (ui tests, for example).
1396 if (GetThemeProvider()) {
1397 button->SetEnabledColor(GetThemeProvider()->GetColor(
1398 ThemeProperties::COLOR_BOOKMARK_TEXT));
1401 button->ClearMaxTextSize();
1402 button->set_context_menu_controller(this);
1403 button->set_drag_controller(this);
1404 if (node->is_url()) {
1405 const gfx::Image& favicon = model_->GetFavicon(node);
1406 if (!favicon.IsEmpty())
1407 button->SetIcon(*favicon.ToImageSkia());
1408 else
1409 button->SetIcon(GetDefaultFavicon());
1411 button->set_max_width(kMaxButtonWidth);
1414 void BookmarkBarView::BookmarkNodeAddedImpl(BookmarkModel* model,
1415 const BookmarkNode* parent,
1416 int index) {
1417 UpdateOtherBookmarksVisibility();
1418 if (parent != model_->bookmark_bar_node()) {
1419 // We only care about nodes on the bookmark bar.
1420 return;
1422 DCHECK(index >= 0 && index <= GetBookmarkButtonCount());
1423 const BookmarkNode* node = parent->GetChild(index);
1424 ProfileSyncService* sync_service(ProfileSyncServiceFactory::
1425 GetInstance()->GetForProfile(browser_->profile()));
1426 if (!throbbing_view_ && sync_service && sync_service->FirstSetupInProgress())
1427 StartThrobbing(node, true);
1428 AddChildViewAt(CreateBookmarkButton(node), index);
1429 UpdateColors();
1430 Layout();
1431 SchedulePaint();
1434 void BookmarkBarView::BookmarkNodeRemovedImpl(BookmarkModel* model,
1435 const BookmarkNode* parent,
1436 int index) {
1437 UpdateOtherBookmarksVisibility();
1439 StopThrobbing(true);
1440 // No need to start throbbing again as the bookmark bubble can't be up at
1441 // the same time as the user reorders.
1443 if (parent != model_->bookmark_bar_node()) {
1444 // We only care about nodes on the bookmark bar.
1445 return;
1447 DCHECK(index >= 0 && index < GetBookmarkButtonCount());
1448 views::View* button = child_at(index);
1449 RemoveChildView(button);
1450 base::MessageLoop::current()->DeleteSoon(FROM_HERE, button);
1451 Layout();
1452 SchedulePaint();
1455 void BookmarkBarView::BookmarkNodeChangedImpl(BookmarkModel* model,
1456 const BookmarkNode* node) {
1457 if (node->parent() != model_->bookmark_bar_node()) {
1458 // We only care about nodes on the bookmark bar.
1459 return;
1461 int index = model_->bookmark_bar_node()->GetIndexOf(node);
1462 DCHECK_NE(-1, index);
1463 views::TextButton* button = GetBookmarkButton(index);
1464 gfx::Size old_pref = button->GetPreferredSize();
1465 ConfigureButton(node, button);
1466 gfx::Size new_pref = button->GetPreferredSize();
1467 if (old_pref.width() != new_pref.width()) {
1468 Layout();
1469 SchedulePaint();
1470 } else if (button->visible()) {
1471 button->SchedulePaint();
1475 void BookmarkBarView::ShowDropFolderForNode(const BookmarkNode* node) {
1476 if (bookmark_drop_menu_) {
1477 if (bookmark_drop_menu_->node() == node) {
1478 // Already showing for the specified node.
1479 return;
1481 bookmark_drop_menu_->Cancel();
1484 views::MenuButton* menu_button = GetMenuButtonForNode(node);
1485 if (!menu_button)
1486 return;
1488 int start_index = 0;
1489 if (node == model_->bookmark_bar_node())
1490 start_index = GetFirstHiddenNodeIndex();
1492 drop_info_->is_menu_showing = true;
1493 bookmark_drop_menu_ = new BookmarkMenuController(browser_,
1494 page_navigator_, GetWidget(), node, start_index);
1495 bookmark_drop_menu_->set_observer(this);
1496 bookmark_drop_menu_->RunMenuAt(this, true);
1499 void BookmarkBarView::StopShowFolderDropMenuTimer() {
1500 show_folder_method_factory_.InvalidateWeakPtrs();
1503 void BookmarkBarView::StartShowFolderDropMenuTimer(const BookmarkNode* node) {
1504 if (!animations_enabled) {
1505 // So that tests can run as fast as possible disable the delay during
1506 // testing.
1507 ShowDropFolderForNode(node);
1508 return;
1510 show_folder_method_factory_.InvalidateWeakPtrs();
1511 base::MessageLoop::current()->PostDelayedTask(
1512 FROM_HERE,
1513 base::Bind(&BookmarkBarView::ShowDropFolderForNode,
1514 show_folder_method_factory_.GetWeakPtr(),
1515 node),
1516 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
1519 void BookmarkBarView::CalculateDropLocation(const DropTargetEvent& event,
1520 const BookmarkNodeData& data,
1521 DropLocation* location) {
1522 DCHECK(model_);
1523 DCHECK(model_->loaded());
1524 DCHECK(data.is_valid());
1526 *location = DropLocation();
1528 // The drop event uses the screen coordinates while the child Views are
1529 // always laid out from left to right (even though they are rendered from
1530 // right-to-left on RTL locales). Thus, in order to make sure the drop
1531 // coordinates calculation works, we mirror the event's X coordinate if the
1532 // locale is RTL.
1533 int mirrored_x = GetMirroredXInView(event.x());
1535 bool found = false;
1536 const int other_delta_x = mirrored_x - other_bookmarked_button_->x();
1537 Profile* profile = browser_->profile();
1538 if (other_bookmarked_button_->visible() && other_delta_x >= 0 &&
1539 other_delta_x < other_bookmarked_button_->width()) {
1540 // Mouse is over 'other' folder.
1541 location->button_type = DROP_OTHER_FOLDER;
1542 location->on = true;
1543 found = true;
1544 } else if (!GetBookmarkButtonCount()) {
1545 // No bookmarks, accept the drop.
1546 location->index = 0;
1547 int ops = data.GetFirstNode(profile) ? ui::DragDropTypes::DRAG_MOVE :
1548 ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
1549 location->operation = chrome::GetPreferredBookmarkDropOperation(
1550 event.source_operations(), ops);
1551 return;
1554 for (int i = 0; i < GetBookmarkButtonCount() &&
1555 GetBookmarkButton(i)->visible() && !found; i++) {
1556 views::TextButton* button = GetBookmarkButton(i);
1557 int button_x = mirrored_x - button->x();
1558 int button_w = button->width();
1559 if (button_x < button_w) {
1560 found = true;
1561 const BookmarkNode* node = model_->bookmark_bar_node()->GetChild(i);
1562 if (node->is_folder()) {
1563 if (button_x <= views::kDropBetweenPixels) {
1564 location->index = i;
1565 } else if (button_x < button_w - views::kDropBetweenPixels) {
1566 location->index = i;
1567 location->on = true;
1568 } else {
1569 location->index = i + 1;
1571 } else if (button_x < button_w / 2) {
1572 location->index = i;
1573 } else {
1574 location->index = i + 1;
1576 break;
1580 if (!found) {
1581 if (overflow_button_->visible()) {
1582 // Are we over the overflow button?
1583 int overflow_delta_x = mirrored_x - overflow_button_->x();
1584 if (overflow_delta_x >= 0 &&
1585 overflow_delta_x < overflow_button_->width()) {
1586 // Mouse is over overflow button.
1587 location->index = GetFirstHiddenNodeIndex();
1588 location->button_type = DROP_OVERFLOW;
1589 } else if (overflow_delta_x < 0) {
1590 // Mouse is after the last visible button but before overflow button;
1591 // use the last visible index.
1592 location->index = GetFirstHiddenNodeIndex();
1593 } else {
1594 return;
1596 } else if (!other_bookmarked_button_->visible() ||
1597 mirrored_x < other_bookmarked_button_->x()) {
1598 // Mouse is after the last visible button but before more recently
1599 // bookmarked; use the last visible index.
1600 location->index = GetFirstHiddenNodeIndex();
1601 } else {
1602 return;
1606 if (location->on) {
1607 const BookmarkNode* parent = (location->button_type == DROP_OTHER_FOLDER) ?
1608 model_->other_node() :
1609 model_->bookmark_bar_node()->GetChild(location->index);
1610 location->operation = chrome::GetBookmarkDropOperation(
1611 profile, event, data, parent, parent->child_count());
1612 if (!location->operation && !data.has_single_url() &&
1613 data.GetFirstNode(profile) == parent) {
1614 // Don't open a menu if the node being dragged is the menu to open.
1615 location->on = false;
1617 } else {
1618 location->operation = chrome::GetBookmarkDropOperation(profile, event,
1619 data, model_->bookmark_bar_node(), location->index);
1623 void BookmarkBarView::WriteBookmarkDragData(const BookmarkNode* node,
1624 ui::OSExchangeData* data) {
1625 DCHECK(node && data);
1626 BookmarkNodeData drag_data(node);
1627 drag_data.Write(browser_->profile(), data);
1630 void BookmarkBarView::StartThrobbing(const BookmarkNode* node,
1631 bool overflow_only) {
1632 DCHECK(!throbbing_view_);
1634 // Determine which visible button is showing the bookmark (or is an ancestor
1635 // of the bookmark).
1636 const BookmarkNode* bbn = model_->bookmark_bar_node();
1637 const BookmarkNode* parent_on_bb = node;
1638 while (parent_on_bb) {
1639 const BookmarkNode* parent = parent_on_bb->parent();
1640 if (parent == bbn)
1641 break;
1642 parent_on_bb = parent;
1644 if (parent_on_bb) {
1645 int index = bbn->GetIndexOf(parent_on_bb);
1646 if (index >= GetFirstHiddenNodeIndex()) {
1647 // Node is hidden, animate the overflow button.
1648 throbbing_view_ = overflow_button_;
1649 } else if (!overflow_only) {
1650 throbbing_view_ = static_cast<CustomButton*>(child_at(index));
1652 } else if (!overflow_only) {
1653 throbbing_view_ = other_bookmarked_button_;
1656 // Use a large number so that the button continues to throb.
1657 if (throbbing_view_)
1658 throbbing_view_->StartThrobbing(std::numeric_limits<int>::max());
1661 views::CustomButton* BookmarkBarView::DetermineViewToThrobFromRemove(
1662 const BookmarkNode* parent,
1663 int old_index) {
1664 const BookmarkNode* bbn = model_->bookmark_bar_node();
1665 const BookmarkNode* old_node = parent;
1666 int old_index_on_bb = old_index;
1667 while (old_node && old_node != bbn) {
1668 const BookmarkNode* parent = old_node->parent();
1669 if (parent == bbn) {
1670 old_index_on_bb = bbn->GetIndexOf(old_node);
1671 break;
1673 old_node = parent;
1675 if (old_node) {
1676 if (old_index_on_bb >= GetFirstHiddenNodeIndex()) {
1677 // Node is hidden, animate the overflow button.
1678 return overflow_button_;
1680 return static_cast<CustomButton*>(child_at(old_index_on_bb));
1682 // Node wasn't on the bookmark bar, use the other bookmark button.
1683 return other_bookmarked_button_;
1686 void BookmarkBarView::UpdateColors() {
1687 // We don't always have a theme provider (ui tests, for example).
1688 const ui::ThemeProvider* theme_provider = GetThemeProvider();
1689 if (!theme_provider)
1690 return;
1691 SkColor text_color =
1692 theme_provider->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
1693 for (int i = 0; i < GetBookmarkButtonCount(); ++i)
1694 GetBookmarkButton(i)->SetEnabledColor(text_color);
1695 other_bookmarked_button()->SetEnabledColor(text_color);
1696 if (apps_page_shortcut_->visible())
1697 apps_page_shortcut_->SetEnabledColor(text_color);
1700 void BookmarkBarView::UpdateOtherBookmarksVisibility() {
1701 bool has_other_children = !model_->other_node()->empty();
1702 if (has_other_children == other_bookmarked_button_->visible())
1703 return;
1704 other_bookmarked_button_->SetVisible(has_other_children);
1705 UpdateBookmarksSeparatorVisibility();
1706 Layout();
1707 SchedulePaint();
1710 void BookmarkBarView::UpdateBookmarksSeparatorVisibility() {
1711 // Ash does not paint the bookmarks separator line because it looks odd on
1712 // the flat background. We keep it present for layout, but don't draw it.
1713 bookmarks_separator_view_->SetVisible(
1714 browser_->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH &&
1715 other_bookmarked_button_->visible());
1718 gfx::Size BookmarkBarView::LayoutItems(bool compute_bounds_only) {
1719 gfx::Size prefsize;
1720 if (!parent() && !compute_bounds_only)
1721 return prefsize;
1723 int x = kLeftMargin;
1724 int top_margin = IsDetached() ? kDetachedTopMargin : 0;
1725 int y = top_margin;
1726 int width = View::width() - kRightMargin - kLeftMargin;
1727 int height = chrome::kBookmarkBarHeight - kBottomMargin;
1728 int separator_margin = kSeparatorMargin;
1730 if (IsDetached()) {
1731 double current_state = 1 - size_animation_->GetCurrentValue();
1732 x += static_cast<int>(kNewtabHorizontalPadding * current_state);
1733 y += (View::height() - chrome::kBookmarkBarHeight) / 2;
1734 width -= static_cast<int>(kNewtabHorizontalPadding * current_state);
1735 separator_margin -= static_cast<int>(kSeparatorMargin * current_state);
1736 } else {
1737 // For the attached appearance, pin the content to the bottom of the bar
1738 // when animating in/out, as shrinking its height instead looks weird. This
1739 // also matches how we layout infobars.
1740 y += View::height() - chrome::kBookmarkBarHeight;
1743 gfx::Size other_bookmarked_pref = other_bookmarked_button_->visible() ?
1744 other_bookmarked_button_->GetPreferredSize() : gfx::Size();
1745 gfx::Size overflow_pref = overflow_button_->GetPreferredSize();
1746 gfx::Size bookmarks_separator_pref =
1747 bookmarks_separator_view_->GetPreferredSize();
1748 gfx::Size apps_page_shortcut_pref = apps_page_shortcut_->visible() ?
1749 apps_page_shortcut_->GetPreferredSize() : gfx::Size();
1751 int max_x = width - overflow_pref.width() - kButtonPadding -
1752 bookmarks_separator_pref.width();
1753 if (other_bookmarked_button_->visible())
1754 max_x -= other_bookmarked_pref.width() + kButtonPadding;
1756 // Next, layout out the buttons. Any buttons that are placed beyond the
1757 // visible region and made invisible.
1759 // Start with the apps page shortcut button.
1760 if (apps_page_shortcut_->visible()) {
1761 if (!compute_bounds_only) {
1762 apps_page_shortcut_->SetBounds(x, y, apps_page_shortcut_pref.width(),
1763 height);
1765 x += apps_page_shortcut_pref.width() + kButtonPadding;
1768 // Then go through the bookmark buttons.
1769 if (GetBookmarkButtonCount() == 0 && model_ && model_->loaded()) {
1770 gfx::Size pref = instructions_->GetPreferredSize();
1771 if (!compute_bounds_only) {
1772 instructions_->SetBounds(
1773 x + kInstructionsPadding, y,
1774 std::min(static_cast<int>(pref.width()),
1775 max_x - x),
1776 height);
1777 instructions_->SetVisible(true);
1779 } else {
1780 if (!compute_bounds_only)
1781 instructions_->SetVisible(false);
1783 for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
1784 views::View* child = child_at(i);
1785 gfx::Size pref = child->GetPreferredSize();
1786 int next_x = x + pref.width() + kButtonPadding;
1787 if (!compute_bounds_only) {
1788 child->SetVisible(next_x < max_x);
1789 child->SetBounds(x, y, pref.width(), height);
1791 x = next_x;
1795 // Layout the right side of the bar.
1796 const bool all_visible = (GetBookmarkButtonCount() == 0 ||
1797 child_at(GetBookmarkButtonCount() - 1)->visible());
1799 // Layout the right side buttons.
1800 if (!compute_bounds_only)
1801 x = max_x + kButtonPadding;
1802 else
1803 x += kButtonPadding;
1805 // The overflow button.
1806 if (!compute_bounds_only) {
1807 overflow_button_->SetBounds(x, y, overflow_pref.width(), height);
1808 overflow_button_->SetVisible(!all_visible);
1810 x += overflow_pref.width();
1812 // Separator.
1813 if (bookmarks_separator_view_->visible()) {
1814 if (!compute_bounds_only) {
1815 bookmarks_separator_view_->SetBounds(x,
1816 y - top_margin,
1817 bookmarks_separator_pref.width(),
1818 height + top_margin + kBottomMargin -
1819 separator_margin);
1822 x += bookmarks_separator_pref.width();
1825 // The other bookmarks button.
1826 if (other_bookmarked_button_->visible()) {
1827 if (!compute_bounds_only) {
1828 other_bookmarked_button_->SetBounds(x, y, other_bookmarked_pref.width(),
1829 height);
1831 x += other_bookmarked_pref.width() + kButtonPadding;
1834 // Set the preferred size computed so far.
1835 if (compute_bounds_only) {
1836 x += kRightMargin;
1837 prefsize.set_width(x);
1838 if (IsDetached()) {
1839 x += static_cast<int>(kNewtabHorizontalPadding *
1840 (1 - size_animation_->GetCurrentValue()));
1841 prefsize.set_height(
1842 chrome::kBookmarkBarHeight +
1843 static_cast<int>(
1844 (chrome::kNTPBookmarkBarHeight - chrome::kBookmarkBarHeight) *
1845 (1 - size_animation_->GetCurrentValue())));
1846 } else {
1847 prefsize.set_height(static_cast<int>(chrome::kBookmarkBarHeight *
1848 size_animation_->GetCurrentValue()));
1851 return prefsize;
1854 void BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged() {
1855 DCHECK(apps_page_shortcut_);
1856 // Only perform layout if required.
1857 bool visible = chrome::ShouldShowAppsShortcutInBookmarkBar(
1858 browser_->profile(), browser_->host_desktop_type());
1859 if (apps_page_shortcut_->visible() == visible)
1860 return;
1861 apps_page_shortcut_->SetVisible(visible);
1862 UpdateBookmarksSeparatorVisibility();
1863 Layout();