bookmark_bar_view.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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.h" 19#include "chrome/browser/bookmarks/bookmark_model_factory.h" 20#include "chrome/browser/browser_process.h" 21#include "chrome/browser/chrome_notification_types.h" 22#include "chrome/browser/defaults.h" 23#include "chrome/browser/extensions/extension_service.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/search/search.h" 26#include "chrome/browser/sync/profile_sync_service.h" 27#include "chrome/browser/sync/profile_sync_service_factory.h" 28#include "chrome/browser/themes/theme_properties.h" 29#include "chrome/browser/ui/bookmarks/bookmark_bar_constants.h" 30#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" 31#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h" 32#include "chrome/browser/ui/bookmarks/bookmark_utils.h" 33#include "chrome/browser/ui/browser.h" 34#include "chrome/browser/ui/chrome_pages.h" 35#include "chrome/browser/ui/elide_url.h" 36#include "chrome/browser/ui/omnibox/omnibox_popup_model.h" 37#include "chrome/browser/ui/omnibox/omnibox_view.h" 38#include "chrome/browser/ui/tabs/tab_strip_model.h" 39#include "chrome/browser/ui/view_ids.h" 40#include "chrome/browser/ui/views/bookmarks/bookmark_bar_instructions_view.h" 41#include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h" 42#include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h" 43#include "chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h" 44#include "chrome/browser/ui/views/event_utils.h" 45#include "chrome/browser/ui/views/frame/browser_view.h" 46#include "chrome/browser/ui/views/location_bar/location_bar_view.h" 47#include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h" 48#include "chrome/common/chrome_switches.h" 49#include "chrome/common/extensions/extension_constants.h" 50#include "chrome/common/pref_names.h" 51#include "chrome/common/url_constants.h" 52#include "content/public/browser/notification_details.h" 53#include "content/public/browser/notification_source.h" 54#include "content/public/browser/page_navigator.h" 55#include "content/public/browser/render_view_host.h" 56#include "content/public/browser/render_widget_host_view.h" 57#include "content/public/browser/user_metrics.h" 58#include "content/public/browser/web_contents.h" 59#include "content/public/common/page_transition_types.h" 60#include "grit/generated_resources.h" 61#include "grit/theme_resources.h" 62#include "grit/ui_resources.h" 63#include "ui/accessibility/ax_view_state.h" 64#include "ui/base/dragdrop/drag_utils.h" 65#include "ui/base/dragdrop/os_exchange_data.h" 66#include "ui/base/l10n/l10n_util.h" 67#include "ui/base/resource/resource_bundle.h" 68#include "ui/base/theme_provider.h" 69#include "ui/base/window_open_disposition.h" 70#include "ui/gfx/animation/slide_animation.h" 71#include "ui/gfx/canvas.h" 72#include "ui/gfx/text_elider.h" 73#include "ui/views/button_drag_utils.h" 74#include "ui/views/controls/button/menu_button.h" 75#include "ui/views/controls/label.h" 76#include "ui/views/controls/menu/menu_item_view.h" 77#include "ui/views/drag_utils.h" 78#include "ui/views/metrics.h" 79#include "ui/views/view_constants.h" 80#include "ui/views/widget/tooltip_manager.h" 81#include "ui/views/widget/widget.h" 82#include "ui/views/window/non_client_view.h" 83 84using base::UserMetricsAction; 85using content::OpenURLParams; 86using content::PageNavigator; 87using content::Referrer; 88using ui::DropTargetEvent; 89using views::CustomButton; 90using views::MenuButton; 91using views::MenuItemView; 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 DCHECK(profile->GetExtensionService()); 315 const extensions::Extension* extension = 316 profile->GetExtensionService()->GetInstalledApp(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() 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 MenuItemView::AnchorPosition* anchor) { 569 if (button == other_bookmarked_button_ || button == overflow_button_) 570 *anchor = MenuItemView::TOPRIGHT; 571 else 572 *anchor = MenuItemView::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(ElideText(localized_title, tt_fonts, max_width, 615 gfx::ELIDE_AT_END)); 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() { 668 return LayoutItems(true); 669} 670 671bool BookmarkBarView::HitTestRect(const gfx::Rect& rect) const { 672 // If bookmark bar is attached and omnibox popup is open (on top of the bar), 673 // force hit-testing to fail. This prevents hovers/clicks just above the 674 // omnibox popup from activating the top few pixels of items on the bookmark 675 // bar. 676 if (!IsDetached() && browser_view_ && 677 browser_view_->GetLocationBar()->GetOmniboxView()->model()-> 678 popup_model()->IsOpen()) { 679 return false; 680 } 681 return DetachableToolbarView::HitTestRect(rect); 682} 683 684gfx::Size BookmarkBarView::GetMinimumSize() { 685 // The minimum width of the bookmark bar should at least contain the overflow 686 // button, by which one can access all the Bookmark Bar items, and the "Other 687 // Bookmarks" folder, along with appropriate margins and button padding. 688 int width = kLeftMargin; 689 690 int height = chrome::kBookmarkBarHeight; 691 if (IsDetached()) { 692 double current_state = 1 - size_animation_->GetCurrentValue(); 693 width += 2 * static_cast<int>(kNewtabHorizontalPadding * current_state); 694 height += static_cast<int>( 695 (chrome::kNTPBookmarkBarHeight - chrome::kBookmarkBarHeight) * 696 current_state); 697 } 698 699 gfx::Size other_bookmarked_pref; 700 if (other_bookmarked_button_->visible()) 701 other_bookmarked_pref = other_bookmarked_button_->GetPreferredSize(); 702 gfx::Size overflow_pref; 703 if (overflow_button_->visible()) 704 overflow_pref = overflow_button_->GetPreferredSize(); 705 gfx::Size bookmarks_separator_pref; 706 if (bookmarks_separator_view_->visible()) 707 bookmarks_separator_pref = bookmarks_separator_view_->GetPreferredSize(); 708 709 gfx::Size apps_page_shortcut_pref; 710 if (apps_page_shortcut_->visible()) 711 apps_page_shortcut_pref = apps_page_shortcut_->GetPreferredSize(); 712 width += other_bookmarked_pref.width() + kButtonPadding + 713 apps_page_shortcut_pref.width() + kButtonPadding + 714 overflow_pref.width() + kButtonPadding + 715 bookmarks_separator_pref.width(); 716 717 return gfx::Size(width, height); 718} 719 720void BookmarkBarView::Layout() { 721 LayoutItems(false); 722} 723 724void BookmarkBarView::ViewHierarchyChanged( 725 const ViewHierarchyChangedDetails& details) { 726 if (details.is_add && details.child == this) { 727 // We may get inserted into a hierarchy with a profile - this typically 728 // occurs when the bar's contents get populated fast enough that the 729 // buttons are created before the bar is attached to a frame. 730 UpdateColors(); 731 732 if (height() > 0) { 733 // We only layout while parented. When we become parented, if our bounds 734 // haven't changed, OnBoundsChanged() won't get invoked and we won't 735 // layout. Therefore we always force a layout when added. 736 Layout(); 737 } 738 } 739} 740 741void BookmarkBarView::PaintChildren(gfx::Canvas* canvas) { 742 View::PaintChildren(canvas); 743 744 if (drop_info_.get() && drop_info_->valid && 745 drop_info_->location.operation != 0 && drop_info_->location.index != -1 && 746 drop_info_->location.button_type != DROP_OVERFLOW && 747 !drop_info_->location.on) { 748 int index = drop_info_->location.index; 749 DCHECK(index <= GetBookmarkButtonCount()); 750 int x = 0; 751 int y = 0; 752 int h = height(); 753 if (index == GetBookmarkButtonCount()) { 754 if (index == 0) { 755 x = kLeftMargin; 756 } else { 757 x = GetBookmarkButton(index - 1)->x() + 758 GetBookmarkButton(index - 1)->width(); 759 } 760 } else { 761 x = GetBookmarkButton(index)->x(); 762 } 763 if (GetBookmarkButtonCount() > 0 && GetBookmarkButton(0)->visible()) { 764 y = GetBookmarkButton(0)->y(); 765 h = GetBookmarkButton(0)->height(); 766 } 767 768 // Since the drop indicator is painted directly onto the canvas, we must 769 // make sure it is painted in the right location if the locale is RTL. 770 gfx::Rect indicator_bounds(x - kDropIndicatorWidth / 2, 771 y, 772 kDropIndicatorWidth, 773 h); 774 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds)); 775 776 // TODO(sky/glen): make me pretty! 777 canvas->FillRect(indicator_bounds, kDropIndicatorColor); 778 } 779} 780 781bool BookmarkBarView::GetDropFormats( 782 int* formats, 783 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) { 784 if (!model_ || !model_->loaded()) 785 return false; 786 *formats = ui::OSExchangeData::URL; 787 custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat()); 788 return true; 789} 790 791bool BookmarkBarView::AreDropTypesRequired() { 792 return true; 793} 794 795bool BookmarkBarView::CanDrop(const ui::OSExchangeData& data) { 796 if (!model_ || !model_->loaded() || 797 !browser_->profile()->GetPrefs()->GetBoolean( 798 prefs::kEditBookmarksEnabled)) 799 return false; 800 801 if (!drop_info_.get()) 802 drop_info_.reset(new DropInfo()); 803 804 // Only accept drops of 1 node, which is the case for all data dragged from 805 // bookmark bar and menus. 806 return drop_info_->data.Read(data) && drop_info_->data.size() == 1; 807} 808 809void BookmarkBarView::OnDragEntered(const DropTargetEvent& event) { 810} 811 812int BookmarkBarView::OnDragUpdated(const DropTargetEvent& event) { 813 if (!drop_info_.get()) 814 return 0; 815 816 if (drop_info_->valid && 817 (drop_info_->x == event.x() && drop_info_->y == event.y())) { 818 // The location of the mouse didn't change, return the last operation. 819 return drop_info_->location.operation; 820 } 821 822 drop_info_->x = event.x(); 823 drop_info_->y = event.y(); 824 825 DropLocation location; 826 CalculateDropLocation(event, drop_info_->data, &location); 827 828 if (drop_info_->valid && drop_info_->location.Equals(location)) { 829 // The position we're going to drop didn't change, return the last drag 830 // operation we calculated. Copy of the operation in case it changed. 831 drop_info_->location.operation = location.operation; 832 return drop_info_->location.operation; 833 } 834 835 StopShowFolderDropMenuTimer(); 836 837 // TODO(sky): Optimize paint region. 838 SchedulePaint(); 839 840 drop_info_->location = location; 841 drop_info_->valid = true; 842 843 if (drop_info_->is_menu_showing) { 844 if (bookmark_drop_menu_) 845 bookmark_drop_menu_->Cancel(); 846 drop_info_->is_menu_showing = false; 847 } 848 849 if (location.on || location.button_type == DROP_OVERFLOW || 850 location.button_type == DROP_OTHER_FOLDER) { 851 const BookmarkNode* node; 852 if (location.button_type == DROP_OTHER_FOLDER) 853 node = model_->other_node(); 854 else if (location.button_type == DROP_OVERFLOW) 855 node = model_->bookmark_bar_node(); 856 else 857 node = model_->bookmark_bar_node()->GetChild(location.index); 858 StartShowFolderDropMenuTimer(node); 859 } 860 861 return drop_info_->location.operation; 862} 863 864void BookmarkBarView::OnDragExited() { 865 StopShowFolderDropMenuTimer(); 866 867 // NOTE: we don't hide the menu on exit as it's possible the user moved the 868 // mouse over the menu, which triggers an exit on us. 869 870 drop_info_->valid = false; 871 872 if (drop_info_->location.index != -1) { 873 // TODO(sky): optimize the paint region. 874 SchedulePaint(); 875 } 876 drop_info_.reset(); 877} 878 879int BookmarkBarView::OnPerformDrop(const DropTargetEvent& event) { 880 StopShowFolderDropMenuTimer(); 881 882 if (bookmark_drop_menu_) 883 bookmark_drop_menu_->Cancel(); 884 885 if (!drop_info_.get() || !drop_info_->location.operation) 886 return ui::DragDropTypes::DRAG_NONE; 887 888 const BookmarkNode* root = 889 (drop_info_->location.button_type == DROP_OTHER_FOLDER) ? 890 model_->other_node() : model_->bookmark_bar_node(); 891 int index = drop_info_->location.index; 892 893 if (index != -1) { 894 // TODO(sky): optimize the SchedulePaint region. 895 SchedulePaint(); 896 } 897 const BookmarkNode* parent_node; 898 if (drop_info_->location.button_type == DROP_OTHER_FOLDER) { 899 parent_node = root; 900 index = parent_node->child_count(); 901 } else if (drop_info_->location.on) { 902 parent_node = root->GetChild(index); 903 index = parent_node->child_count(); 904 } else { 905 parent_node = root; 906 } 907 const BookmarkNodeData data = drop_info_->data; 908 DCHECK(data.is_valid()); 909 drop_info_.reset(); 910 return chrome::DropBookmarks(browser_->profile(), data, parent_node, index); 911} 912 913void BookmarkBarView::OnThemeChanged() { 914 UpdateColors(); 915} 916 917const char* BookmarkBarView::GetClassName() const { 918 return kViewClassName; 919} 920 921void BookmarkBarView::GetAccessibleState(ui::AXViewState* state) { 922 state->role = ui::AX_ROLE_TOOLBAR; 923 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS); 924} 925 926void BookmarkBarView::AnimationProgressed(const gfx::Animation* animation) { 927 // |browser_view_| can be NULL during tests. 928 if (browser_view_) 929 browser_view_->ToolbarSizeChanged(true); 930} 931 932void BookmarkBarView::AnimationEnded(const gfx::Animation* animation) { 933 // |browser_view_| can be NULL during tests. 934 if (browser_view_) { 935 browser_view_->ToolbarSizeChanged(false); 936 SchedulePaint(); 937 } 938} 939 940void BookmarkBarView::BookmarkMenuControllerDeleted( 941 BookmarkMenuController* controller) { 942 if (controller == bookmark_menu_) 943 bookmark_menu_ = NULL; 944 else if (controller == bookmark_drop_menu_) 945 bookmark_drop_menu_ = NULL; 946} 947 948void BookmarkBarView::ShowImportDialog() { 949 int64 install_time = 950 g_browser_process->local_state()->GetInt64(prefs::kInstallDate); 951 int64 time_from_install = base::Time::Now().ToTimeT() - install_time; 952 if (bookmark_bar_state_ == BookmarkBar::SHOW) { 953 UMA_HISTOGRAM_COUNTS("Import.ShowDialog.FromBookmarkBarView", 954 time_from_install); 955 } else if (bookmark_bar_state_ == BookmarkBar::DETACHED) { 956 UMA_HISTOGRAM_COUNTS("Import.ShowDialog.FromFloatingBookmarkBarView", 957 time_from_install); 958 } 959 960 chrome::ShowImportDialog(browser_); 961} 962 963void BookmarkBarView::OnBookmarkBubbleShown(const GURL& url) { 964 StopThrobbing(true); 965 const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url); 966 if (!node) 967 return; // Generally shouldn't happen. 968 StartThrobbing(node, false); 969} 970 971void BookmarkBarView::OnBookmarkBubbleHidden() { 972 StopThrobbing(false); 973} 974 975void BookmarkBarView::BookmarkModelLoaded(BookmarkModel* model, 976 bool ids_reassigned) { 977 // There should be no buttons. If non-zero it means Load was invoked more than 978 // once, or we didn't properly clear things. Either of which shouldn't happen. 979 DCHECK_EQ(0, GetBookmarkButtonCount()); 980 const BookmarkNode* node = model_->bookmark_bar_node(); 981 DCHECK(node); 982 // Create a button for each of the children on the bookmark bar. 983 for (int i = 0, child_count = node->child_count(); i < child_count; ++i) 984 AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i); 985 DCHECK(model_->other_node()); 986 other_bookmarked_button_->SetAccessibleName(model_->other_node()->GetTitle()); 987 other_bookmarked_button_->SetText(model_->other_node()->GetTitle()); 988 UpdateColors(); 989 UpdateOtherBookmarksVisibility(); 990 other_bookmarked_button_->SetEnabled(true); 991 992 Layout(); 993 SchedulePaint(); 994} 995 996void BookmarkBarView::BookmarkModelBeingDeleted(BookmarkModel* model) { 997 NOTREACHED(); 998 // Do minimal cleanup, presumably we'll be deleted shortly. 999 model_->RemoveObserver(this); 1000 model_ = NULL; 1001} 1002 1003void BookmarkBarView::BookmarkNodeMoved(BookmarkModel* model, 1004 const BookmarkNode* old_parent, 1005 int old_index, 1006 const BookmarkNode* new_parent, 1007 int new_index) { 1008 bool was_throbbing = throbbing_view_ && 1009 throbbing_view_ == DetermineViewToThrobFromRemove(old_parent, old_index); 1010 if (was_throbbing) 1011 throbbing_view_->StopThrobbing(); 1012 BookmarkNodeRemovedImpl(model, old_parent, old_index); 1013 BookmarkNodeAddedImpl(model, new_parent, new_index); 1014 if (was_throbbing) 1015 StartThrobbing(new_parent->GetChild(new_index), false); 1016} 1017 1018void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model, 1019 const BookmarkNode* parent, 1020 int index) { 1021 BookmarkNodeAddedImpl(model, parent, index); 1022} 1023 1024void BookmarkBarView::BookmarkNodeRemoved(BookmarkModel* model, 1025 const BookmarkNode* parent, 1026 int old_index, 1027 const BookmarkNode* node) { 1028 // Close the menu if the menu is showing for the deleted node. 1029 if (bookmark_menu_ && bookmark_menu_->node() == node) 1030 bookmark_menu_->Cancel(); 1031 BookmarkNodeRemovedImpl(model, parent, old_index); 1032} 1033 1034void BookmarkBarView::BookmarkAllNodesRemoved(BookmarkModel* model) { 1035 UpdateOtherBookmarksVisibility(); 1036 1037 StopThrobbing(true); 1038 1039 // Remove the existing buttons. 1040 while (GetBookmarkButtonCount()) { 1041 delete GetBookmarkButton(0); 1042 } 1043 1044 Layout(); 1045 SchedulePaint(); 1046} 1047 1048void BookmarkBarView::BookmarkNodeChanged(BookmarkModel* model, 1049 const BookmarkNode* node) { 1050 BookmarkNodeChangedImpl(model, node); 1051} 1052 1053void BookmarkBarView::BookmarkNodeChildrenReordered(BookmarkModel* model, 1054 const BookmarkNode* node) { 1055 if (node != model_->bookmark_bar_node()) 1056 return; // We only care about reordering of the bookmark bar node. 1057 1058 // Remove the existing buttons. 1059 while (GetBookmarkButtonCount()) { 1060 views::View* button = child_at(0); 1061 RemoveChildView(button); 1062 base::MessageLoop::current()->DeleteSoon(FROM_HERE, button); 1063 } 1064 1065 // Create the new buttons. 1066 for (int i = 0, child_count = node->child_count(); i < child_count; ++i) 1067 AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i); 1068 UpdateColors(); 1069 1070 Layout(); 1071 SchedulePaint(); 1072} 1073 1074void BookmarkBarView::BookmarkNodeFaviconChanged(BookmarkModel* model, 1075 const BookmarkNode* node) { 1076 BookmarkNodeChangedImpl(model, node); 1077} 1078 1079void BookmarkBarView::WriteDragDataForView(View* sender, 1080 const gfx::Point& press_pt, 1081 ui::OSExchangeData* data) { 1082 content::RecordAction(UserMetricsAction("BookmarkBar_DragButton")); 1083 1084 for (int i = 0; i < GetBookmarkButtonCount(); ++i) { 1085 if (sender == GetBookmarkButton(i)) { 1086 views::TextButton* button = GetBookmarkButton(i); 1087 scoped_ptr<gfx::Canvas> canvas( 1088 views::GetCanvasForDragImage(button->GetWidget(), button->size())); 1089 button->PaintButton(canvas.get(), views::TextButton::PB_FOR_DRAG); 1090 drag_utils::SetDragImageOnDataObject(*canvas, button->size(), 1091 press_pt.OffsetFromOrigin(), 1092 data); 1093 WriteBookmarkDragData(model_->bookmark_bar_node()->GetChild(i), data); 1094 return; 1095 } 1096 } 1097 NOTREACHED(); 1098} 1099 1100int BookmarkBarView::GetDragOperationsForView(View* sender, 1101 const gfx::Point& p) { 1102 if (size_animation_->is_animating() || 1103 (size_animation_->GetCurrentValue() == 0 && 1104 bookmark_bar_state_ != BookmarkBar::DETACHED)) { 1105 // Don't let the user drag while animating open or we're closed (and not 1106 // detached, when detached size_animation_ is always 0). This typically is 1107 // only hit if the user does something to inadvertently trigger DnD such as 1108 // pressing the mouse and hitting control-b. 1109 return ui::DragDropTypes::DRAG_NONE; 1110 } 1111 1112 for (int i = 0; i < GetBookmarkButtonCount(); ++i) { 1113 if (sender == GetBookmarkButton(i)) { 1114 return chrome::GetBookmarkDragOperation( 1115 browser_->profile(), model_->bookmark_bar_node()->GetChild(i)); 1116 } 1117 } 1118 NOTREACHED(); 1119 return ui::DragDropTypes::DRAG_NONE; 1120} 1121 1122bool BookmarkBarView::CanStartDragForView(views::View* sender, 1123 const gfx::Point& press_pt, 1124 const gfx::Point& p) { 1125 // Check if we have not moved enough horizontally but we have moved downward 1126 // vertically - downward drag. 1127 gfx::Vector2d move_offset = p - press_pt; 1128 gfx::Vector2d horizontal_offset(move_offset.x(), 0); 1129 if (!View::ExceededDragThreshold(horizontal_offset) && move_offset.y() > 0) { 1130 for (int i = 0; i < GetBookmarkButtonCount(); ++i) { 1131 if (sender == GetBookmarkButton(i)) { 1132 const BookmarkNode* node = model_->bookmark_bar_node()->GetChild(i); 1133 // If the folder button was dragged, show the menu instead. 1134 if (node && node->is_folder()) { 1135 views::MenuButton* menu_button = 1136 static_cast<views::MenuButton*>(sender); 1137 menu_button->Activate(); 1138 return false; 1139 } 1140 break; 1141 } 1142 } 1143 } 1144 return true; 1145} 1146 1147void BookmarkBarView::OnMenuButtonClicked(views::View* view, 1148 const gfx::Point& point) { 1149 const BookmarkNode* node; 1150 1151 int start_index = 0; 1152 if (view == other_bookmarked_button_) { 1153 node = model_->other_node(); 1154 } else if (view == overflow_button_) { 1155 node = model_->bookmark_bar_node(); 1156 start_index = GetFirstHiddenNodeIndex(); 1157 } else { 1158 int button_index = GetIndexOf(view); 1159 DCHECK_NE(-1, button_index); 1160 node = model_->bookmark_bar_node()->GetChild(button_index); 1161 } 1162 1163 RecordBookmarkFolderOpen(GetBookmarkLaunchLocation()); 1164 bookmark_menu_ = new BookmarkMenuController( 1165 browser_, page_navigator_, GetWidget(), node, start_index); 1166 bookmark_menu_->set_observer(this); 1167 bookmark_menu_->RunMenuAt(this, false); 1168} 1169 1170void BookmarkBarView::ButtonPressed(views::Button* sender, 1171 const ui::Event& event) { 1172 WindowOpenDisposition disposition_from_event_flags = 1173 ui::DispositionFromEventFlags(event.flags()); 1174 1175 if (sender->tag() == kAppsShortcutButtonTag) { 1176 OpenURLParams params(GURL(chrome::kChromeUIAppsURL), 1177 Referrer(), 1178 disposition_from_event_flags, 1179 content::PAGE_TRANSITION_AUTO_BOOKMARK, 1180 false); 1181 page_navigator_->OpenURL(params); 1182 RecordBookmarkAppsPageOpen(GetBookmarkLaunchLocation()); 1183 return; 1184 } 1185 1186 const BookmarkNode* node; 1187 if (sender->tag() == kOtherFolderButtonTag) { 1188 node = model_->other_node(); 1189 } else { 1190 int index = GetIndexOf(sender); 1191 DCHECK_NE(-1, index); 1192 node = model_->bookmark_bar_node()->GetChild(index); 1193 } 1194 DCHECK(page_navigator_); 1195 1196 if (node->is_url()) { 1197 RecordAppLaunch(browser_->profile(), node->url()); 1198 OpenURLParams params( 1199 node->url(), Referrer(), disposition_from_event_flags, 1200 content::PAGE_TRANSITION_AUTO_BOOKMARK, false); 1201 page_navigator_->OpenURL(params); 1202 } else { 1203 chrome::OpenAll(GetWidget()->GetNativeWindow(), page_navigator_, node, 1204 disposition_from_event_flags, browser_->profile()); 1205 } 1206 1207 RecordBookmarkLaunch(node, GetBookmarkLaunchLocation()); 1208} 1209 1210void BookmarkBarView::ShowContextMenuForView(views::View* source, 1211 const gfx::Point& point, 1212 ui::MenuSourceType source_type) { 1213 if (!model_->loaded()) { 1214 // Don't do anything if the model isn't loaded. 1215 return; 1216 } 1217 1218 const BookmarkNode* parent = NULL; 1219 std::vector<const BookmarkNode*> nodes; 1220 if (source == other_bookmarked_button_) { 1221 parent = model_->other_node(); 1222 // Do this so the user can open all bookmarks. BookmarkContextMenu makes 1223 // sure the user can't edit/delete the node in this case. 1224 nodes.push_back(parent); 1225 } else if (source != this && source != apps_page_shortcut_) { 1226 // User clicked on one of the bookmark buttons, find which one they 1227 // clicked on, except for the apps page shortcut, which must behave as if 1228 // the user clicked on the bookmark bar background. 1229 int bookmark_button_index = GetIndexOf(source); 1230 DCHECK(bookmark_button_index != -1 && 1231 bookmark_button_index < GetBookmarkButtonCount()); 1232 const BookmarkNode* node = 1233 model_->bookmark_bar_node()->GetChild(bookmark_button_index); 1234 nodes.push_back(node); 1235 parent = node->parent(); 1236 } else { 1237 parent = model_->bookmark_bar_node(); 1238 nodes.push_back(parent); 1239 } 1240 bool close_on_remove = 1241 (parent == model_->other_node()) && (parent->child_count() == 1); 1242 1243 context_menu_.reset(new BookmarkContextMenu( 1244 GetWidget(), browser_, browser_->profile(), 1245 browser_->tab_strip_model()->GetActiveWebContents(), 1246 parent, nodes, close_on_remove)); 1247 context_menu_->RunMenuAt(point, source_type); 1248} 1249 1250void BookmarkBarView::Init() { 1251 // Note that at this point we're not in a hierarchy so GetThemeProvider() will 1252 // return NULL. When we're inserted into a hierarchy, we'll call 1253 // UpdateColors(), which will set the appropriate colors for all the objects 1254 // added in this function. 1255 1256 // Child views are traversed in the order they are added. Make sure the order 1257 // they are added matches the visual order. 1258 overflow_button_ = CreateOverflowButton(); 1259 AddChildView(overflow_button_); 1260 1261 other_bookmarked_button_ = CreateOtherBookmarkedButton(); 1262 // We'll re-enable when the model is loaded. 1263 other_bookmarked_button_->SetEnabled(false); 1264 AddChildView(other_bookmarked_button_); 1265 1266 apps_page_shortcut_ = CreateAppsPageShortcutButton(); 1267 AddChildView(apps_page_shortcut_); 1268 profile_pref_registrar_.Init(browser_->profile()->GetPrefs()); 1269 profile_pref_registrar_.Add( 1270 prefs::kShowAppsShortcutInBookmarkBar, 1271 base::Bind(&BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged, 1272 base::Unretained(this))); 1273 apps_page_shortcut_->SetVisible( 1274 chrome::ShouldShowAppsShortcutInBookmarkBar( 1275 browser_->profile(), browser_->host_desktop_type())); 1276 1277 bookmarks_separator_view_ = new ButtonSeparatorView(); 1278 AddChildView(bookmarks_separator_view_); 1279 UpdateBookmarksSeparatorVisibility(); 1280 1281 instructions_ = new BookmarkBarInstructionsView(this); 1282 AddChildView(instructions_); 1283 1284 set_context_menu_controller(this); 1285 1286 size_animation_.reset(new gfx::SlideAnimation(this)); 1287 1288 model_ = BookmarkModelFactory::GetForProfile(browser_->profile()); 1289 if (model_) { 1290 model_->AddObserver(this); 1291 if (model_->loaded()) 1292 BookmarkModelLoaded(model_, false); 1293 // else case: we'll receive notification back from the BookmarkModel when 1294 // done loading, then we'll populate the bar. 1295 } 1296} 1297 1298int BookmarkBarView::GetBookmarkButtonCount() { 1299 // We contain four non-bookmark button views: other bookmarks, bookmarks 1300 // separator, chevrons (for overflow), apps page, and the instruction label. 1301 return child_count() - 5; 1302} 1303 1304views::TextButton* BookmarkBarView::GetBookmarkButton(int index) { 1305 DCHECK(index >= 0 && index < GetBookmarkButtonCount()); 1306 return static_cast<views::TextButton*>(child_at(index)); 1307} 1308 1309BookmarkLaunchLocation BookmarkBarView::GetBookmarkLaunchLocation() const { 1310 return IsDetached() ? BOOKMARK_LAUNCH_LOCATION_DETACHED_BAR : 1311 BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR; 1312} 1313 1314int BookmarkBarView::GetFirstHiddenNodeIndex() { 1315 const int bb_count = GetBookmarkButtonCount(); 1316 for (int i = 0; i < bb_count; ++i) { 1317 if (!GetBookmarkButton(i)->visible()) 1318 return i; 1319 } 1320 return bb_count; 1321} 1322 1323MenuButton* BookmarkBarView::CreateOtherBookmarkedButton() { 1324 // Title is set in Loaded. 1325 MenuButton* button = 1326 new BookmarkFolderButton(this, base::string16(), this, false); 1327 button->set_id(VIEW_ID_OTHER_BOOKMARKS); 1328 button->SetIcon(GetFolderIcon()); 1329 button->set_context_menu_controller(this); 1330 button->set_tag(kOtherFolderButtonTag); 1331 return button; 1332} 1333 1334MenuButton* BookmarkBarView::CreateOverflowButton() { 1335 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 1336 MenuButton* button = new OverFlowButton(this); 1337 button->SetIcon(*rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_CHEVRONS)); 1338 1339 // The overflow button's image contains an arrow and therefore it is a 1340 // direction sensitive image and we need to flip it if the UI layout is 1341 // right-to-left. 1342 // 1343 // By default, menu buttons are not flipped because they generally contain 1344 // text and flipping the gfx::Canvas object will break text rendering. Since 1345 // the overflow button does not contain text, we can safely flip it. 1346 button->EnableCanvasFlippingForRTLUI(true); 1347 1348 // Make visible as necessary. 1349 button->SetVisible(false); 1350 // Set accessibility name. 1351 button->SetAccessibleName( 1352 l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS_CHEVRON)); 1353 return button; 1354} 1355 1356views::View* BookmarkBarView::CreateBookmarkButton(const BookmarkNode* node) { 1357 if (node->is_url()) { 1358 BookmarkButton* button = new BookmarkButton( 1359 this, node->url(), node->GetTitle(), browser_->profile()); 1360 ConfigureButton(node, button); 1361 return button; 1362 } else { 1363 views::MenuButton* button = new BookmarkFolderButton( 1364 this, node->GetTitle(), this, false); 1365 button->SetIcon(GetFolderIcon()); 1366 ConfigureButton(node, button); 1367 return button; 1368 } 1369} 1370 1371views::TextButton* BookmarkBarView::CreateAppsPageShortcutButton() { 1372 views::TextButton* button = new ShortcutButton( 1373 this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME)); 1374 button->SetTooltipText(l10n_util::GetStringUTF16( 1375 IDS_BOOKMARK_BAR_APPS_SHORTCUT_TOOLTIP)); 1376 button->set_id(VIEW_ID_BOOKMARK_BAR_ELEMENT); 1377 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 1378 button->SetIcon(*rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_APPS_SHORTCUT)); 1379 button->set_context_menu_controller(this); 1380 button->set_tag(kAppsShortcutButtonTag); 1381 return button; 1382} 1383 1384void BookmarkBarView::ConfigureButton(const BookmarkNode* node, 1385 views::TextButton* button) { 1386 button->SetText(node->GetTitle()); 1387 button->SetAccessibleName(node->GetTitle()); 1388 button->set_id(VIEW_ID_BOOKMARK_BAR_ELEMENT); 1389 // We don't always have a theme provider (ui tests, for example). 1390 if (GetThemeProvider()) { 1391 button->SetEnabledColor(GetThemeProvider()->GetColor( 1392 ThemeProperties::COLOR_BOOKMARK_TEXT)); 1393 } 1394 1395 button->ClearMaxTextSize(); 1396 button->set_context_menu_controller(this); 1397 button->set_drag_controller(this); 1398 if (node->is_url()) { 1399 const gfx::Image& favicon = model_->GetFavicon(node); 1400 if (!favicon.IsEmpty()) 1401 button->SetIcon(*favicon.ToImageSkia()); 1402 else 1403 button->SetIcon(GetDefaultFavicon()); 1404 } 1405 button->set_max_width(kMaxButtonWidth); 1406} 1407 1408void BookmarkBarView::BookmarkNodeAddedImpl(BookmarkModel* model, 1409 const BookmarkNode* parent, 1410 int index) { 1411 UpdateOtherBookmarksVisibility(); 1412 if (parent != model_->bookmark_bar_node()) { 1413 // We only care about nodes on the bookmark bar. 1414 return; 1415 } 1416 DCHECK(index >= 0 && index <= GetBookmarkButtonCount()); 1417 const BookmarkNode* node = parent->GetChild(index); 1418 ProfileSyncService* sync_service(ProfileSyncServiceFactory:: 1419 GetInstance()->GetForProfile(browser_->profile())); 1420 if (!throbbing_view_ && sync_service && sync_service->FirstSetupInProgress()) 1421 StartThrobbing(node, true); 1422 AddChildViewAt(CreateBookmarkButton(node), index); 1423 UpdateColors(); 1424 Layout(); 1425 SchedulePaint(); 1426} 1427 1428void BookmarkBarView::BookmarkNodeRemovedImpl(BookmarkModel* model, 1429 const BookmarkNode* parent, 1430 int index) { 1431 UpdateOtherBookmarksVisibility(); 1432 1433 StopThrobbing(true); 1434 // No need to start throbbing again as the bookmark bubble can't be up at 1435 // the same time as the user reorders. 1436 1437 if (parent != model_->bookmark_bar_node()) { 1438 // We only care about nodes on the bookmark bar. 1439 return; 1440 } 1441 DCHECK(index >= 0 && index < GetBookmarkButtonCount()); 1442 views::View* button = child_at(index); 1443 RemoveChildView(button); 1444 base::MessageLoop::current()->DeleteSoon(FROM_HERE, button); 1445 Layout(); 1446 SchedulePaint(); 1447} 1448 1449void BookmarkBarView::BookmarkNodeChangedImpl(BookmarkModel* model, 1450 const BookmarkNode* node) { 1451 if (node->parent() != model_->bookmark_bar_node()) { 1452 // We only care about nodes on the bookmark bar. 1453 return; 1454 } 1455 int index = model_->bookmark_bar_node()->GetIndexOf(node); 1456 DCHECK_NE(-1, index); 1457 views::TextButton* button = GetBookmarkButton(index); 1458 gfx::Size old_pref = button->GetPreferredSize(); 1459 ConfigureButton(node, button); 1460 gfx::Size new_pref = button->GetPreferredSize(); 1461 if (old_pref.width() != new_pref.width()) { 1462 Layout(); 1463 SchedulePaint(); 1464 } else if (button->visible()) { 1465 button->SchedulePaint(); 1466 } 1467} 1468 1469void BookmarkBarView::ShowDropFolderForNode(const BookmarkNode* node) { 1470 if (bookmark_drop_menu_) { 1471 if (bookmark_drop_menu_->node() == node) { 1472 // Already showing for the specified node. 1473 return; 1474 } 1475 bookmark_drop_menu_->Cancel(); 1476 } 1477 1478 views::MenuButton* menu_button = GetMenuButtonForNode(node); 1479 if (!menu_button) 1480 return; 1481 1482 int start_index = 0; 1483 if (node == model_->bookmark_bar_node()) 1484 start_index = GetFirstHiddenNodeIndex(); 1485 1486 drop_info_->is_menu_showing = true; 1487 bookmark_drop_menu_ = new BookmarkMenuController(browser_, 1488 page_navigator_, GetWidget(), node, start_index); 1489 bookmark_drop_menu_->set_observer(this); 1490 bookmark_drop_menu_->RunMenuAt(this, true); 1491} 1492 1493void BookmarkBarView::StopShowFolderDropMenuTimer() { 1494 show_folder_method_factory_.InvalidateWeakPtrs(); 1495} 1496 1497void BookmarkBarView::StartShowFolderDropMenuTimer(const BookmarkNode* node) { 1498 if (!animations_enabled) { 1499 // So that tests can run as fast as possible disable the delay during 1500 // testing. 1501 ShowDropFolderForNode(node); 1502 return; 1503 } 1504 show_folder_method_factory_.InvalidateWeakPtrs(); 1505 base::MessageLoop::current()->PostDelayedTask( 1506 FROM_HERE, 1507 base::Bind(&BookmarkBarView::ShowDropFolderForNode, 1508 show_folder_method_factory_.GetWeakPtr(), 1509 node), 1510 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay())); 1511} 1512 1513void BookmarkBarView::CalculateDropLocation(const DropTargetEvent& event, 1514 const BookmarkNodeData& data, 1515 DropLocation* location) { 1516 DCHECK(model_); 1517 DCHECK(model_->loaded()); 1518 DCHECK(data.is_valid()); 1519 1520 *location = DropLocation(); 1521 1522 // The drop event uses the screen coordinates while the child Views are 1523 // always laid out from left to right (even though they are rendered from 1524 // right-to-left on RTL locales). Thus, in order to make sure the drop 1525 // coordinates calculation works, we mirror the event's X coordinate if the 1526 // locale is RTL. 1527 int mirrored_x = GetMirroredXInView(event.x()); 1528 1529 bool found = false; 1530 const int other_delta_x = mirrored_x - other_bookmarked_button_->x(); 1531 Profile* profile = browser_->profile(); 1532 if (other_bookmarked_button_->visible() && other_delta_x >= 0 && 1533 other_delta_x < other_bookmarked_button_->width()) { 1534 // Mouse is over 'other' folder. 1535 location->button_type = DROP_OTHER_FOLDER; 1536 location->on = true; 1537 found = true; 1538 } else if (!GetBookmarkButtonCount()) { 1539 // No bookmarks, accept the drop. 1540 location->index = 0; 1541 int ops = data.GetFirstNode(profile) ? ui::DragDropTypes::DRAG_MOVE : 1542 ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK; 1543 location->operation = chrome::GetPreferredBookmarkDropOperation( 1544 event.source_operations(), ops); 1545 return; 1546 } 1547 1548 for (int i = 0; i < GetBookmarkButtonCount() && 1549 GetBookmarkButton(i)->visible() && !found; i++) { 1550 views::TextButton* button = GetBookmarkButton(i); 1551 int button_x = mirrored_x - button->x(); 1552 int button_w = button->width(); 1553 if (button_x < button_w) { 1554 found = true; 1555 const BookmarkNode* node = model_->bookmark_bar_node()->GetChild(i); 1556 if (node->is_folder()) { 1557 if (button_x <= views::kDropBetweenPixels) { 1558 location->index = i; 1559 } else if (button_x < button_w - views::kDropBetweenPixels) { 1560 location->index = i; 1561 location->on = true; 1562 } else { 1563 location->index = i + 1; 1564 } 1565 } else if (button_x < button_w / 2) { 1566 location->index = i; 1567 } else { 1568 location->index = i + 1; 1569 } 1570 break; 1571 } 1572 } 1573 1574 if (!found) { 1575 if (overflow_button_->visible()) { 1576 // Are we over the overflow button? 1577 int overflow_delta_x = mirrored_x - overflow_button_->x(); 1578 if (overflow_delta_x >= 0 && 1579 overflow_delta_x < overflow_button_->width()) { 1580 // Mouse is over overflow button. 1581 location->index = GetFirstHiddenNodeIndex(); 1582 location->button_type = DROP_OVERFLOW; 1583 } else if (overflow_delta_x < 0) { 1584 // Mouse is after the last visible button but before overflow button; 1585 // use the last visible index. 1586 location->index = GetFirstHiddenNodeIndex(); 1587 } else { 1588 return; 1589 } 1590 } else if (!other_bookmarked_button_->visible() || 1591 mirrored_x < other_bookmarked_button_->x()) { 1592 // Mouse is after the last visible button but before more recently 1593 // bookmarked; use the last visible index. 1594 location->index = GetFirstHiddenNodeIndex(); 1595 } else { 1596 return; 1597 } 1598 } 1599 1600 if (location->on) { 1601 const BookmarkNode* parent = (location->button_type == DROP_OTHER_FOLDER) ? 1602 model_->other_node() : 1603 model_->bookmark_bar_node()->GetChild(location->index); 1604 location->operation = chrome::GetBookmarkDropOperation( 1605 profile, event, data, parent, parent->child_count()); 1606 if (!location->operation && !data.has_single_url() && 1607 data.GetFirstNode(profile) == parent) { 1608 // Don't open a menu if the node being dragged is the menu to open. 1609 location->on = false; 1610 } 1611 } else { 1612 location->operation = chrome::GetBookmarkDropOperation(profile, event, 1613 data, model_->bookmark_bar_node(), location->index); 1614 } 1615} 1616 1617void BookmarkBarView::WriteBookmarkDragData(const BookmarkNode* node, 1618 ui::OSExchangeData* data) { 1619 DCHECK(node && data); 1620 BookmarkNodeData drag_data(node); 1621 drag_data.Write(browser_->profile(), data); 1622} 1623 1624void BookmarkBarView::StartThrobbing(const BookmarkNode* node, 1625 bool overflow_only) { 1626 DCHECK(!throbbing_view_); 1627 1628 // Determine which visible button is showing the bookmark (or is an ancestor 1629 // of the bookmark). 1630 const BookmarkNode* bbn = model_->bookmark_bar_node(); 1631 const BookmarkNode* parent_on_bb = node; 1632 while (parent_on_bb) { 1633 const BookmarkNode* parent = parent_on_bb->parent(); 1634 if (parent == bbn) 1635 break; 1636 parent_on_bb = parent; 1637 } 1638 if (parent_on_bb) { 1639 int index = bbn->GetIndexOf(parent_on_bb); 1640 if (index >= GetFirstHiddenNodeIndex()) { 1641 // Node is hidden, animate the overflow button. 1642 throbbing_view_ = overflow_button_; 1643 } else if (!overflow_only) { 1644 throbbing_view_ = static_cast<CustomButton*>(child_at(index)); 1645 } 1646 } else if (!overflow_only) { 1647 throbbing_view_ = other_bookmarked_button_; 1648 } 1649 1650 // Use a large number so that the button continues to throb. 1651 if (throbbing_view_) 1652 throbbing_view_->StartThrobbing(std::numeric_limits<int>::max()); 1653} 1654 1655views::CustomButton* BookmarkBarView::DetermineViewToThrobFromRemove( 1656 const BookmarkNode* parent, 1657 int old_index) { 1658 const BookmarkNode* bbn = model_->bookmark_bar_node(); 1659 const BookmarkNode* old_node = parent; 1660 int old_index_on_bb = old_index; 1661 while (old_node && old_node != bbn) { 1662 const BookmarkNode* parent = old_node->parent(); 1663 if (parent == bbn) { 1664 old_index_on_bb = bbn->GetIndexOf(old_node); 1665 break; 1666 } 1667 old_node = parent; 1668 } 1669 if (old_node) { 1670 if (old_index_on_bb >= GetFirstHiddenNodeIndex()) { 1671 // Node is hidden, animate the overflow button. 1672 return overflow_button_; 1673 } 1674 return static_cast<CustomButton*>(child_at(old_index_on_bb)); 1675 } 1676 // Node wasn't on the bookmark bar, use the other bookmark button. 1677 return other_bookmarked_button_; 1678} 1679 1680void BookmarkBarView::UpdateColors() { 1681 // We don't always have a theme provider (ui tests, for example). 1682 const ui::ThemeProvider* theme_provider = GetThemeProvider(); 1683 if (!theme_provider) 1684 return; 1685 SkColor text_color = 1686 theme_provider->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT); 1687 for (int i = 0; i < GetBookmarkButtonCount(); ++i) 1688 GetBookmarkButton(i)->SetEnabledColor(text_color); 1689 other_bookmarked_button()->SetEnabledColor(text_color); 1690 if (apps_page_shortcut_->visible()) 1691 apps_page_shortcut_->SetEnabledColor(text_color); 1692} 1693 1694void BookmarkBarView::UpdateOtherBookmarksVisibility() { 1695 bool has_other_children = !model_->other_node()->empty(); 1696 if (has_other_children == other_bookmarked_button_->visible()) 1697 return; 1698 other_bookmarked_button_->SetVisible(has_other_children); 1699 UpdateBookmarksSeparatorVisibility(); 1700 Layout(); 1701 SchedulePaint(); 1702} 1703 1704void BookmarkBarView::UpdateBookmarksSeparatorVisibility() { 1705 // Ash does not paint the bookmarks separator line because it looks odd on 1706 // the flat background. We keep it present for layout, but don't draw it. 1707 bookmarks_separator_view_->SetVisible( 1708 browser_->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH && 1709 other_bookmarked_button_->visible()); 1710} 1711 1712gfx::Size BookmarkBarView::LayoutItems(bool compute_bounds_only) { 1713 gfx::Size prefsize; 1714 if (!parent() && !compute_bounds_only) 1715 return prefsize; 1716 1717 int x = kLeftMargin; 1718 int top_margin = IsDetached() ? kDetachedTopMargin : 0; 1719 int y = top_margin; 1720 int width = View::width() - kRightMargin - kLeftMargin; 1721 int height = chrome::kBookmarkBarHeight - kBottomMargin; 1722 int separator_margin = kSeparatorMargin; 1723 1724 if (IsDetached()) { 1725 double current_state = 1 - size_animation_->GetCurrentValue(); 1726 x += static_cast<int>(kNewtabHorizontalPadding * current_state); 1727 y += (View::height() - chrome::kBookmarkBarHeight) / 2; 1728 width -= static_cast<int>(kNewtabHorizontalPadding * current_state); 1729 separator_margin -= static_cast<int>(kSeparatorMargin * current_state); 1730 } else { 1731 // For the attached appearance, pin the content to the bottom of the bar 1732 // when animating in/out, as shrinking its height instead looks weird. This 1733 // also matches how we layout infobars. 1734 y += View::height() - chrome::kBookmarkBarHeight; 1735 } 1736 1737 gfx::Size other_bookmarked_pref = other_bookmarked_button_->visible() ? 1738 other_bookmarked_button_->GetPreferredSize() : gfx::Size(); 1739 gfx::Size overflow_pref = overflow_button_->GetPreferredSize(); 1740 gfx::Size bookmarks_separator_pref = 1741 bookmarks_separator_view_->GetPreferredSize(); 1742 gfx::Size apps_page_shortcut_pref = apps_page_shortcut_->visible() ? 1743 apps_page_shortcut_->GetPreferredSize() : gfx::Size(); 1744 1745 int max_x = width - overflow_pref.width() - kButtonPadding - 1746 bookmarks_separator_pref.width(); 1747 if (other_bookmarked_button_->visible()) 1748 max_x -= other_bookmarked_pref.width() + kButtonPadding; 1749 1750 // Next, layout out the buttons. Any buttons that are placed beyond the 1751 // visible region and made invisible. 1752 1753 // Start with the apps page shortcut button. 1754 if (apps_page_shortcut_->visible()) { 1755 if (!compute_bounds_only) { 1756 apps_page_shortcut_->SetBounds(x, y, apps_page_shortcut_pref.width(), 1757 height); 1758 } 1759 x += apps_page_shortcut_pref.width() + kButtonPadding; 1760 } 1761 1762 // Then go through the bookmark buttons. 1763 if (GetBookmarkButtonCount() == 0 && model_ && model_->loaded()) { 1764 gfx::Size pref = instructions_->GetPreferredSize(); 1765 if (!compute_bounds_only) { 1766 instructions_->SetBounds( 1767 x + kInstructionsPadding, y, 1768 std::min(static_cast<int>(pref.width()), 1769 max_x - x), 1770 height); 1771 instructions_->SetVisible(true); 1772 } 1773 } else { 1774 if (!compute_bounds_only) 1775 instructions_->SetVisible(false); 1776 1777 for (int i = 0; i < GetBookmarkButtonCount(); ++i) { 1778 views::View* child = child_at(i); 1779 gfx::Size pref = child->GetPreferredSize(); 1780 int next_x = x + pref.width() + kButtonPadding; 1781 if (!compute_bounds_only) { 1782 child->SetVisible(next_x < max_x); 1783 child->SetBounds(x, y, pref.width(), height); 1784 } 1785 x = next_x; 1786 } 1787 } 1788 1789 // Layout the right side of the bar. 1790 const bool all_visible = (GetBookmarkButtonCount() == 0 || 1791 child_at(GetBookmarkButtonCount() - 1)->visible()); 1792 1793 // Layout the right side buttons. 1794 if (!compute_bounds_only) 1795 x = max_x + kButtonPadding; 1796 else 1797 x += kButtonPadding; 1798 1799 // The overflow button. 1800 if (!compute_bounds_only) { 1801 overflow_button_->SetBounds(x, y, overflow_pref.width(), height); 1802 overflow_button_->SetVisible(!all_visible); 1803 } 1804 x += overflow_pref.width(); 1805 1806 // Separator. 1807 if (bookmarks_separator_view_->visible()) { 1808 if (!compute_bounds_only) { 1809 bookmarks_separator_view_->SetBounds(x, 1810 y - top_margin, 1811 bookmarks_separator_pref.width(), 1812 height + top_margin + kBottomMargin - 1813 separator_margin); 1814 } 1815 1816 x += bookmarks_separator_pref.width(); 1817 } 1818 1819 // The other bookmarks button. 1820 if (other_bookmarked_button_->visible()) { 1821 if (!compute_bounds_only) { 1822 other_bookmarked_button_->SetBounds(x, y, other_bookmarked_pref.width(), 1823 height); 1824 } 1825 x += other_bookmarked_pref.width() + kButtonPadding; 1826 } 1827 1828 // Set the preferred size computed so far. 1829 if (compute_bounds_only) { 1830 x += kRightMargin; 1831 prefsize.set_width(x); 1832 if (IsDetached()) { 1833 x += static_cast<int>(kNewtabHorizontalPadding * 1834 (1 - size_animation_->GetCurrentValue())); 1835 prefsize.set_height( 1836 chrome::kBookmarkBarHeight + 1837 static_cast<int>( 1838 (chrome::kNTPBookmarkBarHeight - chrome::kBookmarkBarHeight) * 1839 (1 - size_animation_->GetCurrentValue()))); 1840 } else { 1841 prefsize.set_height(static_cast<int>(chrome::kBookmarkBarHeight * 1842 size_animation_->GetCurrentValue())); 1843 } 1844 } 1845 return prefsize; 1846} 1847 1848void BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged() { 1849 DCHECK(apps_page_shortcut_); 1850 // Only perform layout if required. 1851 bool visible = chrome::ShouldShowAppsShortcutInBookmarkBar( 1852 browser_->profile(), browser_->host_desktop_type()); 1853 if (apps_page_shortcut_->visible() == visible) 1854 return; 1855 apps_page_shortcut_->SetVisible(visible); 1856 UpdateBookmarksSeparatorVisibility(); 1857 Layout(); 1858} 1859