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