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