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