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