browser_actions_container.cc revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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/views/browser_actions_container.h" 6 7#include "app/l10n_util.h" 8#include "app/resource_bundle.h" 9#include "base/stl_util-inl.h" 10#include "base/string_util.h" 11#include "base/utf_string_conversions.h" 12#include "chrome/browser/browser_window.h" 13#include "chrome/browser/extensions/extension_browser_event_router.h" 14#include "chrome/browser/extensions/extension_host.h" 15#include "chrome/browser/extensions/extension_tabs_module.h" 16#include "chrome/browser/extensions/extension_service.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/browser/renderer_host/render_view_host.h" 19#include "chrome/browser/renderer_host/render_widget_host_view.h" 20#include "chrome/browser/tab_contents/tab_contents.h" 21#include "chrome/browser/themes/browser_theme_provider.h" 22#include "chrome/browser/ui/browser.h" 23#include "chrome/browser/ui/view_ids.h" 24#include "chrome/browser/ui/views/detachable_toolbar_view.h" 25#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h" 26#include "chrome/browser/ui/views/extensions/extension_popup.h" 27#include "chrome/browser/ui/views/toolbar_view.h" 28#include "chrome/common/extensions/extension_action.h" 29#include "chrome/common/extensions/extension_resource.h" 30#include "chrome/common/notification_source.h" 31#include "chrome/common/notification_type.h" 32#include "chrome/common/pref_names.h" 33#include "gfx/canvas.h" 34#include "gfx/canvas_skia.h" 35#include "grit/app_resources.h" 36#include "grit/generated_resources.h" 37#include "third_party/skia/include/core/SkBitmap.h" 38#include "third_party/skia/include/core/SkTypeface.h" 39#include "third_party/skia/include/effects/SkGradientShader.h" 40#include "ui/base/animation/slide_animation.h" 41#include "views/controls/button/menu_button.h" 42#include "views/controls/button/text_button.h" 43#include "views/controls/menu/menu_2.h" 44#include "views/drag_utils.h" 45#include "views/window/window.h" 46 47#include "grit/theme_resources.h" 48 49// Horizontal spacing between most items in the container, as well as after the 50// last item or chevron (if visible). 51static const int kItemSpacing = ToolbarView::kStandardSpacing; 52// Horizontal spacing before the chevron (if visible). 53static const int kChevronSpacing = kItemSpacing - 2; 54 55// static 56bool BrowserActionsContainer::disable_animations_during_testing_ = false; 57 58//////////////////////////////////////////////////////////////////////////////// 59// BrowserActionButton 60 61BrowserActionButton::BrowserActionButton(const Extension* extension, 62 BrowserActionsContainer* panel) 63 : ALLOW_THIS_IN_INITIALIZER_LIST( 64 MenuButton(this, std::wstring(), NULL, false)), 65 browser_action_(extension->browser_action()), 66 extension_(extension), 67 ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)), 68 showing_context_menu_(false), 69 panel_(panel) { 70 set_border(NULL); 71 set_alignment(TextButton::ALIGN_CENTER); 72 73 // No UpdateState() here because View hierarchy not setup yet. Our parent 74 // should call UpdateState() after creation. 75 76 registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, 77 Source<ExtensionAction>(browser_action_)); 78} 79 80void BrowserActionButton::Destroy() { 81 if (showing_context_menu_) { 82 context_menu_menu_->CancelMenu(); 83 MessageLoop::current()->DeleteSoon(FROM_HERE, this); 84 } else { 85 delete this; 86 } 87} 88 89void BrowserActionButton::ViewHierarchyChanged( 90 bool is_add, View* parent, View* child) { 91 if (is_add && child == this) { 92 // The Browser Action API does not allow the default icon path to be 93 // changed at runtime, so we can load this now and cache it. 94 std::string relative_path = browser_action_->default_icon_path(); 95 if (relative_path.empty()) 96 return; 97 98 // LoadImage is not guaranteed to be synchronous, so we might see the 99 // callback OnImageLoaded execute immediately. It (through UpdateState) 100 // expects GetParent() to return the owner for this button, so this 101 // function is as early as we can start this request. 102 tracker_.LoadImage(extension_, extension_->GetResource(relative_path), 103 gfx::Size(Extension::kBrowserActionIconMaxSize, 104 Extension::kBrowserActionIconMaxSize), 105 ImageLoadingTracker::DONT_CACHE); 106 } 107 108 MenuButton::ViewHierarchyChanged(is_add, parent, child); 109} 110 111void BrowserActionButton::ButtonPressed(views::Button* sender, 112 const views::Event& event) { 113 panel_->OnBrowserActionExecuted(this, false); 114} 115 116void BrowserActionButton::OnImageLoaded(SkBitmap* image, 117 ExtensionResource resource, 118 int index) { 119 if (image) 120 default_icon_ = *image; 121 122 // Call back to UpdateState() because a more specific icon might have been set 123 // while the load was outstanding. 124 UpdateState(); 125} 126 127void BrowserActionButton::UpdateState() { 128 int tab_id = panel_->GetCurrentTabId(); 129 if (tab_id < 0) 130 return; 131 132 SkBitmap icon(browser_action()->GetIcon(tab_id)); 133 if (icon.isNull()) 134 icon = default_icon_; 135 if (!icon.isNull()) { 136 SkPaint paint; 137 paint.setXfermode(SkXfermode::Create(SkXfermode::kSrcOver_Mode)); 138 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 139 140 SkBitmap bg; 141 rb.GetBitmapNamed(IDR_BROWSER_ACTION)->copyTo(&bg, 142 SkBitmap::kARGB_8888_Config); 143 SkCanvas bg_canvas(bg); 144 bg_canvas.drawBitmap(icon, SkIntToScalar((bg.width() - icon.width()) / 2), 145 SkIntToScalar((bg.height() - icon.height()) / 2), &paint); 146 SetIcon(bg); 147 148 SkBitmap bg_h; 149 rb.GetBitmapNamed(IDR_BROWSER_ACTION_H)->copyTo(&bg_h, 150 SkBitmap::kARGB_8888_Config); 151 SkCanvas bg_h_canvas(bg_h); 152 bg_h_canvas.drawBitmap(icon, 153 SkIntToScalar((bg_h.width() - icon.width()) / 2), 154 SkIntToScalar((bg_h.height() - icon.height()) / 2), &paint); 155 SetHoverIcon(bg_h); 156 157 SkBitmap bg_p; 158 rb.GetBitmapNamed(IDR_BROWSER_ACTION_P)->copyTo(&bg_p, 159 SkBitmap::kARGB_8888_Config); 160 SkCanvas bg_p_canvas(bg_p); 161 bg_p_canvas.drawBitmap(icon, 162 SkIntToScalar((bg_p.width() - icon.width()) / 2), 163 SkIntToScalar((bg_p.height() - icon.height()) / 2), &paint); 164 SetPushedIcon(bg_p); 165 } 166 167 // If the browser action name is empty, show the extension name instead. 168 std::wstring name = UTF8ToWide(browser_action()->GetTitle(tab_id)); 169 if (name.empty()) 170 name = UTF8ToWide(extension()->name()); 171 SetTooltipText(name); 172 SetAccessibleName(name); 173 GetParent()->SchedulePaint(); 174} 175 176void BrowserActionButton::Observe(NotificationType type, 177 const NotificationSource& source, 178 const NotificationDetails& details) { 179 DCHECK(type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED); 180 UpdateState(); 181 // The browser action may have become visible/hidden so we need to make 182 // sure the state gets updated. 183 panel_->OnBrowserActionVisibilityChanged(); 184} 185 186bool BrowserActionButton::IsPopup() { 187 int tab_id = panel_->GetCurrentTabId(); 188 return (tab_id < 0) ? false : browser_action_->HasPopup(tab_id); 189} 190 191GURL BrowserActionButton::GetPopupUrl() { 192 int tab_id = panel_->GetCurrentTabId(); 193 return (tab_id < 0) ? GURL() : browser_action_->GetPopupUrl(tab_id); 194} 195 196bool BrowserActionButton::Activate() { 197 if (!IsPopup()) 198 return true; 199 200 panel_->OnBrowserActionExecuted(this, false); 201 202 // TODO(erikkay): Run a nested modal loop while the mouse is down to 203 // enable menu-like drag-select behavior. 204 205 // The return value of this method is returned via OnMousePressed. 206 // We need to return false here since we're handing off focus to another 207 // widget/view, and true will grab it right back and try to send events 208 // to us. 209 return false; 210} 211 212bool BrowserActionButton::OnMousePressed(const views::MouseEvent& e) { 213 if (!e.IsRightMouseButton()) { 214 return IsPopup() ? 215 MenuButton::OnMousePressed(e) : TextButton::OnMousePressed(e); 216 } 217 218 // Get the top left point of this button in screen coordinates. 219 gfx::Point point = gfx::Point(0, 0); 220 ConvertPointToScreen(this, &point); 221 222 // Make the menu appear below the button. 223 point.Offset(0, height()); 224 225 ShowContextMenu(point, true); 226 return false; 227} 228 229void BrowserActionButton::OnMouseReleased(const views::MouseEvent& e, 230 bool canceled) { 231 if (IsPopup() || showing_context_menu_) { 232 // TODO(erikkay) this never actually gets called (probably because of the 233 // loss of focus). 234 MenuButton::OnMouseReleased(e, canceled); 235 } else { 236 TextButton::OnMouseReleased(e, canceled); 237 } 238} 239 240bool BrowserActionButton::OnKeyReleased(const views::KeyEvent& e) { 241 return IsPopup() ? 242 MenuButton::OnKeyReleased(e) : TextButton::OnKeyReleased(e); 243} 244 245void BrowserActionButton::OnMouseExited(const views::MouseEvent& e) { 246 if (IsPopup() || showing_context_menu_) 247 MenuButton::OnMouseExited(e); 248 else 249 TextButton::OnMouseExited(e); 250} 251 252void BrowserActionButton::ShowContextMenu(const gfx::Point& p, 253 bool is_mouse_gesture) { 254 showing_context_menu_ = true; 255 SetButtonPushed(); 256 257 // Reconstructs the menu every time because the menu's contents are dynamic. 258 context_menu_contents_ = 259 new ExtensionContextMenuModel(extension(), panel_->browser(), panel_); 260 context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get())); 261 context_menu_menu_->RunContextMenuAt(p); 262 263 SetButtonNotPushed(); 264 showing_context_menu_ = false; 265} 266 267void BrowserActionButton::SetButtonPushed() { 268 SetState(views::CustomButton::BS_PUSHED); 269 menu_visible_ = true; 270} 271 272void BrowserActionButton::SetButtonNotPushed() { 273 SetState(views::CustomButton::BS_NORMAL); 274 menu_visible_ = false; 275} 276 277BrowserActionButton::~BrowserActionButton() { 278} 279 280 281//////////////////////////////////////////////////////////////////////////////// 282// BrowserActionView 283 284BrowserActionView::BrowserActionView(const Extension* extension, 285 BrowserActionsContainer* panel) 286 : panel_(panel) { 287 button_ = new BrowserActionButton(extension, panel); 288 button_->SetDragController(panel_); 289 AddChildView(button_); 290 button_->UpdateState(); 291 SetAccessibleName(UTF16ToWide( 292 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_BROWSER_ACTION))); 293} 294 295BrowserActionView::~BrowserActionView() { 296 RemoveChildView(button_); 297 button_->Destroy(); 298} 299 300gfx::Canvas* BrowserActionView::GetIconWithBadge() { 301 int tab_id = panel_->GetCurrentTabId(); 302 303 SkBitmap icon = button_->extension()->browser_action()->GetIcon(tab_id); 304 if (icon.isNull()) 305 icon = button_->default_icon(); 306 307 gfx::Canvas* canvas = new gfx::CanvasSkia(icon.width(), icon.height(), false); 308 canvas->DrawBitmapInt(icon, 0, 0); 309 310 if (tab_id >= 0) { 311 gfx::Rect bounds(icon.width(), icon.height() + ToolbarView::kVertSpacing); 312 button_->extension()->browser_action()->PaintBadge(canvas, bounds, tab_id); 313 } 314 315 return canvas; 316} 317 318AccessibilityTypes::Role BrowserActionView::GetAccessibleRole() { 319 return AccessibilityTypes::ROLE_GROUPING; 320} 321 322void BrowserActionView::Layout() { 323 // We can't rely on button_->GetPreferredSize() here because that's not set 324 // correctly until the first call to 325 // BrowserActionsContainer::RefreshBrowserActionViews(), whereas this can be 326 // called before that when the initial bounds are set (and then not after, 327 // since the bounds don't change). So instead of setting the height from the 328 // button's preferred size, we use IconHeight(), since that's how big the 329 // button should be regardless of what it's displaying. 330 button_->SetBounds(0, ToolbarView::kVertSpacing, width(), 331 BrowserActionsContainer::IconHeight()); 332} 333 334void BrowserActionView::PaintChildren(gfx::Canvas* canvas) { 335 View::PaintChildren(canvas); 336 ExtensionAction* action = button()->browser_action(); 337 int tab_id = panel_->GetCurrentTabId(); 338 if (tab_id >= 0) 339 action->PaintBadge(canvas, gfx::Rect(width(), height()), tab_id); 340} 341 342//////////////////////////////////////////////////////////////////////////////// 343// BrowserActionsContainer 344 345BrowserActionsContainer::BrowserActionsContainer(Browser* browser, 346 View* owner_view) 347 : profile_(browser->profile()), 348 browser_(browser), 349 owner_view_(owner_view), 350 popup_(NULL), 351 popup_button_(NULL), 352 model_(NULL), 353 container_width_(0), 354 chevron_(NULL), 355 overflow_menu_(NULL), 356 suppress_chevron_(false), 357 resize_amount_(0), 358 animation_target_size_(0), 359 drop_indicator_position_(-1), 360 ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), 361 ALLOW_THIS_IN_INITIALIZER_LIST(show_menu_task_factory_(this)) { 362 SetID(VIEW_ID_BROWSER_ACTION_TOOLBAR); 363 364 if (profile_->GetExtensionService()) { 365 model_ = profile_->GetExtensionService()->toolbar_model(); 366 model_->AddObserver(this); 367 } 368 369 resize_animation_.reset(new ui::SlideAnimation(this)); 370 resize_area_ = new views::ResizeArea(this); 371 resize_area_->SetAccessibleName( 372 UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_SEPARATOR))); 373 AddChildView(resize_area_); 374 375 chevron_ = new views::MenuButton(NULL, std::wstring(), this, false); 376 chevron_->set_border(NULL); 377 chevron_->EnableCanvasFlippingForRTLUI(true); 378 chevron_->SetAccessibleName( 379 UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON))); 380 chevron_->SetVisible(false); 381 AddChildView(chevron_); 382 383 SetAccessibleName( 384 UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS))); 385} 386 387BrowserActionsContainer::~BrowserActionsContainer() { 388 if (model_) 389 model_->RemoveObserver(this); 390 StopShowFolderDropMenuTimer(); 391 HidePopup(); 392 DeleteBrowserActionViews(); 393} 394 395// Static. 396void BrowserActionsContainer::RegisterUserPrefs(PrefService* prefs) { 397 prefs->RegisterIntegerPref(prefs::kBrowserActionContainerWidth, 0); 398} 399 400void BrowserActionsContainer::Init() { 401 LoadImages(); 402 403 // We wait to set the container width until now so that the chevron images 404 // will be loaded. The width calculation needs to know the chevron size. 405 if (model_ && 406 !profile_->GetPrefs()->HasPrefPath(prefs::kExtensionToolbarSize)) { 407 // Migration code to the new VisibleIconCount pref. 408 // TODO(mpcomplete): remove this after users are upgraded to 5.0. 409 int predefined_width = 410 profile_->GetPrefs()->GetInteger(prefs::kBrowserActionContainerWidth); 411 if (predefined_width != 0) 412 model_->SetVisibleIconCount(WidthToIconCount(predefined_width)); 413 } 414 if (model_ && model_->extensions_initialized()) 415 SetContainerWidth(); 416} 417 418int BrowserActionsContainer::GetCurrentTabId() const { 419 TabContents* tab_contents = browser_->GetSelectedTabContents(); 420 return tab_contents ? tab_contents->controller().session_id().id() : -1; 421} 422 423BrowserActionView* BrowserActionsContainer::GetBrowserActionView( 424 ExtensionAction* action) { 425 for (BrowserActionViews::iterator iter = browser_action_views_.begin(); 426 iter != browser_action_views_.end(); ++iter) { 427 if ((*iter)->button()->browser_action() == action) 428 return *iter; 429 } 430 return NULL; 431} 432 433void BrowserActionsContainer::RefreshBrowserActionViews() { 434 for (size_t i = 0; i < browser_action_views_.size(); ++i) 435 browser_action_views_[i]->button()->UpdateState(); 436} 437 438void BrowserActionsContainer::CreateBrowserActionViews() { 439 DCHECK(browser_action_views_.empty()); 440 if (!model_) 441 return; 442 443 for (ExtensionList::iterator iter = model_->begin(); iter != model_->end(); 444 ++iter) { 445 if (!ShouldDisplayBrowserAction(*iter)) 446 continue; 447 448 BrowserActionView* view = new BrowserActionView(*iter, this); 449 browser_action_views_.push_back(view); 450 AddChildView(view); 451 } 452} 453 454void BrowserActionsContainer::DeleteBrowserActionViews() { 455 if (!browser_action_views_.empty()) { 456 for (size_t i = 0; i < browser_action_views_.size(); ++i) 457 RemoveChildView(browser_action_views_[i]); 458 STLDeleteContainerPointers(browser_action_views_.begin(), 459 browser_action_views_.end()); 460 browser_action_views_.clear(); 461 } 462} 463 464void BrowserActionsContainer::OnBrowserActionVisibilityChanged() { 465 SetVisible(!browser_action_views_.empty()); 466 owner_view_->Layout(); 467 owner_view_->SchedulePaint(); 468} 469 470size_t BrowserActionsContainer::VisibleBrowserActions() const { 471 size_t visible_actions = 0; 472 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 473 if (browser_action_views_[i]->IsVisible()) 474 ++visible_actions; 475 } 476 return visible_actions; 477} 478 479void BrowserActionsContainer::OnBrowserActionExecuted( 480 BrowserActionButton* button, 481 bool inspect_with_devtools) { 482 ExtensionAction* browser_action = button->browser_action(); 483 484 // Popups just display. No notification to the extension. 485 // TODO(erikkay): should there be? 486 if (!button->IsPopup()) { 487 ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( 488 profile_, browser_action->extension_id(), browser_); 489 return; 490 } 491 492 // If we're showing the same popup, just hide it and return. 493 bool same_showing = popup_ && button == popup_button_; 494 495 // Always hide the current popup, even if it's not the same. 496 // Only one popup should be visible at a time. 497 HidePopup(); 498 499 if (same_showing) 500 return; 501 502 // We can get the execute event for browser actions that are not visible, 503 // since buttons can be activated from the overflow menu (chevron). In that 504 // case we show the popup as originating from the chevron. 505 View* reference_view = button->GetParent()->IsVisible() ? button : chevron_; 506 gfx::Point origin; 507 View::ConvertPointToScreen(reference_view, &origin); 508 gfx::Rect rect = reference_view->bounds(); 509 rect.set_origin(origin); 510 511 gfx::NativeWindow frame_window = browser_->window()->GetNativeHandle(); 512 BubbleBorder::ArrowLocation arrow_location = base::i18n::IsRTL() ? 513 BubbleBorder::TOP_LEFT : BubbleBorder::TOP_RIGHT; 514 515 popup_ = ExtensionPopup::Show(button->GetPopupUrl(), browser_, 516 browser_->profile(), frame_window, rect, arrow_location, true, 517 inspect_with_devtools, ExtensionPopup::BUBBLE_CHROME, this); 518 popup_button_ = button; 519 popup_button_->SetButtonPushed(); 520} 521 522gfx::Size BrowserActionsContainer::GetPreferredSize() { 523 if (browser_action_views_.empty()) 524 return gfx::Size(ToolbarView::kStandardSpacing, 0); 525 526 // We calculate the size of the view by taking the current width and 527 // subtracting resize_amount_ (the latter represents how far the user is 528 // resizing the view or, if animating the snapping, how far to animate it). 529 // But we also clamp it to a minimum size and the maximum size, so that the 530 // container can never shrink too far or take up more space than it needs. In 531 // other words: ContainerMinSize() < width() - resize < ClampTo(MAX). 532 int clamped_width = std::min( 533 std::max(ContainerMinSize(), container_width_ - resize_amount_), 534 IconCountToWidth(-1, false)); 535 return gfx::Size(clamped_width, 0); 536} 537 538void BrowserActionsContainer::Layout() { 539 if (browser_action_views_.empty()) { 540 SetVisible(false); 541 return; 542 } 543 544 SetVisible(true); 545 resize_area_->SetBounds(0, ToolbarView::kVertSpacing, kItemSpacing, 546 IconHeight()); 547 548 // If the icons don't all fit, show the chevron (unless suppressed). 549 int max_x = GetPreferredSize().width(); 550 if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_) { 551 chevron_->SetVisible(true); 552 gfx::Size chevron_size(chevron_->GetPreferredSize()); 553 max_x -= 554 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing; 555 chevron_->SetBounds( 556 width() - ToolbarView::kStandardSpacing - chevron_size.width(), 557 ToolbarView::kVertSpacing, chevron_size.width(), chevron_size.height()); 558 } else { 559 chevron_->SetVisible(false); 560 } 561 562 // Now draw the icons for the browser actions in the available space. 563 int icon_width = IconWidth(false); 564 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 565 BrowserActionView* view = browser_action_views_[i]; 566 int x = ToolbarView::kStandardSpacing + (i * IconWidth(true)); 567 if (x + icon_width <= max_x) { 568 view->SetBounds(x, 0, icon_width, height()); 569 view->SetVisible(true); 570 } else { 571 view->SetVisible(false); 572 } 573 } 574} 575 576void BrowserActionsContainer::Paint(gfx::Canvas* canvas) { 577 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while 578 // dragging (like we do for tab dragging). 579 if (drop_indicator_position_ > -1) { 580 // The two-pixel width drop indicator. 581 static const int kDropIndicatorWidth = 2; 582 gfx::Rect indicator_bounds( 583 drop_indicator_position_ - (kDropIndicatorWidth / 2), 584 ToolbarView::kVertSpacing, kDropIndicatorWidth, IconHeight()); 585 586 // Color of the drop indicator. 587 static const SkColor kDropIndicatorColor = SK_ColorBLACK; 588 canvas->FillRectInt(kDropIndicatorColor, indicator_bounds.x(), 589 indicator_bounds.y(), indicator_bounds.width(), 590 indicator_bounds.height()); 591 } 592} 593 594void BrowserActionsContainer::ViewHierarchyChanged(bool is_add, 595 views::View* parent, 596 views::View* child) { 597 // No extensions (e.g., incognito). 598 if (!model_) 599 return; 600 601 if (is_add && child == this) { 602 // Initial toolbar button creation and placement in the widget hierarchy. 603 // We do this here instead of in the constructor because AddBrowserAction 604 // calls Layout on the Toolbar, which needs this object to be constructed 605 // before its Layout function is called. 606 CreateBrowserActionViews(); 607 } 608} 609 610bool BrowserActionsContainer::GetDropFormats( 611 int* formats, 612 std::set<OSExchangeData::CustomFormat>* custom_formats) { 613 custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat()); 614 615 return true; 616} 617 618bool BrowserActionsContainer::AreDropTypesRequired() { 619 return true; 620} 621 622bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) { 623 BrowserActionDragData drop_data; 624 return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false; 625} 626 627void BrowserActionsContainer::OnDragEntered( 628 const views::DropTargetEvent& event) { 629} 630 631int BrowserActionsContainer::OnDragUpdated( 632 const views::DropTargetEvent& event) { 633 // First check if we are above the chevron (overflow) menu. 634 if (GetViewForPoint(event.location()) == chevron_) { 635 if (show_menu_task_factory_.empty() && !overflow_menu_) 636 StartShowFolderDropMenuTimer(); 637 return DragDropTypes::DRAG_MOVE; 638 } 639 StopShowFolderDropMenuTimer(); 640 641 // Figure out where to display the indicator. This is a complex calculation: 642 643 // First, we figure out how much space is to the left of the icon area, so we 644 // can calculate the true offset into the icon area. 645 int width_before_icons = ToolbarView::kStandardSpacing + 646 (base::i18n::IsRTL() ? 647 (chevron_->GetPreferredSize().width() + kChevronSpacing) : 0); 648 int offset_into_icon_area = event.x() - width_before_icons; 649 650 // Next, we determine which icon to place the indicator in front of. We want 651 // to place the indicator in front of icon n when the cursor is between the 652 // midpoints of icons (n - 1) and n. To do this we take the offset into the 653 // icon area and transform it as follows: 654 // 655 // Real icon area: 656 // 0 a * b c 657 // | | | | 658 // |[IC|ON] [IC|ON] [IC|ON] 659 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc. 660 // Here the "*" represents the offset into the icon area, and since it's 661 // between a and b, we want to return "1". 662 // 663 // Transformed "icon area": 664 // 0 a * b c 665 // | | | | 666 // |[ICON] |[ICON] |[ICON] | 667 // If we shift both our offset and our divider points later by half an icon 668 // plus one spacing unit, then it becomes very easy to calculate how many 669 // divider points we've passed, because they're the multiples of "one icon 670 // plus padding". 671 int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) + 672 kItemSpacing) / IconWidth(true); 673 674 // Because the user can drag outside the container bounds, we need to clamp to 675 // the valid range. Note that the maximum allowable value is (num icons), not 676 // (num icons - 1), because we represent the indicator being past the last 677 // icon as being "before the (last + 1) icon". 678 int before_icon = std::min(std::max(before_icon_unclamped, 0), 679 static_cast<int>(VisibleBrowserActions())); 680 681 // Now we convert back to a pixel offset into the container. We want to place 682 // the center of the drop indicator at the midpoint of the space before our 683 // chosen icon. 684 SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) - 685 (kItemSpacing / 2)); 686 687 return DragDropTypes::DRAG_MOVE; 688} 689 690void BrowserActionsContainer::OnDragExited() { 691 StopShowFolderDropMenuTimer(); 692 drop_indicator_position_ = -1; 693 SchedulePaint(); 694} 695 696int BrowserActionsContainer::OnPerformDrop( 697 const views::DropTargetEvent& event) { 698 BrowserActionDragData data; 699 if (!data.Read(event.GetData())) 700 return DragDropTypes::DRAG_NONE; 701 702 // Make sure we have the same view as we started with. 703 DCHECK_EQ(browser_action_views_[data.index()]->button()->extension()->id(), 704 data.id()); 705 DCHECK(model_); 706 707 size_t i = 0; 708 for (; i < browser_action_views_.size(); ++i) { 709 int view_x = 710 browser_action_views_[i]->GetBounds(APPLY_MIRRORING_TRANSFORMATION).x(); 711 if (!browser_action_views_[i]->IsVisible() || 712 (base::i18n::IsRTL() ? (view_x < drop_indicator_position_) : 713 (view_x >= drop_indicator_position_))) { 714 // We have reached the end of the visible icons or found one that has a 715 // higher x position than the drop point. 716 break; 717 } 718 } 719 720 // |i| now points to the item to the right of the drop indicator*, which is 721 // correct when dragging an icon to the left. When dragging to the right, 722 // however, we want the icon being dragged to get the index of the item to 723 // the left of the drop indicator, so we subtract one. 724 // * Well, it can also point to the end, but not when dragging to the left. :) 725 if (i > data.index()) 726 --i; 727 728 if (profile_->IsOffTheRecord()) 729 i = model_->IncognitoIndexToOriginal(i); 730 731 model_->MoveBrowserAction( 732 browser_action_views_[data.index()]->button()->extension(), i); 733 734 OnDragExited(); // Perform clean up after dragging. 735 return DragDropTypes::DRAG_MOVE; 736} 737 738void BrowserActionsContainer::OnThemeChanged() { 739 LoadImages(); 740} 741 742AccessibilityTypes::Role BrowserActionsContainer::GetAccessibleRole() { 743 return AccessibilityTypes::ROLE_GROUPING; 744} 745 746void BrowserActionsContainer::RunMenu(View* source, const gfx::Point& pt) { 747 if (source == chevron_) { 748 overflow_menu_ = new BrowserActionOverflowMenuController( 749 this, chevron_, browser_action_views_, VisibleBrowserActions()); 750 overflow_menu_->set_observer(this); 751 overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), false); 752 } 753} 754 755void BrowserActionsContainer::WriteDragData(View* sender, 756 const gfx::Point& press_pt, 757 OSExchangeData* data) { 758 DCHECK(data); 759 760 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 761 BrowserActionButton* button = browser_action_views_[i]->button(); 762 if (button == sender) { 763 // Set the dragging image for the icon. 764 scoped_ptr<gfx::Canvas> canvas( 765 browser_action_views_[i]->GetIconWithBadge()); 766 drag_utils::SetDragImageOnDataObject(*canvas, button->size(), press_pt, 767 data); 768 769 // Fill in the remaining info. 770 BrowserActionDragData drag_data( 771 browser_action_views_[i]->button()->extension()->id(), i); 772 drag_data.Write(profile_, data); 773 break; 774 } 775 } 776} 777 778int BrowserActionsContainer::GetDragOperations(View* sender, 779 const gfx::Point& p) { 780 return DragDropTypes::DRAG_MOVE; 781} 782 783bool BrowserActionsContainer::CanStartDrag(View* sender, 784 const gfx::Point& press_pt, 785 const gfx::Point& p) { 786 return true; 787} 788 789void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) { 790 if (!done_resizing) { 791 resize_amount_ = resize_amount; 792 OnBrowserActionVisibilityChanged(); 793 return; 794 } 795 796 // Up until now we've only been modifying the resize_amount, but now it is 797 // time to set the container size to the size we have resized to, and then 798 // animate to the nearest icon count size if necessary (which may be 0). 799 int max_width = IconCountToWidth(-1, false); 800 container_width_ = 801 std::min(std::max(0, container_width_ - resize_amount), max_width); 802 SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT, 803 WidthToIconCount(container_width_)); 804} 805 806void BrowserActionsContainer::AnimationProgressed( 807 const ui::Animation* animation) { 808 DCHECK_EQ(resize_animation_.get(), animation); 809 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() * 810 (container_width_ - animation_target_size_)); 811 OnBrowserActionVisibilityChanged(); 812} 813 814void BrowserActionsContainer::AnimationEnded(const ui::Animation* animation) { 815 container_width_ = animation_target_size_; 816 animation_target_size_ = 0; 817 resize_amount_ = 0; 818 OnBrowserActionVisibilityChanged(); 819 suppress_chevron_ = false; 820} 821 822void BrowserActionsContainer::NotifyMenuDeleted( 823 BrowserActionOverflowMenuController* controller) { 824 DCHECK(controller == overflow_menu_); 825 overflow_menu_ = NULL; 826} 827 828void BrowserActionsContainer::InspectPopup(ExtensionAction* action) { 829 OnBrowserActionExecuted(GetBrowserActionView(action)->button(), true); 830} 831 832void BrowserActionsContainer::ExtensionPopupIsClosing(ExtensionPopup* popup) { 833 // ExtensionPopup is ref-counted, so we don't need to delete it. 834 DCHECK_EQ(popup_, popup); 835 popup_ = NULL; 836 popup_button_->SetButtonNotPushed(); 837 popup_button_ = NULL; 838} 839 840void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id, 841 size_t new_index) { 842 ExtensionService* service = profile_->GetExtensionService(); 843 if (service) { 844 const Extension* extension = service->GetExtensionById(extension_id, false); 845 model_->MoveBrowserAction(extension, new_index); 846 SchedulePaint(); 847 } 848} 849 850void BrowserActionsContainer::HidePopup() { 851 if (popup_) 852 popup_->Close(); 853} 854 855void BrowserActionsContainer::TestExecuteBrowserAction(int index) { 856 BrowserActionButton* button = browser_action_views_[index]->button(); 857 OnBrowserActionExecuted(button, false); 858} 859 860void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) { 861 model_->SetVisibleIconCount(icons); 862 chevron_->SetVisible(icons < browser_action_views_.size()); 863 container_width_ = IconCountToWidth(icons, chevron_->IsVisible()); 864 Layout(); 865 SchedulePaint(); 866} 867 868// static 869int BrowserActionsContainer::IconWidth(bool include_padding) { 870 static bool initialized = false; 871 static int icon_width = 0; 872 if (!initialized) { 873 initialized = true; 874 icon_width = ResourceBundle::GetSharedInstance().GetBitmapNamed( 875 IDR_BROWSER_ACTION)->width(); 876 } 877 return icon_width + (include_padding ? kItemSpacing : 0); 878} 879 880// static 881int BrowserActionsContainer::IconHeight() { 882 static bool initialized = false; 883 static int icon_height = 0; 884 if (!initialized) { 885 initialized = true; 886 icon_height = ResourceBundle::GetSharedInstance().GetBitmapNamed( 887 IDR_BROWSER_ACTION)->height(); 888 } 889 return icon_height; 890} 891 892void BrowserActionsContainer::BrowserActionAdded(const Extension* extension, 893 int index) { 894#if defined(DEBUG) 895 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 896 DCHECK(browser_action_views_[i]->button()->extension() != extension) << 897 "Asked to add a browser action view for an extension that already " 898 "exists."; 899 } 900#endif 901 CloseOverflowMenu(); 902 903 if (!ShouldDisplayBrowserAction(extension)) 904 return; 905 906 size_t visible_actions = VisibleBrowserActions(); 907 908 // Add the new browser action to the vector and the view hierarchy. 909 if (profile_->IsOffTheRecord()) 910 index = model_->OriginalIndexToIncognito(index); 911 BrowserActionView* view = new BrowserActionView(extension, this); 912 browser_action_views_.insert(browser_action_views_.begin() + index, view); 913 AddChildView(index, view); 914 915 // If we are still initializing the container, don't bother animating. 916 if (!model_->extensions_initialized()) 917 return; 918 919 // Enlarge the container if it was already at maximum size and we're not in 920 // the middle of upgrading. 921 if ((model_->GetVisibleIconCount() < 0) && 922 !profile_->GetExtensionService()->IsBeingUpgraded(extension)) { 923 suppress_chevron_ = true; 924 SaveDesiredSizeAndAnimate(ui::Tween::LINEAR, visible_actions + 1); 925 } else { 926 // Just redraw the (possibly modified) visible icon set. 927 OnBrowserActionVisibilityChanged(); 928 } 929} 930 931void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) { 932 CloseOverflowMenu(); 933 934 if (popup_ && popup_->host()->extension() == extension) 935 HidePopup(); 936 937 size_t visible_actions = VisibleBrowserActions(); 938 for (BrowserActionViews::iterator iter = browser_action_views_.begin(); 939 iter != browser_action_views_.end(); ++iter) { 940 if ((*iter)->button()->extension() == extension) { 941 RemoveChildView(*iter); 942 delete *iter; 943 browser_action_views_.erase(iter); 944 945 // If the extension is being upgraded we don't want the bar to shrink 946 // because the icon is just going to get re-added to the same location. 947 if (profile_->GetExtensionService()->IsBeingUpgraded(extension)) 948 return; 949 950 if (browser_action_views_.size() > visible_actions) { 951 // If we have more icons than we can show, then we must not be changing 952 // the container size (since we either removed an icon from the main 953 // area and one from the overflow list will have shifted in, or we 954 // removed an entry directly from the overflow list). 955 OnBrowserActionVisibilityChanged(); 956 } else { 957 // Either we went from overflow to no-overflow, or we shrunk the no- 958 // overflow container by 1. Either way the size changed, so animate. 959 chevron_->SetVisible(false); 960 SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT, 961 browser_action_views_.size()); 962 } 963 return; 964 } 965 } 966} 967 968void BrowserActionsContainer::BrowserActionMoved(const Extension* extension, 969 int index) { 970 if (!ShouldDisplayBrowserAction(extension)) 971 return; 972 973 if (profile_->IsOffTheRecord()) 974 index = model_->OriginalIndexToIncognito(index); 975 976 DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size())); 977 978 DeleteBrowserActionViews(); 979 CreateBrowserActionViews(); 980 Layout(); 981 SchedulePaint(); 982} 983 984void BrowserActionsContainer::ModelLoaded() { 985 SetContainerWidth(); 986} 987 988void BrowserActionsContainer::LoadImages() { 989 ThemeProvider* tp = GetThemeProvider(); 990 chevron_->SetIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW)); 991 chevron_->SetHoverIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_H)); 992 chevron_->SetPushedIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_P)); 993} 994 995void BrowserActionsContainer::SetContainerWidth() { 996 int visible_actions = model_->GetVisibleIconCount(); 997 if (visible_actions < 0) // All icons should be visible. 998 visible_actions = model_->size(); 999 chevron_->SetVisible(static_cast<size_t>(visible_actions) < model_->size()); 1000 container_width_ = IconCountToWidth(visible_actions, chevron_->IsVisible()); 1001} 1002 1003void BrowserActionsContainer::CloseOverflowMenu() { 1004 if (overflow_menu_) 1005 overflow_menu_->CancelMenu(); 1006} 1007 1008void BrowserActionsContainer::StopShowFolderDropMenuTimer() { 1009 show_menu_task_factory_.RevokeAll(); 1010} 1011 1012void BrowserActionsContainer::StartShowFolderDropMenuTimer() { 1013 int delay = View::GetMenuShowDelay(); 1014 MessageLoop::current()->PostDelayedTask(FROM_HERE, 1015 show_menu_task_factory_.NewRunnableMethod( 1016 &BrowserActionsContainer::ShowDropFolder), 1017 delay); 1018} 1019 1020void BrowserActionsContainer::ShowDropFolder() { 1021 DCHECK(!overflow_menu_); 1022 SetDropIndicator(-1); 1023 overflow_menu_ = new BrowserActionOverflowMenuController( 1024 this, chevron_, browser_action_views_, VisibleBrowserActions()); 1025 overflow_menu_->set_observer(this); 1026 overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), true); 1027} 1028 1029void BrowserActionsContainer::SetDropIndicator(int x_pos) { 1030 if (drop_indicator_position_ != x_pos) { 1031 drop_indicator_position_ = x_pos; 1032 SchedulePaint(); 1033 } 1034} 1035 1036int BrowserActionsContainer::IconCountToWidth(int icons, 1037 bool display_chevron) const { 1038 if (icons < 0) 1039 icons = browser_action_views_.size(); 1040 if ((icons == 0) && !display_chevron) 1041 return ToolbarView::kStandardSpacing; 1042 int icons_size = 1043 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing); 1044 int chevron_size = display_chevron ? 1045 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0; 1046 return ToolbarView::kStandardSpacing + icons_size + chevron_size + 1047 ToolbarView::kStandardSpacing; 1048} 1049 1050size_t BrowserActionsContainer::WidthToIconCount(int pixels) const { 1051 // Check for widths large enough to show the entire icon set. 1052 if (pixels >= IconCountToWidth(-1, false)) 1053 return browser_action_views_.size(); 1054 1055 // We need to reserve space for the resize area, chevron, and the spacing on 1056 // either side of the chevron. 1057 int available_space = pixels - ToolbarView::kStandardSpacing - 1058 chevron_->GetPreferredSize().width() - kChevronSpacing - 1059 ToolbarView::kStandardSpacing; 1060 // Now we add an extra between-item padding value so the space can be divided 1061 // evenly by (size of icon with padding). 1062 return static_cast<size_t>( 1063 std::max(0, available_space + kItemSpacing) / IconWidth(true)); 1064} 1065 1066int BrowserActionsContainer::ContainerMinSize() const { 1067 return ToolbarView::kStandardSpacing + kChevronSpacing + 1068 chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing; 1069} 1070 1071void BrowserActionsContainer::SaveDesiredSizeAndAnimate( 1072 ui::Tween::Type tween_type, 1073 size_t num_visible_icons) { 1074 // Save off the desired number of visible icons. We do this now instead of at 1075 // the end of the animation so that even if the browser is shut down while 1076 // animating, the right value will be restored on next run. 1077 // NOTE: Don't save the icon count in incognito because there may be fewer 1078 // icons in that mode. The result is that the container in a normal window is 1079 // always at least as wide as in an incognito window. 1080 if (!profile_->IsOffTheRecord()) 1081 model_->SetVisibleIconCount(num_visible_icons); 1082 1083 int target_size = IconCountToWidth(num_visible_icons, 1084 num_visible_icons < browser_action_views_.size()); 1085 if (!disable_animations_during_testing_) { 1086 // Animate! We have to set the animation_target_size_ after calling Reset(), 1087 // because that could end up calling AnimationEnded which clears the value. 1088 resize_animation_->Reset(); 1089 resize_animation_->SetTweenType(tween_type); 1090 animation_target_size_ = target_size; 1091 resize_animation_->Show(); 1092 } else { 1093 animation_target_size_ = target_size; 1094 AnimationEnded(resize_animation_.get()); 1095 } 1096} 1097 1098bool BrowserActionsContainer::ShouldDisplayBrowserAction( 1099 const Extension* extension) { 1100 // Only display incognito-enabled extensions while in incognito mode. 1101 return (!profile_->IsOffTheRecord() || 1102 profile_->GetExtensionService()->IsIncognitoEnabled(extension)); 1103} 1104