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