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