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