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