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