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