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