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