1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/views/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_service.h" 11 #include "chrome/browser/extensions/extension_system.h" 12 #include "chrome/browser/extensions/tab_helper.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/browser/sessions/session_tab_helper.h" 15 #include "chrome/browser/ui/browser.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/browser_action_view.h" 19 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h" 20 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h" 21 #include "chrome/browser/ui/views/extensions/extension_popup.h" 22 #include "chrome/browser/ui/views/toolbar_view.h" 23 #include "chrome/common/pref_names.h" 24 #include "grit/generated_resources.h" 25 #include "grit/theme_resources.h" 26 #include "grit/ui_resources.h" 27 #include "ui/base/accessibility/accessible_view_state.h" 28 #include "ui/base/animation/slide_animation.h" 29 #include "ui/base/dragdrop/drag_utils.h" 30 #include "ui/base/l10n/l10n_util.h" 31 #include "ui/base/resource/resource_bundle.h" 32 #include "ui/base/theme_provider.h" 33 #include "ui/gfx/canvas.h" 34 #include "ui/views/controls/resize_area.h" 35 #include "ui/views/metrics.h" 36 #include "ui/views/widget/widget.h" 37 38 using extensions::Extension; 39 40 namespace { 41 42 // Horizontal spacing between most items in the container, as well as after the 43 // last item or chevron (if visible). 44 const int kItemSpacing = ToolbarView::kStandardSpacing; 45 46 // Horizontal spacing before the chevron (if visible). 47 const int kChevronSpacing = kItemSpacing - 2; 48 49 } // namespace 50 51 // static 52 bool BrowserActionsContainer::disable_animations_during_testing_ = false; 53 54 //////////////////////////////////////////////////////////////////////////////// 55 // BrowserActionsContainer 56 57 BrowserActionsContainer::BrowserActionsContainer(Browser* browser, 58 View* owner_view) 59 : profile_(browser->profile()), 60 browser_(browser), 61 owner_view_(owner_view), 62 popup_(NULL), 63 popup_button_(NULL), 64 model_(NULL), 65 container_width_(0), 66 chevron_(NULL), 67 overflow_menu_(NULL), 68 suppress_chevron_(false), 69 resize_amount_(0), 70 animation_target_size_(0), 71 drop_indicator_position_(-1), 72 task_factory_(this), 73 show_menu_task_factory_(this) { 74 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR); 75 76 ExtensionService* service = 77 extensions::ExtensionSystem::Get(profile_)->extension_service(); 78 if (service) { 79 model_ = service->toolbar_model(); 80 model_->AddObserver(this); 81 } 82 83 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews( 84 browser->profile(), 85 owner_view->GetFocusManager(), 86 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS, 87 this)), 88 89 resize_animation_.reset(new ui::SlideAnimation(this)); 90 resize_area_ = new views::ResizeArea(this); 91 AddChildView(resize_area_); 92 93 chevron_ = new views::MenuButton(NULL, string16(), this, false); 94 chevron_->set_border(NULL); 95 chevron_->EnableCanvasFlippingForRTLUI(true); 96 chevron_->SetAccessibleName( 97 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON)); 98 chevron_->SetVisible(false); 99 AddChildView(chevron_); 100 } 101 102 BrowserActionsContainer::~BrowserActionsContainer() { 103 if (overflow_menu_) 104 overflow_menu_->set_observer(NULL); 105 if (model_) 106 model_->RemoveObserver(this); 107 StopShowFolderDropMenuTimer(); 108 if (popup_) 109 popup_->GetWidget()->RemoveObserver(this); 110 HidePopup(); 111 DeleteBrowserActionViews(); 112 } 113 114 void BrowserActionsContainer::Init() { 115 LoadImages(); 116 117 // We wait to set the container width until now so that the chevron images 118 // will be loaded. The width calculation needs to know the chevron size. 119 if (model_ && 120 !profile_->GetPrefs()->HasPrefPath(prefs::kExtensionToolbarSize)) { 121 // Migration code to the new VisibleIconCount pref. 122 // TODO(mpcomplete): remove this after users are upgraded to 5.0. 123 int predefined_width = 124 profile_->GetPrefs()->GetInteger(prefs::kBrowserActionContainerWidth); 125 if (predefined_width != 0) 126 model_->SetVisibleIconCount(WidthToIconCount(predefined_width)); 127 } 128 if (model_ && model_->extensions_initialized()) 129 SetContainerWidth(); 130 } 131 132 BrowserActionView* BrowserActionsContainer::GetBrowserActionView( 133 ExtensionAction* action) { 134 for (BrowserActionViews::iterator i(browser_action_views_.begin()); 135 i != browser_action_views_.end(); ++i) { 136 if ((*i)->button()->browser_action() == action) 137 return *i; 138 } 139 return NULL; 140 } 141 142 void BrowserActionsContainer::RefreshBrowserActionViews() { 143 for (size_t i = 0; i < browser_action_views_.size(); ++i) 144 browser_action_views_[i]->button()->UpdateState(); 145 } 146 147 void BrowserActionsContainer::CreateBrowserActionViews() { 148 DCHECK(browser_action_views_.empty()); 149 if (!model_) 150 return; 151 152 const extensions::ExtensionList& toolbar_items = model_->toolbar_items(); 153 for (extensions::ExtensionList::const_iterator i(toolbar_items.begin()); 154 i != toolbar_items.end(); ++i) { 155 if (!ShouldDisplayBrowserAction(i->get())) 156 continue; 157 158 BrowserActionView* view = new BrowserActionView(i->get(), browser_, this); 159 browser_action_views_.push_back(view); 160 AddChildView(view); 161 } 162 } 163 164 void BrowserActionsContainer::DeleteBrowserActionViews() { 165 HidePopup(); 166 STLDeleteElements(&browser_action_views_); 167 } 168 169 size_t BrowserActionsContainer::VisibleBrowserActions() const { 170 size_t visible_actions = 0; 171 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 172 if (browser_action_views_[i]->visible()) 173 ++visible_actions; 174 } 175 return visible_actions; 176 } 177 178 gfx::Size BrowserActionsContainer::GetPreferredSize() { 179 if (browser_action_views_.empty()) 180 return gfx::Size(ToolbarView::kStandardSpacing, 0); 181 182 // We calculate the size of the view by taking the current width and 183 // subtracting resize_amount_ (the latter represents how far the user is 184 // resizing the view or, if animating the snapping, how far to animate it). 185 // But we also clamp it to a minimum size and the maximum size, so that the 186 // container can never shrink too far or take up more space than it needs. In 187 // other words: ContainerMinSize() < width() - resize < ClampTo(MAX). 188 int clamped_width = std::min( 189 std::max(ContainerMinSize(), container_width_ - resize_amount_), 190 IconCountToWidth(-1, false)); 191 return gfx::Size(clamped_width, 0); 192 } 193 194 void BrowserActionsContainer::Layout() { 195 if (browser_action_views_.empty()) { 196 SetVisible(false); 197 return; 198 } 199 200 SetVisible(true); 201 resize_area_->SetBounds(0, ToolbarView::kVertSpacing, kItemSpacing, 202 IconHeight()); 203 204 // If the icons don't all fit, show the chevron (unless suppressed). 205 int max_x = GetPreferredSize().width(); 206 if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_) { 207 chevron_->SetVisible(true); 208 gfx::Size chevron_size(chevron_->GetPreferredSize()); 209 max_x -= 210 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing; 211 chevron_->SetBounds( 212 width() - ToolbarView::kStandardSpacing - chevron_size.width(), 213 ToolbarView::kVertSpacing, chevron_size.width(), chevron_size.height()); 214 } else { 215 chevron_->SetVisible(false); 216 } 217 218 // Now draw the icons for the browser actions in the available space. 219 int icon_width = IconWidth(false); 220 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 221 BrowserActionView* view = browser_action_views_[i]; 222 int x = ToolbarView::kStandardSpacing + (i * IconWidth(true)); 223 if (x + icon_width <= max_x) { 224 view->SetBounds(x, 0, icon_width, height()); 225 view->SetVisible(true); 226 } else { 227 view->SetVisible(false); 228 } 229 } 230 } 231 232 bool BrowserActionsContainer::GetDropFormats( 233 int* formats, 234 std::set<OSExchangeData::CustomFormat>* custom_formats) { 235 custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat()); 236 237 return true; 238 } 239 240 bool BrowserActionsContainer::AreDropTypesRequired() { 241 return true; 242 } 243 244 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) { 245 BrowserActionDragData drop_data; 246 return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false; 247 } 248 249 void BrowserActionsContainer::OnDragEntered( 250 const ui::DropTargetEvent& event) { 251 } 252 253 int BrowserActionsContainer::OnDragUpdated( 254 const ui::DropTargetEvent& event) { 255 // First check if we are above the chevron (overflow) menu. 256 if (GetEventHandlerForPoint(event.location()) == chevron_) { 257 if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_) 258 StartShowFolderDropMenuTimer(); 259 return ui::DragDropTypes::DRAG_MOVE; 260 } 261 StopShowFolderDropMenuTimer(); 262 263 // Figure out where to display the indicator. This is a complex calculation: 264 265 // First, we figure out how much space is to the left of the icon area, so we 266 // can calculate the true offset into the icon area. 267 int width_before_icons = ToolbarView::kStandardSpacing + 268 (base::i18n::IsRTL() ? 269 (chevron_->GetPreferredSize().width() + kChevronSpacing) : 0); 270 int offset_into_icon_area = event.x() - width_before_icons; 271 272 // Next, we determine which icon to place the indicator in front of. We want 273 // to place the indicator in front of icon n when the cursor is between the 274 // midpoints of icons (n - 1) and n. To do this we take the offset into the 275 // icon area and transform it as follows: 276 // 277 // Real icon area: 278 // 0 a * b c 279 // | | | | 280 // |[IC|ON] [IC|ON] [IC|ON] 281 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc. 282 // Here the "*" represents the offset into the icon area, and since it's 283 // between a and b, we want to return "1". 284 // 285 // Transformed "icon area": 286 // 0 a * b c 287 // | | | | 288 // |[ICON] |[ICON] |[ICON] | 289 // If we shift both our offset and our divider points later by half an icon 290 // plus one spacing unit, then it becomes very easy to calculate how many 291 // divider points we've passed, because they're the multiples of "one icon 292 // plus padding". 293 int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) + 294 kItemSpacing) / IconWidth(true); 295 296 // Because the user can drag outside the container bounds, we need to clamp to 297 // the valid range. Note that the maximum allowable value is (num icons), not 298 // (num icons - 1), because we represent the indicator being past the last 299 // icon as being "before the (last + 1) icon". 300 int before_icon = std::min(std::max(before_icon_unclamped, 0), 301 static_cast<int>(VisibleBrowserActions())); 302 303 // Now we convert back to a pixel offset into the container. We want to place 304 // the center of the drop indicator at the midpoint of the space before our 305 // chosen icon. 306 SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) - 307 (kItemSpacing / 2)); 308 309 return ui::DragDropTypes::DRAG_MOVE; 310 } 311 312 void BrowserActionsContainer::OnDragExited() { 313 StopShowFolderDropMenuTimer(); 314 drop_indicator_position_ = -1; 315 SchedulePaint(); 316 } 317 318 int BrowserActionsContainer::OnPerformDrop( 319 const ui::DropTargetEvent& event) { 320 BrowserActionDragData data; 321 if (!data.Read(event.data())) 322 return ui::DragDropTypes::DRAG_NONE; 323 324 // Make sure we have the same view as we started with. 325 DCHECK_EQ(browser_action_views_[data.index()]->button()->extension()->id(), 326 data.id()); 327 DCHECK(model_); 328 329 size_t i = 0; 330 for (; i < browser_action_views_.size(); ++i) { 331 int view_x = browser_action_views_[i]->GetMirroredBounds().x(); 332 if (!browser_action_views_[i]->visible() || 333 (base::i18n::IsRTL() ? (view_x < drop_indicator_position_) : 334 (view_x >= drop_indicator_position_))) { 335 // We have reached the end of the visible icons or found one that has a 336 // higher x position than the drop point. 337 break; 338 } 339 } 340 341 // |i| now points to the item to the right of the drop indicator*, which is 342 // correct when dragging an icon to the left. When dragging to the right, 343 // however, we want the icon being dragged to get the index of the item to 344 // the left of the drop indicator, so we subtract one. 345 // * Well, it can also point to the end, but not when dragging to the left. :) 346 if (i > data.index()) 347 --i; 348 349 if (profile_->IsOffTheRecord()) 350 i = model_->IncognitoIndexToOriginal(i); 351 352 model_->MoveBrowserAction( 353 browser_action_views_[data.index()]->button()->extension(), i); 354 355 OnDragExited(); // Perform clean up after dragging. 356 return ui::DragDropTypes::DRAG_MOVE; 357 } 358 359 void BrowserActionsContainer::GetAccessibleState( 360 ui::AccessibleViewState* state) { 361 state->role = ui::AccessibilityTypes::ROLE_GROUPING; 362 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS); 363 } 364 365 void BrowserActionsContainer::OnMenuButtonClicked(views::View* source, 366 const gfx::Point& point) { 367 if (source == chevron_) { 368 overflow_menu_ = new BrowserActionOverflowMenuController( 369 this, browser_, chevron_, browser_action_views_, 370 VisibleBrowserActions()); 371 overflow_menu_->set_observer(this); 372 overflow_menu_->RunMenu(GetWidget(), false); 373 } 374 } 375 376 void BrowserActionsContainer::WriteDragDataForView(View* sender, 377 const gfx::Point& press_pt, 378 OSExchangeData* data) { 379 DCHECK(data); 380 381 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 382 BrowserActionButton* button = browser_action_views_[i]->button(); 383 if (button == sender) { 384 // Set the dragging image for the icon. 385 gfx::ImageSkia badge(browser_action_views_[i]->GetIconWithBadge()); 386 drag_utils::SetDragImageOnDataObject(badge, button->size(), 387 press_pt.OffsetFromOrigin(), 388 data); 389 390 // Fill in the remaining info. 391 BrowserActionDragData drag_data( 392 browser_action_views_[i]->button()->extension()->id(), i); 393 drag_data.Write(profile_, data); 394 break; 395 } 396 } 397 } 398 399 int BrowserActionsContainer::GetDragOperationsForView(View* sender, 400 const gfx::Point& p) { 401 return ui::DragDropTypes::DRAG_MOVE; 402 } 403 404 bool BrowserActionsContainer::CanStartDragForView(View* sender, 405 const gfx::Point& press_pt, 406 const gfx::Point& p) { 407 return true; 408 } 409 410 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) { 411 if (!done_resizing) { 412 resize_amount_ = resize_amount; 413 OnBrowserActionVisibilityChanged(); 414 return; 415 } 416 417 // Up until now we've only been modifying the resize_amount, but now it is 418 // time to set the container size to the size we have resized to, and then 419 // animate to the nearest icon count size if necessary (which may be 0). 420 int max_width = IconCountToWidth(-1, false); 421 container_width_ = 422 std::min(std::max(0, container_width_ - resize_amount), max_width); 423 SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT, 424 WidthToIconCount(container_width_)); 425 } 426 427 void BrowserActionsContainer::AnimationProgressed( 428 const ui::Animation* animation) { 429 DCHECK_EQ(resize_animation_.get(), animation); 430 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() * 431 (container_width_ - animation_target_size_)); 432 OnBrowserActionVisibilityChanged(); 433 } 434 435 void BrowserActionsContainer::AnimationEnded(const ui::Animation* animation) { 436 container_width_ = animation_target_size_; 437 animation_target_size_ = 0; 438 resize_amount_ = 0; 439 OnBrowserActionVisibilityChanged(); 440 suppress_chevron_ = false; 441 } 442 443 void BrowserActionsContainer::NotifyMenuDeleted( 444 BrowserActionOverflowMenuController* controller) { 445 DCHECK_EQ(overflow_menu_, controller); 446 overflow_menu_ = NULL; 447 } 448 449 void BrowserActionsContainer::OnWidgetDestroying(views::Widget* widget) { 450 DCHECK_EQ(popup_->GetWidget(), widget); 451 popup_->GetWidget()->RemoveObserver(this); 452 popup_ = NULL; 453 // |popup_button_| is NULL if the extension has been removed. 454 if (popup_button_) { 455 popup_button_->SetButtonNotPushed(); 456 popup_button_ = NULL; 457 } 458 } 459 460 void BrowserActionsContainer::InspectPopup(ExtensionAction* action) { 461 BrowserActionView* view = GetBrowserActionView(action); 462 ShowPopup(view->button(), ExtensionPopup::SHOW_AND_INSPECT); 463 } 464 465 int BrowserActionsContainer::GetCurrentTabId() const { 466 content::WebContents* active_tab = 467 browser_->tab_strip_model()->GetActiveWebContents(); 468 if (!active_tab) 469 return -1; 470 471 return SessionTabHelper::FromWebContents(active_tab)->session_id().id(); 472 } 473 474 void BrowserActionsContainer::OnBrowserActionExecuted( 475 BrowserActionButton* button) { 476 ShowPopup(button, ExtensionPopup::SHOW); 477 } 478 479 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() { 480 SetVisible(!browser_action_views_.empty()); 481 owner_view_->Layout(); 482 owner_view_->SchedulePaint(); 483 } 484 485 gfx::Point BrowserActionsContainer::GetViewContentOffset() const { 486 return gfx::Point(0, ToolbarView::kVertSpacing); 487 } 488 489 extensions::ActiveTabPermissionGranter* 490 BrowserActionsContainer::GetActiveTabPermissionGranter() { 491 content::WebContents* web_contents = 492 browser_->tab_strip_model()->GetActiveWebContents(); 493 if (!web_contents) 494 return NULL; 495 return extensions::TabHelper::FromWebContents(web_contents)-> 496 active_tab_permission_granter(); 497 } 498 499 void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id, 500 size_t new_index) { 501 ExtensionService* service = 502 extensions::ExtensionSystem::Get(profile_)->extension_service(); 503 if (service) { 504 const Extension* extension = service->GetExtensionById(extension_id, false); 505 model_->MoveBrowserAction(extension, new_index); 506 SchedulePaint(); 507 } 508 } 509 510 void BrowserActionsContainer::HidePopup() { 511 // Remove this as an observer and clear |popup_| and |popup_button_| here, 512 // since we might change them before OnWidgetDestroying() gets called. 513 if (popup_) { 514 popup_->GetWidget()->RemoveObserver(this); 515 popup_->GetWidget()->Close(); 516 popup_ = NULL; 517 } 518 if (popup_button_) { 519 popup_button_->SetButtonNotPushed(); 520 popup_button_ = NULL; 521 } 522 } 523 524 void BrowserActionsContainer::TestExecuteBrowserAction(int index) { 525 BrowserActionButton* button = browser_action_views_[index]->button(); 526 OnBrowserActionExecuted(button); 527 } 528 529 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) { 530 model_->SetVisibleIconCount(icons); 531 chevron_->SetVisible(icons < browser_action_views_.size()); 532 container_width_ = IconCountToWidth(icons, chevron_->visible()); 533 Layout(); 534 SchedulePaint(); 535 } 536 537 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) { 538 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while 539 // dragging (like we do for tab dragging). 540 if (drop_indicator_position_ > -1) { 541 // The two-pixel width drop indicator. 542 static const int kDropIndicatorWidth = 2; 543 gfx::Rect indicator_bounds( 544 drop_indicator_position_ - (kDropIndicatorWidth / 2), 545 ToolbarView::kVertSpacing, kDropIndicatorWidth, IconHeight()); 546 547 // Color of the drop indicator. 548 static const SkColor kDropIndicatorColor = SK_ColorBLACK; 549 canvas->FillRect(indicator_bounds, kDropIndicatorColor); 550 } 551 } 552 553 void BrowserActionsContainer::OnThemeChanged() { 554 LoadImages(); 555 } 556 557 void BrowserActionsContainer::ViewHierarchyChanged( 558 const ViewHierarchyChangedDetails& details) { 559 // No extensions (e.g., incognito). 560 if (!model_) 561 return; 562 563 if (details.is_add && details.child == this) { 564 // Initial toolbar button creation and placement in the widget hierarchy. 565 // We do this here instead of in the constructor because AddBrowserAction 566 // calls Layout on the Toolbar, which needs this object to be constructed 567 // before its Layout function is called. 568 CreateBrowserActionViews(); 569 } 570 } 571 572 // static 573 int BrowserActionsContainer::IconWidth(bool include_padding) { 574 static bool initialized = false; 575 static int icon_width = 0; 576 if (!initialized) { 577 initialized = true; 578 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 579 IDR_BROWSER_ACTION)->width(); 580 } 581 return icon_width + (include_padding ? kItemSpacing : 0); 582 } 583 584 // static 585 int BrowserActionsContainer::IconHeight() { 586 static bool initialized = false; 587 static int icon_height = 0; 588 if (!initialized) { 589 initialized = true; 590 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 591 IDR_BROWSER_ACTION)->height(); 592 } 593 return icon_height; 594 } 595 596 void BrowserActionsContainer::BrowserActionAdded(const Extension* extension, 597 int index) { 598 #if defined(DEBUG) 599 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 600 DCHECK(browser_action_views_[i]->button()->extension() != extension) << 601 "Asked to add a browser action view for an extension that already " 602 "exists."; 603 } 604 #endif 605 CloseOverflowMenu(); 606 607 if (!ShouldDisplayBrowserAction(extension)) 608 return; 609 610 size_t visible_actions = VisibleBrowserActions(); 611 612 // Add the new browser action to the vector and the view hierarchy. 613 if (profile_->IsOffTheRecord()) 614 index = model_->OriginalIndexToIncognito(index); 615 BrowserActionView* view = new BrowserActionView(extension, browser_, this); 616 browser_action_views_.insert(browser_action_views_.begin() + index, view); 617 AddChildViewAt(view, index); 618 619 // If we are still initializing the container, don't bother animating. 620 if (!model_->extensions_initialized()) 621 return; 622 623 // Enlarge the container if it was already at maximum size and we're not in 624 // the middle of upgrading. 625 if ((model_->GetVisibleIconCount() < 0) && 626 !extensions::ExtensionSystem::Get(profile_)->extension_service()-> 627 IsBeingUpgraded(extension)) { 628 suppress_chevron_ = true; 629 SaveDesiredSizeAndAnimate(ui::Tween::LINEAR, visible_actions + 1); 630 } else { 631 // Just redraw the (possibly modified) visible icon set. 632 OnBrowserActionVisibilityChanged(); 633 } 634 } 635 636 void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) { 637 CloseOverflowMenu(); 638 639 if (popup_ && popup_->host()->extension() == extension) 640 HidePopup(); 641 642 size_t visible_actions = VisibleBrowserActions(); 643 for (BrowserActionViews::iterator i(browser_action_views_.begin()); 644 i != browser_action_views_.end(); ++i) { 645 if ((*i)->button()->extension() == extension) { 646 delete *i; 647 browser_action_views_.erase(i); 648 649 // If the extension is being upgraded we don't want the bar to shrink 650 // because the icon is just going to get re-added to the same location. 651 if (extensions::ExtensionSystem::Get(profile_)->extension_service()-> 652 IsBeingUpgraded(extension)) 653 return; 654 655 if (browser_action_views_.size() > visible_actions) { 656 // If we have more icons than we can show, then we must not be changing 657 // the container size (since we either removed an icon from the main 658 // area and one from the overflow list will have shifted in, or we 659 // removed an entry directly from the overflow list). 660 OnBrowserActionVisibilityChanged(); 661 } else { 662 // Either we went from overflow to no-overflow, or we shrunk the no- 663 // overflow container by 1. Either way the size changed, so animate. 664 chevron_->SetVisible(false); 665 SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT, 666 browser_action_views_.size()); 667 } 668 return; 669 } 670 } 671 } 672 673 void BrowserActionsContainer::BrowserActionMoved(const Extension* extension, 674 int index) { 675 if (!ShouldDisplayBrowserAction(extension)) 676 return; 677 678 if (profile_->IsOffTheRecord()) 679 index = model_->OriginalIndexToIncognito(index); 680 681 DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size())); 682 683 DeleteBrowserActionViews(); 684 CreateBrowserActionViews(); 685 Layout(); 686 SchedulePaint(); 687 } 688 689 void BrowserActionsContainer::ModelLoaded() { 690 SetContainerWidth(); 691 } 692 693 void BrowserActionsContainer::LoadImages() { 694 ui::ThemeProvider* tp = GetThemeProvider(); 695 chevron_->SetIcon(*tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW)); 696 chevron_->SetHoverIcon(*tp->GetImageSkiaNamed( 697 IDR_BROWSER_ACTIONS_OVERFLOW_H)); 698 chevron_->SetPushedIcon(*tp->GetImageSkiaNamed( 699 IDR_BROWSER_ACTIONS_OVERFLOW_P)); 700 } 701 702 void BrowserActionsContainer::SetContainerWidth() { 703 int visible_actions = model_->GetVisibleIconCount(); 704 if (visible_actions < 0) // All icons should be visible. 705 visible_actions = model_->toolbar_items().size(); 706 chevron_->SetVisible( 707 static_cast<size_t>(visible_actions) < model_->toolbar_items().size()); 708 container_width_ = IconCountToWidth(visible_actions, chevron_->visible()); 709 } 710 711 void BrowserActionsContainer::CloseOverflowMenu() { 712 if (overflow_menu_) 713 overflow_menu_->CancelMenu(); 714 } 715 716 void BrowserActionsContainer::StopShowFolderDropMenuTimer() { 717 show_menu_task_factory_.InvalidateWeakPtrs(); 718 } 719 720 void BrowserActionsContainer::StartShowFolderDropMenuTimer() { 721 base::MessageLoop::current()->PostDelayedTask( 722 FROM_HERE, 723 base::Bind(&BrowserActionsContainer::ShowDropFolder, 724 show_menu_task_factory_.GetWeakPtr()), 725 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay())); 726 } 727 728 void BrowserActionsContainer::ShowDropFolder() { 729 DCHECK(!overflow_menu_); 730 SetDropIndicator(-1); 731 overflow_menu_ = new BrowserActionOverflowMenuController( 732 this, browser_, chevron_, browser_action_views_, VisibleBrowserActions()); 733 overflow_menu_->set_observer(this); 734 overflow_menu_->RunMenu(GetWidget(), true); 735 } 736 737 void BrowserActionsContainer::SetDropIndicator(int x_pos) { 738 if (drop_indicator_position_ != x_pos) { 739 drop_indicator_position_ = x_pos; 740 SchedulePaint(); 741 } 742 } 743 744 int BrowserActionsContainer::IconCountToWidth(int icons, 745 bool display_chevron) const { 746 if (icons < 0) 747 icons = browser_action_views_.size(); 748 if ((icons == 0) && !display_chevron) 749 return ToolbarView::kStandardSpacing; 750 int icons_size = 751 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing); 752 int chevron_size = display_chevron ? 753 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0; 754 return ToolbarView::kStandardSpacing + icons_size + chevron_size + 755 ToolbarView::kStandardSpacing; 756 } 757 758 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const { 759 // Check for widths large enough to show the entire icon set. 760 if (pixels >= IconCountToWidth(-1, false)) 761 return browser_action_views_.size(); 762 763 // We need to reserve space for the resize area, chevron, and the spacing on 764 // either side of the chevron. 765 int available_space = pixels - ToolbarView::kStandardSpacing - 766 chevron_->GetPreferredSize().width() - kChevronSpacing - 767 ToolbarView::kStandardSpacing; 768 // Now we add an extra between-item padding value so the space can be divided 769 // evenly by (size of icon with padding). 770 return static_cast<size_t>( 771 std::max(0, available_space + kItemSpacing) / IconWidth(true)); 772 } 773 774 int BrowserActionsContainer::ContainerMinSize() const { 775 return ToolbarView::kStandardSpacing + kChevronSpacing + 776 chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing; 777 } 778 779 void BrowserActionsContainer::SaveDesiredSizeAndAnimate( 780 ui::Tween::Type tween_type, 781 size_t num_visible_icons) { 782 // Save off the desired number of visible icons. We do this now instead of at 783 // the end of the animation so that even if the browser is shut down while 784 // animating, the right value will be restored on next run. 785 // NOTE: Don't save the icon count in incognito because there may be fewer 786 // icons in that mode. The result is that the container in a normal window is 787 // always at least as wide as in an incognito window. 788 if (!profile_->IsOffTheRecord()) 789 model_->SetVisibleIconCount(num_visible_icons); 790 791 int target_size = IconCountToWidth(num_visible_icons, 792 num_visible_icons < browser_action_views_.size()); 793 if (!disable_animations_during_testing_) { 794 // Animate! We have to set the animation_target_size_ after calling Reset(), 795 // because that could end up calling AnimationEnded which clears the value. 796 resize_animation_->Reset(); 797 resize_animation_->SetTweenType(tween_type); 798 animation_target_size_ = target_size; 799 resize_animation_->Show(); 800 } else { 801 animation_target_size_ = target_size; 802 AnimationEnded(resize_animation_.get()); 803 } 804 } 805 806 bool BrowserActionsContainer::ShouldDisplayBrowserAction( 807 const Extension* extension) { 808 // Only display incognito-enabled extensions while in incognito mode. 809 return 810 (!profile_->IsOffTheRecord() || 811 extensions::ExtensionSystem::Get(profile_)->extension_service()-> 812 IsIncognitoEnabled(extension->id())); 813 } 814 815 void BrowserActionsContainer::ShowPopup( 816 BrowserActionButton* button, 817 ExtensionPopup::ShowAction show_action) { 818 const Extension* extension = button->extension(); 819 GURL popup_url; 820 if (model_->ExecuteBrowserAction(extension, browser_, &popup_url) != 821 ExtensionToolbarModel::ACTION_SHOW_POPUP) { 822 return; 823 } 824 825 // If we're showing the same popup, just hide it and return. 826 bool same_showing = popup_ && button == popup_button_; 827 828 // Always hide the current popup, even if it's not the same. 829 // Only one popup should be visible at a time. 830 HidePopup(); 831 832 if (same_showing) 833 return; 834 835 // We can get the execute event for browser actions that are not visible, 836 // since buttons can be activated from the overflow menu (chevron). In that 837 // case we show the popup as originating from the chevron. 838 View* reference_view = button->parent()->visible() ? button : chevron_; 839 views::BubbleBorder::Arrow arrow = base::i18n::IsRTL() ? 840 views::BubbleBorder::TOP_LEFT : views::BubbleBorder::TOP_RIGHT; 841 popup_ = ExtensionPopup::ShowPopup(popup_url, 842 browser_, 843 reference_view, 844 arrow, 845 show_action); 846 popup_->GetWidget()->AddObserver(this); 847 popup_button_ = button; 848 popup_button_->SetButtonPushed(); 849 } 850