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