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