1 // Copyright 2013 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/toolbar/browser_actions_container.h" 6 7 #include "base/compiler_specific.h" 8 #include "base/stl_util.h" 9 #include "chrome/browser/extensions/extension_action_manager.h" 10 #include "chrome/browser/extensions/extension_util.h" 11 #include "chrome/browser/extensions/extension_view_host.h" 12 #include "chrome/browser/extensions/tab_helper.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/browser/ui/browser.h" 15 #include "chrome/browser/ui/browser_window.h" 16 #include "chrome/browser/ui/tabs/tab_strip_model.h" 17 #include "chrome/browser/ui/view_ids.h" 18 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h" 19 #include "chrome/browser/ui/views/extensions/extension_popup.h" 20 #include "chrome/browser/ui/views/frame/browser_view.h" 21 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h" 22 #include "chrome/browser/ui/views/toolbar/toolbar_view.h" 23 #include "chrome/common/extensions/command.h" 24 #include "chrome/grit/generated_resources.h" 25 #include "extensions/browser/extension_registry.h" 26 #include "extensions/browser/extension_system.h" 27 #include "extensions/browser/runtime_data.h" 28 #include "extensions/common/feature_switch.h" 29 #include "grit/theme_resources.h" 30 #include "third_party/skia/include/core/SkColor.h" 31 #include "ui/accessibility/ax_view_state.h" 32 #include "ui/base/dragdrop/drag_utils.h" 33 #include "ui/base/l10n/l10n_util.h" 34 #include "ui/base/nine_image_painter_factory.h" 35 #include "ui/base/resource/resource_bundle.h" 36 #include "ui/base/theme_provider.h" 37 #include "ui/gfx/animation/slide_animation.h" 38 #include "ui/gfx/canvas.h" 39 #include "ui/gfx/geometry/rect.h" 40 #include "ui/resources/grit/ui_resources.h" 41 #include "ui/views/controls/button/label_button_border.h" 42 #include "ui/views/controls/button/menu_button.h" 43 #include "ui/views/controls/resize_area.h" 44 #include "ui/views/metrics.h" 45 #include "ui/views/painter.h" 46 #include "ui/views/widget/widget.h" 47 48 using extensions::Extension; 49 50 namespace { 51 52 // Horizontal spacing before the chevron (if visible). 53 const int kChevronSpacing = ToolbarView::kStandardSpacing - 2; 54 55 // A version of MenuButton with almost empty insets to fit properly on the 56 // toolbar. 57 class ChevronMenuButton : public views::MenuButton { 58 public: 59 ChevronMenuButton(views::ButtonListener* listener, 60 const base::string16& text, 61 views::MenuButtonListener* menu_button_listener, 62 bool show_menu_marker) 63 : views::MenuButton(listener, 64 text, 65 menu_button_listener, 66 show_menu_marker) { 67 } 68 69 virtual ~ChevronMenuButton() {} 70 71 virtual scoped_ptr<views::LabelButtonBorder> CreateDefaultBorder() const 72 OVERRIDE { 73 // The chevron resource was designed to not have any insets. 74 scoped_ptr<views::LabelButtonBorder> border = 75 views::MenuButton::CreateDefaultBorder(); 76 border->set_insets(gfx::Insets()); 77 return border.Pass(); 78 } 79 80 private: 81 DISALLOW_COPY_AND_ASSIGN(ChevronMenuButton); 82 }; 83 84 } // namespace 85 86 //////////////////////////////////////////////////////////////////////////////// 87 // BrowserActionsContainer::DropPosition 88 89 struct BrowserActionsContainer::DropPosition { 90 DropPosition(size_t row, size_t icon_in_row); 91 92 // The (0-indexed) row into which the action will be dropped. 93 size_t row; 94 95 // The (0-indexed) icon in the row before the action will be dropped. 96 size_t icon_in_row; 97 }; 98 99 BrowserActionsContainer::DropPosition::DropPosition( 100 size_t row, size_t icon_in_row) 101 : row(row), icon_in_row(icon_in_row) { 102 } 103 104 //////////////////////////////////////////////////////////////////////////////// 105 // BrowserActionsContainer 106 107 // static 108 int BrowserActionsContainer::icons_per_overflow_menu_row_ = 1; 109 110 // static 111 const int BrowserActionsContainer::kItemSpacing = ToolbarView::kStandardSpacing; 112 113 // static 114 bool BrowserActionsContainer::disable_animations_during_testing_ = false; 115 116 BrowserActionsContainer::BrowserActionsContainer( 117 Browser* browser, 118 View* owner_view, 119 BrowserActionsContainer* main_container) 120 : initialized_(false), 121 profile_(browser->profile()), 122 browser_(browser), 123 owner_view_(owner_view), 124 main_container_(main_container), 125 popup_owner_(NULL), 126 model_(NULL), 127 container_width_(0), 128 resize_area_(NULL), 129 chevron_(NULL), 130 overflow_menu_(NULL), 131 suppress_chevron_(false), 132 resize_amount_(0), 133 animation_target_size_(0), 134 show_menu_task_factory_(this) { 135 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR); 136 137 model_ = extensions::ExtensionToolbarModel::Get(browser->profile()); 138 if (model_) 139 model_->AddObserver(this); 140 141 bool overflow_experiment = 142 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled(); 143 DCHECK(!in_overflow_mode() || overflow_experiment); 144 145 if (!in_overflow_mode()) { 146 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews( 147 browser->profile(), 148 owner_view->GetFocusManager(), 149 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS, 150 this)); 151 152 resize_animation_.reset(new gfx::SlideAnimation(this)); 153 resize_area_ = new views::ResizeArea(this); 154 AddChildView(resize_area_); 155 156 // 'Main' mode doesn't need a chevron overflow when overflow is shown inside 157 // the Chrome menu. 158 if (!overflow_experiment) { 159 chevron_ = new ChevronMenuButton(NULL, base::string16(), this, false); 160 chevron_->EnableCanvasFlippingForRTLUI(true); 161 chevron_->SetAccessibleName( 162 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON)); 163 chevron_->SetVisible(false); 164 AddChildView(chevron_); 165 } 166 } 167 } 168 169 BrowserActionsContainer::~BrowserActionsContainer() { 170 FOR_EACH_OBSERVER(BrowserActionsContainerObserver, 171 observers_, 172 OnBrowserActionsContainerDestroyed()); 173 174 if (overflow_menu_) 175 overflow_menu_->set_observer(NULL); 176 if (model_) 177 model_->RemoveObserver(this); 178 StopShowFolderDropMenuTimer(); 179 HideActivePopup(); 180 DeleteBrowserActionViews(); 181 } 182 183 void BrowserActionsContainer::Init() { 184 LoadImages(); 185 186 // We wait to set the container width until now so that the chevron images 187 // will be loaded. The width calculation needs to know the chevron size. 188 if (model_ && model_->extensions_initialized()) { 189 container_width_ = GetPreferredWidth(); 190 SetChevronVisibility(); 191 } 192 193 initialized_ = true; 194 } 195 196 BrowserActionView* BrowserActionsContainer::GetViewForExtension( 197 const Extension* extension) { 198 for (BrowserActionViews::iterator view = browser_action_views_.begin(); 199 view != browser_action_views_.end(); ++view) { 200 if ((*view)->extension() == extension) 201 return *view; 202 } 203 return NULL; 204 } 205 206 void BrowserActionsContainer::RefreshBrowserActionViews() { 207 for (size_t i = 0; i < browser_action_views_.size(); ++i) 208 browser_action_views_[i]->UpdateState(); 209 } 210 211 void BrowserActionsContainer::CreateBrowserActionViews() { 212 DCHECK(browser_action_views_.empty()); 213 if (!model_) 214 return; 215 216 extensions::ExtensionActionManager* action_manager = 217 extensions::ExtensionActionManager::Get(profile_); 218 const extensions::ExtensionList& toolbar_items = model_->toolbar_items(); 219 for (extensions::ExtensionList::const_iterator i(toolbar_items.begin()); 220 i != toolbar_items.end(); ++i) { 221 if (!ShouldDisplayBrowserAction(i->get())) 222 continue; 223 224 BrowserActionView* view = 225 new BrowserActionView(i->get(), 226 action_manager->GetExtensionAction(**i), 227 browser_, 228 this); 229 browser_action_views_.push_back(view); 230 AddChildView(view); 231 } 232 } 233 234 void BrowserActionsContainer::DeleteBrowserActionViews() { 235 HideActivePopup(); 236 if (overflow_menu_) 237 overflow_menu_->NotifyBrowserActionViewsDeleting(); 238 STLDeleteElements(&browser_action_views_); 239 } 240 241 size_t BrowserActionsContainer::VisibleBrowserActions() const { 242 size_t visible_actions = 0; 243 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 244 if (browser_action_views_[i]->visible()) 245 ++visible_actions; 246 } 247 return visible_actions; 248 } 249 250 size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const { 251 if (!animating()) 252 return VisibleBrowserActions(); 253 254 return WidthToIconCount(animation_target_size_); 255 } 256 257 void BrowserActionsContainer::ExecuteExtensionCommand( 258 const extensions::Extension* extension, 259 const extensions::Command& command) { 260 // Global commands are handled by the ExtensionCommandsGlobalRegistry 261 // instance. 262 DCHECK(!command.global()); 263 extension_keybinding_registry_->ExecuteCommand(extension->id(), 264 command.accelerator()); 265 } 266 267 void BrowserActionsContainer::NotifyActionMovedToOverflow() { 268 // When an action is moved to overflow, we shrink the size of the container 269 // by 1. 270 if (!profile_->IsOffTheRecord()) { 271 int icon_count = model_->GetVisibleIconCount(); 272 // Since this happens when an icon moves from the main bar to overflow, we 273 // can't possibly have had no visible icons on the main bar. 274 DCHECK_NE(0, icon_count); 275 if (icon_count == -1) 276 icon_count = browser_action_views_.size(); 277 model_->SetVisibleIconCount(icon_count - 1); 278 } 279 Animate(gfx::Tween::EASE_OUT, 280 VisibleBrowserActionsAfterAnimation() - 1); 281 } 282 283 bool BrowserActionsContainer::ShownInsideMenu() const { 284 return in_overflow_mode(); 285 } 286 287 void BrowserActionsContainer::OnBrowserActionViewDragDone() { 288 ToolbarVisibleCountChanged(); 289 FOR_EACH_OBSERVER(BrowserActionsContainerObserver, 290 observers_, 291 OnBrowserActionDragDone()); 292 } 293 294 views::MenuButton* BrowserActionsContainer::GetOverflowReferenceView() { 295 // With traditional overflow, the reference is the chevron. With the 296 // redesign, we use the wrench menu instead. 297 return chevron_ ? 298 chevron_ : 299 BrowserView::GetBrowserViewForBrowser(browser_)->toolbar()->app_menu(); 300 } 301 302 void BrowserActionsContainer::SetPopupOwner(BrowserActionView* popup_owner) { 303 // We should never be setting a popup owner when one already exists, and 304 // never unsetting one when one wasn't set. 305 DCHECK((!popup_owner_ && popup_owner) || 306 (popup_owner_ && !popup_owner)); 307 popup_owner_ = popup_owner; 308 } 309 310 void BrowserActionsContainer::HideActivePopup() { 311 if (popup_owner_) 312 popup_owner_->view_controller()->HidePopup(); 313 } 314 315 BrowserActionView* BrowserActionsContainer::GetMainViewForExtension( 316 const Extension* extension) { 317 return in_overflow_mode() ? 318 main_container_->GetViewForExtension(extension) : 319 GetViewForExtension(extension); 320 } 321 322 void BrowserActionsContainer::AddObserver( 323 BrowserActionsContainerObserver* observer) { 324 observers_.AddObserver(observer); 325 } 326 327 void BrowserActionsContainer::RemoveObserver( 328 BrowserActionsContainerObserver* observer) { 329 observers_.RemoveObserver(observer); 330 } 331 332 gfx::Size BrowserActionsContainer::GetPreferredSize() const { 333 if (in_overflow_mode()) { 334 int icon_count = GetIconCount(); 335 // In overflow, we always have a preferred size of a full row (even if we 336 // don't use it), and always of at least one row. The parent may decide to 337 // show us even when empty, e.g. as a drag target for dragging in icons from 338 // the main container. 339 int row_count = 340 ((std::max(0, icon_count - 1)) / icons_per_overflow_menu_row_) + 1; 341 return gfx::Size( 342 IconCountToWidth(icons_per_overflow_menu_row_, false), 343 row_count * IconHeight()); 344 } 345 346 // If there are no actions to show, then don't show the container at all. 347 if (browser_action_views_.empty()) 348 return gfx::Size(); 349 350 // We calculate the size of the view by taking the current width and 351 // subtracting resize_amount_ (the latter represents how far the user is 352 // resizing the view or, if animating the snapping, how far to animate it). 353 // But we also clamp it to a minimum size and the maximum size, so that the 354 // container can never shrink too far or take up more space than it needs. 355 // In other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX). 356 int preferred_width = std::min( 357 std::max(MinimumNonemptyWidth(), container_width_ - resize_amount_), 358 IconCountToWidth(-1, false)); 359 return gfx::Size(preferred_width, IconHeight()); 360 } 361 362 int BrowserActionsContainer::GetHeightForWidth(int width) const { 363 if (in_overflow_mode()) 364 icons_per_overflow_menu_row_ = (width - kItemSpacing) / IconWidth(true); 365 return GetPreferredSize().height(); 366 } 367 368 gfx::Size BrowserActionsContainer::GetMinimumSize() const { 369 int min_width = std::min(MinimumNonemptyWidth(), IconCountToWidth(-1, false)); 370 return gfx::Size(min_width, IconHeight()); 371 } 372 373 void BrowserActionsContainer::Layout() { 374 if (browser_action_views_.empty()) { 375 SetVisible(false); 376 return; 377 } 378 379 SetVisible(true); 380 if (resize_area_) 381 resize_area_->SetBounds(0, 0, kItemSpacing, height()); 382 383 // If the icons don't all fit, show the chevron (unless suppressed). 384 int max_x = GetPreferredSize().width(); 385 if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_ && chevron_) { 386 chevron_->SetVisible(true); 387 gfx::Size chevron_size(chevron_->GetPreferredSize()); 388 max_x -= 389 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing; 390 chevron_->SetBounds( 391 width() - ToolbarView::kStandardSpacing - chevron_size.width(), 392 0, 393 chevron_size.width(), 394 chevron_size.height()); 395 } else if (chevron_) { 396 chevron_->SetVisible(false); 397 } 398 399 // Now draw the icons for the browser actions in the available space. 400 int icon_width = IconWidth(false); 401 if (in_overflow_mode()) { 402 for (size_t i = 0; 403 i < main_container_->VisibleBrowserActionsAfterAnimation(); ++i) { 404 // Ensure that any browser actions shown in the main view are hidden in 405 // the overflow view. 406 browser_action_views_[i]->SetVisible(false); 407 } 408 409 for (size_t i = main_container_->VisibleBrowserActionsAfterAnimation(); 410 i < browser_action_views_.size(); ++i) { 411 BrowserActionView* view = browser_action_views_[i]; 412 size_t index = i - main_container_->VisibleBrowserActionsAfterAnimation(); 413 int row_index = static_cast<int>(index) / icons_per_overflow_menu_row_; 414 int x = kItemSpacing + (index * IconWidth(true)) - 415 (row_index * IconWidth(true) * icons_per_overflow_menu_row_); 416 gfx::Rect rect_bounds( 417 x, IconHeight() * row_index, icon_width, IconHeight()); 418 view->SetBoundsRect(rect_bounds); 419 view->SetVisible(true); 420 } 421 } else { 422 for (BrowserActionViews::const_iterator it = browser_action_views_.begin(); 423 it < browser_action_views_.end(); ++it) { 424 BrowserActionView* view = *it; 425 int x = ToolbarView::kStandardSpacing + 426 ((it - browser_action_views_.begin()) * IconWidth(true)); 427 view->SetVisible(x + icon_width <= max_x); 428 if (view->visible()) 429 view->SetBounds(x, 0, icon_width, IconHeight()); 430 } 431 } 432 } 433 434 bool BrowserActionsContainer::GetDropFormats( 435 int* formats, 436 std::set<OSExchangeData::CustomFormat>* custom_formats) { 437 return BrowserActionDragData::GetDropFormats(custom_formats); 438 } 439 440 bool BrowserActionsContainer::AreDropTypesRequired() { 441 return BrowserActionDragData::AreDropTypesRequired(); 442 } 443 444 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) { 445 return BrowserActionDragData::CanDrop(data, profile_); 446 } 447 448 int BrowserActionsContainer::OnDragUpdated( 449 const ui::DropTargetEvent& event) { 450 // First check if we are above the chevron (overflow) menu. 451 if (chevron_ && GetEventHandlerForPoint(event.location()) == chevron_) { 452 if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_) 453 StartShowFolderDropMenuTimer(); 454 return ui::DragDropTypes::DRAG_MOVE; 455 } 456 StopShowFolderDropMenuTimer(); 457 458 size_t row_index = 0; 459 size_t before_icon_in_row = 0; 460 // If there are no visible browser actions (such as when dragging an icon to 461 // an empty overflow/main container), then 0, 0 for row, column is correct. 462 if (VisibleBrowserActions() != 0) { 463 // Figure out where to display the indicator. This is a complex calculation: 464 465 // First, we subtract out the padding to the left of the icon area, which is 466 // ToolbarView::kStandardSpacing. If we're right-to-left, we also mirror the 467 // event.x() so that our calculations are consistent with left-to-right. 468 int offset_into_icon_area = 469 GetMirroredXInView(event.x()) - ToolbarView::kStandardSpacing; 470 471 // Next, figure out what row we're on. This only matters for overflow mode, 472 // but the calculation is the same for both. 473 row_index = event.y() / IconHeight(); 474 475 // Sanity check - we should never be on a different row in the main 476 // container. 477 DCHECK(in_overflow_mode() || row_index == 0); 478 479 // Next, we determine which icon to place the indicator in front of. We want 480 // to place the indicator in front of icon n when the cursor is between the 481 // midpoints of icons (n - 1) and n. To do this we take the offset into the 482 // icon area and transform it as follows: 483 // 484 // Real icon area: 485 // 0 a * b c 486 // | | | | 487 // |[IC|ON] [IC|ON] [IC|ON] 488 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc. 489 // Here the "*" represents the offset into the icon area, and since it's 490 // between a and b, we want to return "1". 491 // 492 // Transformed "icon area": 493 // 0 a * b c 494 // | | | | 495 // |[ICON] |[ICON] |[ICON] | 496 // If we shift both our offset and our divider points later by half an icon 497 // plus one spacing unit, then it becomes very easy to calculate how many 498 // divider points we've passed, because they're the multiples of "one icon 499 // plus padding". 500 int before_icon_unclamped = 501 (offset_into_icon_area + (IconWidth(false) / 2) + 502 kItemSpacing) / IconWidth(true); 503 504 // We need to figure out how many icons are visible on the relevant row. 505 // In the main container, this will just be the visible actions. 506 int visible_icons_on_row = VisibleBrowserActionsAfterAnimation(); 507 if (in_overflow_mode()) { 508 // If this is the final row of the overflow, then this is the remainder of 509 // visible icons. Otherwise, it's a full row (kIconsPerRow). 510 visible_icons_on_row = 511 row_index == 512 static_cast<size_t>(visible_icons_on_row / 513 icons_per_overflow_menu_row_) ? 514 visible_icons_on_row % icons_per_overflow_menu_row_ : 515 icons_per_overflow_menu_row_; 516 } 517 518 // Because the user can drag outside the container bounds, we need to clamp 519 // to the valid range. Note that the maximum allowable value is (num icons), 520 // not (num icons - 1), because we represent the indicator being past the 521 // last icon as being "before the (last + 1) icon". 522 before_icon_in_row = 523 std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row); 524 } 525 526 if (!drop_position_.get() || 527 !(drop_position_->row == row_index && 528 drop_position_->icon_in_row == before_icon_in_row)) { 529 drop_position_.reset(new DropPosition(row_index, before_icon_in_row)); 530 SchedulePaint(); 531 } 532 533 return ui::DragDropTypes::DRAG_MOVE; 534 } 535 536 void BrowserActionsContainer::OnDragExited() { 537 StopShowFolderDropMenuTimer(); 538 drop_position_.reset(); 539 SchedulePaint(); 540 } 541 542 int BrowserActionsContainer::OnPerformDrop( 543 const ui::DropTargetEvent& event) { 544 BrowserActionDragData data; 545 if (!data.Read(event.data())) 546 return ui::DragDropTypes::DRAG_NONE; 547 548 // Make sure we have the same view as we started with. 549 DCHECK_EQ(browser_action_views_[data.index()]->extension()->id(), 550 data.id()); 551 DCHECK(model_); 552 553 size_t i = drop_position_->row * icons_per_overflow_menu_row_ + 554 drop_position_->icon_in_row; 555 if (in_overflow_mode()) 556 i += main_container_->VisibleBrowserActionsAfterAnimation(); 557 // |i| now points to the item to the right of the drop indicator*, which is 558 // correct when dragging an icon to the left. When dragging to the right, 559 // however, we want the icon being dragged to get the index of the item to 560 // the left of the drop indicator, so we subtract one. 561 // * Well, it can also point to the end, but not when dragging to the left. :) 562 if (i > data.index()) 563 --i; 564 565 if (profile_->IsOffTheRecord()) 566 i = model_->IncognitoIndexToOriginal(i); 567 568 // If this was a drag between containers, we will have to adjust the number of 569 // visible icons. 570 bool drag_between_containers = 571 !browser_action_views_[data.index()]->visible(); 572 model_->MoveExtensionIcon( 573 browser_action_views_[data.index()]->extension(), i); 574 575 if (drag_between_containers) { 576 // Add one for the dropped icon. 577 size_t new_icon_count = VisibleBrowserActionsAfterAnimation() + 1; 578 579 // Let the main container update the model. 580 if (in_overflow_mode()) 581 main_container_->NotifyActionMovedToOverflow(); 582 else if (!profile_->IsOffTheRecord()) // This is the main container. 583 model_->SetVisibleIconCount(model_->GetVisibleIconCount() + 1); 584 585 // The size changed, so we need to animate. 586 Animate(gfx::Tween::EASE_OUT, new_icon_count); 587 } 588 589 OnDragExited(); // Perform clean up after dragging. 590 return ui::DragDropTypes::DRAG_MOVE; 591 } 592 593 void BrowserActionsContainer::GetAccessibleState( 594 ui::AXViewState* state) { 595 state->role = ui::AX_ROLE_GROUP; 596 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS); 597 } 598 599 void BrowserActionsContainer::OnMenuButtonClicked(views::View* source, 600 const gfx::Point& point) { 601 if (source == chevron_) { 602 overflow_menu_ = 603 new BrowserActionOverflowMenuController(this, 604 browser_, 605 chevron_, 606 browser_action_views_, 607 VisibleBrowserActions(), 608 false); 609 overflow_menu_->set_observer(this); 610 overflow_menu_->RunMenu(GetWidget()); 611 } 612 } 613 614 void BrowserActionsContainer::WriteDragDataForView(View* sender, 615 const gfx::Point& press_pt, 616 OSExchangeData* data) { 617 DCHECK(data); 618 619 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 620 BrowserActionView* view = browser_action_views_[i]; 621 if (view == sender) { 622 // Set the dragging image for the icon. 623 gfx::ImageSkia badge(view->GetIconWithBadge()); 624 drag_utils::SetDragImageOnDataObject(badge, 625 press_pt.OffsetFromOrigin(), 626 data); 627 628 // Fill in the remaining info. 629 BrowserActionDragData drag_data(view->extension()->id(), i); 630 drag_data.Write(profile_, data); 631 break; 632 } 633 } 634 } 635 636 int BrowserActionsContainer::GetDragOperationsForView(View* sender, 637 const gfx::Point& p) { 638 return ui::DragDropTypes::DRAG_MOVE; 639 } 640 641 bool BrowserActionsContainer::CanStartDragForView(View* sender, 642 const gfx::Point& press_pt, 643 const gfx::Point& p) { 644 // We don't allow dragging while we're highlighting. 645 return !model_->is_highlighting(); 646 } 647 648 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) { 649 if (!done_resizing) { 650 resize_amount_ = resize_amount; 651 OnBrowserActionVisibilityChanged(); 652 return; 653 } 654 655 // Up until now we've only been modifying the resize_amount, but now it is 656 // time to set the container size to the size we have resized to, and then 657 // animate to the nearest icon count size if necessary (which may be 0). 658 int max_width = IconCountToWidth(-1, false); 659 container_width_ = 660 std::min(std::max(0, container_width_ - resize_amount), max_width); 661 662 // Save off the desired number of visible icons. We do this now instead of at 663 // the end of the animation so that even if the browser is shut down while 664 // animating, the right value will be restored on next run. 665 // NOTE: Don't save the icon count in incognito because there may be fewer 666 // icons in that mode. The result is that the container in a normal window is 667 // always at least as wide as in an incognito window. 668 int visible_icons = WidthToIconCount(container_width_); 669 if (!profile_->IsOffTheRecord()) 670 model_->SetVisibleIconCount(visible_icons); 671 Animate(gfx::Tween::EASE_OUT, visible_icons); 672 } 673 674 void BrowserActionsContainer::AnimationProgressed( 675 const gfx::Animation* animation) { 676 DCHECK_EQ(resize_animation_.get(), animation); 677 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() * 678 (container_width_ - animation_target_size_)); 679 OnBrowserActionVisibilityChanged(); 680 } 681 682 void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) { 683 container_width_ = animation_target_size_; 684 animation_target_size_ = 0; 685 resize_amount_ = 0; 686 suppress_chevron_ = false; 687 SetChevronVisibility(); 688 OnBrowserActionVisibilityChanged(); 689 690 FOR_EACH_OBSERVER(BrowserActionsContainerObserver, 691 observers_, 692 OnBrowserActionsContainerAnimationEnded()); 693 } 694 695 void BrowserActionsContainer::NotifyMenuDeleted( 696 BrowserActionOverflowMenuController* controller) { 697 DCHECK_EQ(overflow_menu_, controller); 698 overflow_menu_ = NULL; 699 } 700 701 content::WebContents* BrowserActionsContainer::GetCurrentWebContents() { 702 return browser_->tab_strip_model()->GetActiveWebContents(); 703 } 704 705 extensions::ActiveTabPermissionGranter* 706 BrowserActionsContainer::GetActiveTabPermissionGranter() { 707 content::WebContents* web_contents = 708 browser_->tab_strip_model()->GetActiveWebContents(); 709 if (!web_contents) 710 return NULL; 711 return extensions::TabHelper::FromWebContents(web_contents)-> 712 active_tab_permission_granter(); 713 } 714 715 ExtensionPopup* BrowserActionsContainer::TestGetPopup() { 716 return popup_owner_ ? popup_owner_->view_controller()->popup() : NULL; 717 } 718 719 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) { 720 model_->SetVisibleIconCountForTest(icons); 721 } 722 723 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) { 724 // If the views haven't been initialized yet, wait for the next call to 725 // paint (one will be triggered by entering highlight mode). 726 if (model_->is_highlighting() && !browser_action_views_.empty()) { 727 views::Painter::PaintPainterAt( 728 canvas, highlight_painter_.get(), GetLocalBounds()); 729 } 730 731 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while 732 // dragging (like we do for tab dragging). 733 if (drop_position_.get()) { 734 // The two-pixel width drop indicator. 735 static const int kDropIndicatorWidth = 2; 736 737 // Convert back to a pixel offset into the container. First find the X 738 // coordinate of the drop icon. 739 int drop_icon_x = ToolbarView::kStandardSpacing + 740 (drop_position_->icon_in_row * IconWidth(true)); 741 // Next, find the space before the drop icon. This will either be 742 // kItemSpacing or ToolbarView::kStandardSpacing, depending on whether this 743 // is the first icon. 744 // NOTE: Right now, these are the same. But let's do this right for if they 745 // ever aren't. 746 int space_before_drop_icon = drop_position_->icon_in_row == 0 ? 747 ToolbarView::kStandardSpacing : kItemSpacing; 748 // Now place the drop indicator halfway between this and the end of the 749 // previous icon. If there is an odd amount of available space between the 750 // two icons (or the icon and the address bar) after subtracting the drop 751 // indicator width, this calculation puts the extra pixel on the left side 752 // of the indicator, since when the indicator is between the address bar and 753 // the first icon, it looks better closer to the icon. 754 int drop_indicator_x = drop_icon_x - 755 ((space_before_drop_icon + kDropIndicatorWidth) / 2); 756 int row_height = IconHeight(); 757 int drop_indicator_y = row_height * drop_position_->row; 758 gfx::Rect indicator_bounds(drop_indicator_x, 759 drop_indicator_y, 760 kDropIndicatorWidth, 761 row_height); 762 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds)); 763 764 // Color of the drop indicator. 765 static const SkColor kDropIndicatorColor = SK_ColorBLACK; 766 canvas->FillRect(indicator_bounds, kDropIndicatorColor); 767 } 768 } 769 770 void BrowserActionsContainer::OnThemeChanged() { 771 LoadImages(); 772 } 773 774 void BrowserActionsContainer::ViewHierarchyChanged( 775 const ViewHierarchyChangedDetails& details) { 776 // No extensions (e.g., incognito). 777 if (!model_) 778 return; 779 780 if (details.is_add && details.child == this) { 781 // Initial toolbar button creation and placement in the widget hierarchy. 782 // We do this here instead of in the constructor because AddBrowserAction 783 // calls Layout on the Toolbar, which needs this object to be constructed 784 // before its Layout function is called. 785 CreateBrowserActionViews(); 786 } 787 } 788 789 // static 790 int BrowserActionsContainer::IconWidth(bool include_padding) { 791 static bool initialized = false; 792 static int icon_width = 0; 793 if (!initialized) { 794 initialized = true; 795 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 796 IDR_BROWSER_ACTION)->width(); 797 } 798 return icon_width + (include_padding ? kItemSpacing : 0); 799 } 800 801 // static 802 int BrowserActionsContainer::IconHeight() { 803 static bool initialized = false; 804 static int icon_height = 0; 805 if (!initialized) { 806 initialized = true; 807 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 808 IDR_BROWSER_ACTION)->height(); 809 } 810 return icon_height; 811 } 812 813 void BrowserActionsContainer::ToolbarExtensionAdded(const Extension* extension, 814 int index) { 815 #if defined(DEBUG) 816 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 817 DCHECK(browser_action_views_[i]->extension() != extension) << 818 "Asked to add a browser action view for an extension that already " 819 "exists."; 820 } 821 #endif 822 CloseOverflowMenu(); 823 824 if (!ShouldDisplayBrowserAction(extension)) 825 return; 826 827 // Add the new browser action to the vector and the view hierarchy. 828 if (profile_->IsOffTheRecord()) 829 index = model_->OriginalIndexToIncognito(index); 830 BrowserActionView* view = 831 new BrowserActionView(extension, 832 extensions::ExtensionActionManager::Get(profile_)-> 833 GetExtensionAction(*extension), 834 browser_, 835 this); 836 browser_action_views_.insert(browser_action_views_.begin() + index, view); 837 AddChildViewAt(view, index); 838 839 // If we are still initializing the container, don't bother animating. 840 if (!model_->extensions_initialized()) 841 return; 842 843 // If this is just an upgrade, then don't worry about resizing. 844 if (!extensions::ExtensionSystem::Get(profile_)->runtime_data()-> 845 IsBeingUpgraded(extension)) { 846 // We need to resize if either: 847 // - The container is set to display all icons (visible count = -1), or 848 // - The container will need to expand to include the chevron. This can 849 // happen when the container is set to display <n> icons, where <n> is 850 // the number of icons before the new icon. With the new icon, the chevron 851 // will need to be displayed. 852 int model_icon_count = model_->GetVisibleIconCount(); 853 if (model_icon_count == -1 || 854 (static_cast<size_t>(model_icon_count) < browser_action_views_.size() && 855 (chevron_ && !chevron_->visible()))) { 856 suppress_chevron_ = true; 857 Animate(gfx::Tween::LINEAR, GetIconCount()); 858 return; 859 } 860 } 861 862 // Otherwise, we don't have to resize, so just redraw the (possibly modified) 863 // visible icon set. 864 OnBrowserActionVisibilityChanged(); 865 } 866 867 void BrowserActionsContainer::ToolbarExtensionRemoved( 868 const Extension* extension) { 869 CloseOverflowMenu(); 870 871 size_t visible_actions = VisibleBrowserActionsAfterAnimation(); 872 for (BrowserActionViews::iterator i(browser_action_views_.begin()); 873 i != browser_action_views_.end(); ++i) { 874 if ((*i)->extension() == extension) { 875 delete *i; 876 browser_action_views_.erase(i); 877 878 // If the extension is being upgraded we don't want the bar to shrink 879 // because the icon is just going to get re-added to the same location. 880 if (extensions::ExtensionSystem::Get(profile_)->runtime_data()-> 881 IsBeingUpgraded(extension)) 882 return; 883 884 if (browser_action_views_.size() > visible_actions) { 885 // If we have more icons than we can show, then we must not be changing 886 // the container size (since we either removed an icon from the main 887 // area and one from the overflow list will have shifted in, or we 888 // removed an entry directly from the overflow list). 889 OnBrowserActionVisibilityChanged(); 890 } else { 891 // Either we went from overflow to no-overflow, or we shrunk the no- 892 // overflow container by 1. Either way the size changed, so animate. 893 if (chevron_) 894 chevron_->SetVisible(false); 895 Animate(gfx::Tween::EASE_OUT, browser_action_views_.size()); 896 } 897 return; // We have found the action to remove, bail out. 898 } 899 } 900 } 901 902 void BrowserActionsContainer::ToolbarExtensionMoved(const Extension* extension, 903 int index) { 904 if (!ShouldDisplayBrowserAction(extension)) 905 return; 906 907 if (profile_->IsOffTheRecord()) 908 index = model_->OriginalIndexToIncognito(index); 909 910 DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size())); 911 912 BrowserActionViews::iterator iter = browser_action_views_.begin(); 913 int old_index = 0; 914 while (iter != browser_action_views_.end() && 915 (*iter)->extension() != extension) { 916 ++iter; 917 ++old_index; 918 } 919 920 DCHECK(iter != browser_action_views_.end()); 921 if (old_index == index) 922 return; // Already in place. 923 924 BrowserActionView* moved_view = *iter; 925 browser_action_views_.erase(iter); 926 browser_action_views_.insert( 927 browser_action_views_.begin() + index, moved_view); 928 929 Layout(); 930 SchedulePaint(); 931 } 932 933 void BrowserActionsContainer::ToolbarExtensionUpdated( 934 const Extension* extension) { 935 BrowserActionView* view = GetViewForExtension(extension); 936 if (view) 937 view->UpdateState(); 938 } 939 940 bool BrowserActionsContainer::ShowExtensionActionPopup( 941 const Extension* extension, 942 bool grant_active_tab) { 943 // Don't override another popup, and only show in the active window. 944 if (popup_owner_ || !browser_->window()->IsActive()) 945 return false; 946 947 BrowserActionView* view = GetViewForExtension(extension); 948 return view && view->view_controller()->ExecuteAction(ExtensionPopup::SHOW, 949 grant_active_tab); 950 } 951 952 void BrowserActionsContainer::ToolbarVisibleCountChanged() { 953 if (GetPreferredWidth() != container_width_) 954 Animate(gfx::Tween::EASE_OUT, GetIconCount()); 955 } 956 957 void BrowserActionsContainer::ToolbarHighlightModeChanged( 958 bool is_highlighting) { 959 // The visual highlighting is done in OnPaint(). It's a bit of a pain that 960 // we delete and recreate everything here, but given everything else going on 961 // (the lack of highlight, n more extensions appearing, etc), it's not worth 962 // the extra complexity to create and insert only the new extensions. 963 DeleteBrowserActionViews(); 964 CreateBrowserActionViews(); 965 Animate(gfx::Tween::LINEAR, GetIconCount()); 966 } 967 968 Browser* BrowserActionsContainer::GetBrowser() { 969 return browser_; 970 } 971 972 void BrowserActionsContainer::LoadImages() { 973 ui::ThemeProvider* tp = GetThemeProvider(); 974 if (!tp || !chevron_) 975 return; 976 977 chevron_->SetImage(views::Button::STATE_NORMAL, 978 *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW)); 979 980 const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT); 981 highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages)); 982 } 983 984 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() { 985 SetVisible(!browser_action_views_.empty()); 986 if (owner_view_) { 987 owner_view_->Layout(); 988 owner_view_->SchedulePaint(); 989 } else { 990 // In overflow mode, we don't have an owner view, but we still have to 991 // update ourselves. 992 Layout(); 993 SchedulePaint(); 994 } 995 } 996 997 int BrowserActionsContainer::GetPreferredWidth() { 998 size_t visible_actions = GetIconCount(); 999 return IconCountToWidth( 1000 visible_actions, 1001 chevron_ && visible_actions < browser_action_views_.size()); 1002 } 1003 1004 void BrowserActionsContainer::SetChevronVisibility() { 1005 if (chevron_) { 1006 chevron_->SetVisible( 1007 VisibleBrowserActionsAfterAnimation() < browser_action_views_.size()); 1008 } 1009 } 1010 1011 void BrowserActionsContainer::CloseOverflowMenu() { 1012 if (overflow_menu_) 1013 overflow_menu_->CancelMenu(); 1014 } 1015 1016 void BrowserActionsContainer::StopShowFolderDropMenuTimer() { 1017 show_menu_task_factory_.InvalidateWeakPtrs(); 1018 } 1019 1020 void BrowserActionsContainer::StartShowFolderDropMenuTimer() { 1021 base::MessageLoop::current()->PostDelayedTask( 1022 FROM_HERE, 1023 base::Bind(&BrowserActionsContainer::ShowDropFolder, 1024 show_menu_task_factory_.GetWeakPtr()), 1025 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay())); 1026 } 1027 1028 void BrowserActionsContainer::ShowDropFolder() { 1029 DCHECK(!overflow_menu_); 1030 overflow_menu_ = 1031 new BrowserActionOverflowMenuController(this, 1032 browser_, 1033 chevron_, 1034 browser_action_views_, 1035 VisibleBrowserActions(), 1036 true); 1037 overflow_menu_->set_observer(this); 1038 overflow_menu_->RunMenu(GetWidget()); 1039 } 1040 1041 int BrowserActionsContainer::IconCountToWidth(int icons, 1042 bool display_chevron) const { 1043 if (icons < 0) 1044 icons = browser_action_views_.size(); 1045 if ((icons == 0) && !display_chevron) 1046 return ToolbarView::kStandardSpacing; 1047 int icons_size = 1048 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing); 1049 int chevron_size = chevron_ && display_chevron ? 1050 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0; 1051 // In overflow mode, our padding is to use item spacing on either end (just so 1052 // we can see the drop indicator). Otherwise we use the standard toolbar 1053 // spacing. 1054 // Note: These are actually the same thing, but, on the offchance one 1055 // changes, let's get it right. 1056 int padding = 1057 2 * (in_overflow_mode() ? kItemSpacing : ToolbarView::kStandardSpacing); 1058 return icons_size + chevron_size + padding; 1059 } 1060 1061 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const { 1062 // Check for widths large enough to show the entire icon set. 1063 if (pixels >= IconCountToWidth(-1, false)) 1064 return browser_action_views_.size(); 1065 1066 // We reserve space for the padding on either side of the toolbar... 1067 int available_space = pixels - (ToolbarView::kStandardSpacing * 2); 1068 // ... and, if the chevron is enabled, the chevron. 1069 if (chevron_) 1070 available_space -= (chevron_->GetPreferredSize().width() + kChevronSpacing); 1071 1072 // Now we add an extra between-item padding value so the space can be divided 1073 // evenly by (size of icon with padding). 1074 return static_cast<size_t>( 1075 std::max(0, available_space + kItemSpacing) / IconWidth(true)); 1076 } 1077 1078 int BrowserActionsContainer::MinimumNonemptyWidth() const { 1079 if (!chevron_) 1080 return ToolbarView::kStandardSpacing; 1081 return (ToolbarView::kStandardSpacing * 2) + kChevronSpacing + 1082 chevron_->GetPreferredSize().width(); 1083 } 1084 1085 void BrowserActionsContainer::Animate(gfx::Tween::Type tween_type, 1086 size_t num_visible_icons) { 1087 int target_size = IconCountToWidth(num_visible_icons, 1088 num_visible_icons < browser_action_views_.size()); 1089 if (resize_animation_ && !disable_animations_during_testing_) { 1090 // Animate! We have to set the animation_target_size_ after calling Reset(), 1091 // because that could end up calling AnimationEnded which clears the value. 1092 resize_animation_->Reset(); 1093 resize_animation_->SetTweenType(tween_type); 1094 animation_target_size_ = target_size; 1095 resize_animation_->Show(); 1096 } else { 1097 animation_target_size_ = target_size; 1098 AnimationEnded(resize_animation_.get()); 1099 } 1100 } 1101 1102 bool BrowserActionsContainer::ShouldDisplayBrowserAction( 1103 const Extension* extension) const { 1104 // Only display incognito-enabled extensions while in incognito mode. 1105 return !profile_->IsOffTheRecord() || 1106 extensions::util::IsIncognitoEnabled(extension->id(), profile_); 1107 } 1108 1109 size_t BrowserActionsContainer::GetIconCount() const { 1110 if (!model_) 1111 return 0u; 1112 1113 const extensions::ExtensionList& extensions = model_->toolbar_items(); 1114 1115 // Find the absolute value for the model's visible count. 1116 int model_visible_size = model_->GetVisibleIconCount(); 1117 size_t absolute_model_visible_size = 1118 model_visible_size == -1 ? extensions.size() : model_visible_size; 1119 1120 // Find the number of icons which could be displayed. 1121 size_t displayable_icon_count = 0u; 1122 size_t main_displayed = 0u; 1123 for (size_t i = 0; i < extensions.size(); ++i) { 1124 // Should there be an icon for this extension at all? 1125 if (ShouldDisplayBrowserAction(extensions[i].get())) { 1126 ++displayable_icon_count; 1127 // Should we display it on the main bar? If this is an incognito window, 1128 // icons have the same overflow status they do in a regular window. 1129 main_displayed += i < absolute_model_visible_size ? 1u : 0u; 1130 } 1131 } 1132 1133 // If this is an existing (initialized) container from an incognito profile, 1134 // we can't trust the model (because the incognito bars don't adjust model 1135 // settings). Instead, we go off what we currently have displayed. 1136 if (initialized_ && profile_->IsOffTheRecord()) { 1137 main_displayed = in_overflow_mode() ? 1138 main_container_->VisibleBrowserActionsAfterAnimation() : 1139 VisibleBrowserActionsAfterAnimation(); 1140 } 1141 1142 // The overflow displays any (displayable) icons not shown by the main bar. 1143 return in_overflow_mode() ? 1144 displayable_icon_count - main_displayed : main_displayed; 1145 } 1146