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