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