browser_actions_container.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
13c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Copyright 2013 The Chromium Authors. All rights reserved.
23c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Use of this source code is governed by a BSD-style license that can be
33c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// found in the LICENSE file.
43c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
53c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
63c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
73c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/compiler_specific.h"
83c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/stl_util.h"
93c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/extension_action_manager.h"
103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/extension_util.h"
113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/extension_view_host.h"
123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/tab_helper.h"
133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/profiles/profile.h"
143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/browser.h"
153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/browser_window.h"
163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/tabs/tab_strip_model.h"
173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/view_ids.h"
183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/extensions/extension_popup.h"
213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/common/extensions/command.h"
243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/extension_registry.h"
253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/extension_system.h"
263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/runtime_data.h"
273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/common/feature_switch.h"
283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "grit/generated_resources.h"
293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "grit/theme_resources.h"
303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "third_party/skia/include/core/SkColor.h"
313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/accessibility/ax_view_state.h"
323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/dragdrop/drag_utils.h"
333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/l10n/l10n_util.h"
343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/nine_image_painter_factory.h"
353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/resource/resource_bundle.h"
363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/theme_provider.h"
373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/gfx/animation/slide_animation.h"
383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/gfx/canvas.h"
393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/gfx/geometry/rect.h"
403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/resources/grit/ui_resources.h"
413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/controls/button/label_button_border.h"
423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/controls/button/menu_button.h"
433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/controls/resize_area.h"
443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/metrics.h"
453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/painter.h"
463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/widget/widget.h"
473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
483c827367444ee418f129b2c238299f49d3264554Jarkko Poyryusing extensions::Extension;
493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
503c827367444ee418f129b2c238299f49d3264554Jarkko Poyrynamespace {
513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Horizontal spacing between most items in the container, as well as after the
533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// last item or chevron (if visible).
54const int kItemSpacing = ToolbarView::kStandardSpacing;
55
56// Horizontal spacing before the chevron (if visible).
57const int kChevronSpacing = kItemSpacing - 2;
58
59// The maximum number of icons to show per row when in overflow mode (showing
60// icons in the application menu).
61// TODO(devlin): Compute the right number of icons to show, depending on the
62//               menu width.
63#if defined(OS_LINUX)
64const int kIconsPerMenuRow = 8;  // The menu on Linux is wider.
65#else
66const int kIconsPerMenuRow = 7;
67#endif
68
69// Returns the ExtensionAction for the given |extension|.
70ExtensionAction* GetExtensionAction(const Extension& extension,
71                                    Profile* profile) {
72  extensions::ExtensionActionManager* action_manager =
73      extensions::ExtensionActionManager::Get(profile);
74  ExtensionAction* action = action_manager->GetBrowserAction(extension);
75  return action ? action : action_manager->GetPageAction(extension);
76}
77
78// A version of MenuButton with almost empty insets to fit properly on the
79// toolbar.
80class ChevronMenuButton : public views::MenuButton {
81 public:
82  ChevronMenuButton(views::ButtonListener* listener,
83                    const base::string16& text,
84                    views::MenuButtonListener* menu_button_listener,
85                    bool show_menu_marker)
86      : views::MenuButton(listener,
87                          text,
88                          menu_button_listener,
89                          show_menu_marker) {
90  }
91
92  virtual ~ChevronMenuButton() {}
93
94  virtual scoped_ptr<views::LabelButtonBorder> CreateDefaultBorder() const
95      OVERRIDE {
96    // The chevron resource was designed to not have any insets.
97    scoped_ptr<views::LabelButtonBorder> border =
98        views::MenuButton::CreateDefaultBorder();
99    border->set_insets(gfx::Insets());
100    return border.Pass();
101  }
102
103 private:
104  DISALLOW_COPY_AND_ASSIGN(ChevronMenuButton);
105};
106
107}  // namespace
108
109////////////////////////////////////////////////////////////////////////////////
110// BrowserActionsContainer::DropPosition
111
112struct BrowserActionsContainer::DropPosition {
113  DropPosition(size_t row, size_t icon_in_row);
114
115  // The (0-indexed) row into which the action will be dropped.
116  size_t row;
117
118  // The (0-indexed) icon in the row before the action will be dropped.
119  size_t icon_in_row;
120};
121
122BrowserActionsContainer::DropPosition::DropPosition(
123    size_t row, size_t icon_in_row)
124    : row(row), icon_in_row(icon_in_row) {
125}
126
127////////////////////////////////////////////////////////////////////////////////
128// BrowserActionsContainer
129
130// static
131bool BrowserActionsContainer::disable_animations_during_testing_ = false;
132
133BrowserActionsContainer::BrowserActionsContainer(
134    Browser* browser,
135    View* owner_view,
136    BrowserActionsContainer* main_container)
137    : profile_(browser->profile()),
138      browser_(browser),
139      owner_view_(owner_view),
140      main_container_(main_container),
141      popup_owner_(NULL),
142      model_(NULL),
143      container_width_(0),
144      resize_area_(NULL),
145      chevron_(NULL),
146      overflow_menu_(NULL),
147      suppress_chevron_(false),
148      resize_amount_(0),
149      animation_target_size_(0),
150      task_factory_(this),
151      show_menu_task_factory_(this) {
152  set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
153
154  model_ = extensions::ExtensionToolbarModel::Get(browser->profile());
155  if (model_)
156    model_->AddObserver(this);
157
158  bool overflow_experiment =
159      extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
160  DCHECK(!in_overflow_mode() || overflow_experiment);
161
162  if (!in_overflow_mode()) {
163    extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
164        browser->profile(),
165        owner_view->GetFocusManager(),
166        extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
167        this));
168
169    resize_animation_.reset(new gfx::SlideAnimation(this));
170    resize_area_ = new views::ResizeArea(this);
171    AddChildView(resize_area_);
172
173    // 'Main' mode doesn't need a chevron overflow when overflow is shown inside
174    // the Chrome menu.
175    if (!overflow_experiment) {
176      chevron_ = new ChevronMenuButton(NULL, base::string16(), this, false);
177      chevron_->SetBorder(views::Border::NullBorder());
178      chevron_->EnableCanvasFlippingForRTLUI(true);
179      chevron_->SetAccessibleName(
180          l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
181      chevron_->SetVisible(false);
182      AddChildView(chevron_);
183    }
184  }
185}
186
187BrowserActionsContainer::~BrowserActionsContainer() {
188  FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
189                    observers_,
190                    OnBrowserActionsContainerDestroyed());
191
192  if (overflow_menu_)
193    overflow_menu_->set_observer(NULL);
194  if (model_)
195    model_->RemoveObserver(this);
196  StopShowFolderDropMenuTimer();
197  HideActivePopup();
198  DeleteBrowserActionViews();
199}
200
201void BrowserActionsContainer::Init() {
202  LoadImages();
203
204  // We wait to set the container width until now so that the chevron images
205  // will be loaded.  The width calculation needs to know the chevron size.
206  if (model_ && model_->extensions_initialized())
207    SetContainerWidth();
208}
209
210BrowserActionView* BrowserActionsContainer::GetBrowserActionView(
211    ExtensionAction* action) {
212  for (BrowserActionViews::iterator i(browser_action_views_.begin());
213       i != browser_action_views_.end(); ++i) {
214    if ((*i)->extension_action() == action)
215      return *i;
216  }
217  return NULL;
218}
219
220void BrowserActionsContainer::RefreshBrowserActionViews() {
221  for (size_t i = 0; i < browser_action_views_.size(); ++i)
222    browser_action_views_[i]->UpdateState();
223}
224
225void BrowserActionsContainer::CreateBrowserActionViews() {
226  DCHECK(browser_action_views_.empty());
227  if (!model_)
228    return;
229
230  const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
231  for (extensions::ExtensionList::const_iterator i(toolbar_items.begin());
232       i != toolbar_items.end(); ++i) {
233    if (!ShouldDisplayBrowserAction(i->get()))
234      continue;
235
236    BrowserActionView* view =
237        new BrowserActionView(i->get(),
238                              GetExtensionAction(*i->get(), profile_),
239                              browser_,
240                              this);
241    browser_action_views_.push_back(view);
242    AddChildView(view);
243  }
244}
245
246void BrowserActionsContainer::DeleteBrowserActionViews() {
247  HideActivePopup();
248  if (overflow_menu_)
249    overflow_menu_->NotifyBrowserActionViewsDeleting();
250  STLDeleteElements(&browser_action_views_);
251}
252
253size_t BrowserActionsContainer::VisibleBrowserActions() const {
254  size_t visible_actions = 0;
255  for (size_t i = 0; i < browser_action_views_.size(); ++i) {
256    if (browser_action_views_[i]->visible())
257      ++visible_actions;
258  }
259  return visible_actions;
260}
261
262size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
263  if (!animating())
264    return VisibleBrowserActions();
265
266  return WidthToIconCount(animation_target_size_);
267}
268
269void BrowserActionsContainer::ExecuteExtensionCommand(
270    const extensions::Extension* extension,
271    const extensions::Command& command) {
272  // Global commands are handled by the ExtensionCommandsGlobalRegistry
273  // instance.
274  DCHECK(!command.global());
275  extension_keybinding_registry_->ExecuteCommand(extension->id(),
276                                                 command.accelerator());
277}
278
279bool BrowserActionsContainer::ShownInsideMenu() const {
280  return in_overflow_mode();
281}
282
283void BrowserActionsContainer::OnBrowserActionViewDragDone() {
284  // We notify here as well as in OnPerformDrop because the dragged view is
285  // removed in OnPerformDrop, so it will never get its OnDragDone() call.
286  // TODO(devlin): we should see about fixing that.
287  FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
288                    observers_,
289                    OnBrowserActionDragDone());
290}
291
292views::View* BrowserActionsContainer::GetOverflowReferenceView() {
293  // We should only need an overflow reference when using the traditional
294  // chevron overflow.
295  DCHECK(chevron_);
296  return chevron_;
297}
298
299void BrowserActionsContainer::SetPopupOwner(BrowserActionView* popup_owner) {
300  // We should never be setting a popup owner when one already exists.
301  DCHECK(!popup_owner_ || !popup_owner);
302  popup_owner_ = popup_owner;
303}
304
305void BrowserActionsContainer::HideActivePopup() {
306  if (popup_owner_)
307    popup_owner_->view_controller()->HidePopup();
308}
309
310void BrowserActionsContainer::AddObserver(
311    BrowserActionsContainerObserver* observer) {
312  observers_.AddObserver(observer);
313}
314
315void BrowserActionsContainer::RemoveObserver(
316    BrowserActionsContainerObserver* observer) {
317  observers_.RemoveObserver(observer);
318}
319
320gfx::Size BrowserActionsContainer::GetPreferredSize() const {
321  size_t icon_count = GetIconCount();
322
323  // If there are no actions to show, or we are in overflow mode and the main
324  // container is already showing them all, then no further work is required.
325  if (icon_count == 0)
326    return gfx::Size();
327
328  if (in_overflow_mode()) {
329    // When in overflow, y is multiline, so the pixel count is IconHeight()
330    // times the number of rows needed.
331    return gfx::Size(
332        IconCountToWidth(kIconsPerMenuRow, false),
333        (((icon_count - 1) / kIconsPerMenuRow) + 1) * IconHeight());
334  }
335
336  // We calculate the size of the view by taking the current width and
337  // subtracting resize_amount_ (the latter represents how far the user is
338  // resizing the view or, if animating the snapping, how far to animate it).
339  // But we also clamp it to a minimum size and the maximum size, so that the
340  // container can never shrink too far or take up more space than it needs.
341  // In other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX).
342  int preferred_width = std::min(
343      std::max(MinimumNonemptyWidth(), container_width_ - resize_amount_),
344      IconCountToWidth(-1, false));
345  return gfx::Size(preferred_width, IconHeight());
346}
347
348gfx::Size BrowserActionsContainer::GetMinimumSize() const {
349  int min_width = std::min(MinimumNonemptyWidth(), IconCountToWidth(-1, false));
350  return gfx::Size(min_width, IconHeight());
351}
352
353void BrowserActionsContainer::Layout() {
354  if (browser_action_views_.empty()) {
355    SetVisible(false);
356    return;
357  }
358
359  SetVisible(true);
360  if (resize_area_)
361    resize_area_->SetBounds(0, 0, kItemSpacing, height());
362
363  // If the icons don't all fit, show the chevron (unless suppressed).
364  int max_x = GetPreferredSize().width();
365  if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_ && chevron_) {
366    chevron_->SetVisible(true);
367    gfx::Size chevron_size(chevron_->GetPreferredSize());
368    max_x -=
369        ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
370    chevron_->SetBounds(
371        width() - ToolbarView::kStandardSpacing - chevron_size.width(),
372        0,
373        chevron_size.width(),
374        chevron_size.height());
375  } else if (chevron_) {
376    chevron_->SetVisible(false);
377  }
378
379  // Now draw the icons for the browser actions in the available space.
380  int icon_width = IconWidth(false);
381  if (in_overflow_mode()) {
382    for (size_t i = 0;
383         i < main_container_->VisibleBrowserActionsAfterAnimation(); ++i) {
384      // Ensure that any browser actions shown in the main view are hidden in
385      // the overflow view.
386      browser_action_views_[i]->SetVisible(false);
387    }
388
389    for (size_t i = main_container_->VisibleBrowserActionsAfterAnimation();
390         i < browser_action_views_.size(); ++i) {
391      BrowserActionView* view = browser_action_views_[i];
392      size_t index = i - main_container_->VisibleBrowserActionsAfterAnimation();
393      int row_index = static_cast<int>(index) / kIconsPerMenuRow;
394      int x = kItemSpacing + (index * IconWidth(true)) -
395          (row_index * IconWidth(true) * kIconsPerMenuRow);
396      gfx::Rect rect_bounds(
397          x, IconHeight() * row_index, icon_width, IconHeight());
398      view->SetBoundsRect(rect_bounds);
399      view->SetVisible(true);
400    }
401  } else {
402    for (BrowserActionViews::const_iterator it = browser_action_views_.begin();
403         it < browser_action_views_.end(); ++it) {
404      BrowserActionView* view = *it;
405      int x = ToolbarView::kStandardSpacing +
406          ((it - browser_action_views_.begin()) * IconWidth(true));
407      view->SetVisible(x + icon_width <= max_x);
408      if (view->visible())
409        view->SetBounds(x, 0, icon_width, IconHeight());
410    }
411  }
412}
413
414bool BrowserActionsContainer::GetDropFormats(
415    int* formats,
416    std::set<OSExchangeData::CustomFormat>* custom_formats) {
417  return BrowserActionDragData::GetDropFormats(custom_formats);
418}
419
420bool BrowserActionsContainer::AreDropTypesRequired() {
421  return BrowserActionDragData::AreDropTypesRequired();
422}
423
424bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
425  return BrowserActionDragData::CanDrop(data, profile_);
426}
427
428void BrowserActionsContainer::OnDragEntered(
429    const ui::DropTargetEvent& event) {
430}
431
432int BrowserActionsContainer::OnDragUpdated(
433    const ui::DropTargetEvent& event) {
434  // First check if we are above the chevron (overflow) menu.
435  if (GetEventHandlerForPoint(event.location()) == chevron_) {
436    if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_)
437      StartShowFolderDropMenuTimer();
438    return ui::DragDropTypes::DRAG_MOVE;
439  }
440  StopShowFolderDropMenuTimer();
441
442  // Figure out where to display the indicator.  This is a complex calculation:
443
444  // First, we figure out how much space is to the left of the icon area, so we
445  // can calculate the true offset into the icon area. The easiest way to do
446  // this is to just find where the first icon starts.
447  int width_before_icons =
448      browser_action_views_[GetFirstVisibleIconIndex()]->x();
449
450  // If we're right-to-left, we flip the mirror the event.x() so that our
451  // calculations are consistent with left-to-right.
452  int offset_into_icon_area =
453      GetMirroredXInView(event.x()) - width_before_icons;
454
455  // Next, figure out what row we're on. This only matters for overflow mode,
456  // but the calculation is the same for both.
457  size_t row_index = event.y() / IconHeight();
458
459  // Sanity check - we should never be on a different row in the main container.
460  DCHECK(in_overflow_mode() || row_index == 0);
461
462  // Next, we determine which icon to place the indicator in front of.  We want
463  // to place the indicator in front of icon n when the cursor is between the
464  // midpoints of icons (n - 1) and n.  To do this we take the offset into the
465  // icon area and transform it as follows:
466  //
467  // Real icon area:
468  //   0   a     *  b        c
469  //   |   |        |        |
470  //   |[IC|ON]  [IC|ON]  [IC|ON]
471  // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
472  // Here the "*" represents the offset into the icon area, and since it's
473  // between a and b, we want to return "1".
474  //
475  // Transformed "icon area":
476  //   0        a     *  b        c
477  //   |        |        |        |
478  //   |[ICON]  |[ICON]  |[ICON]  |
479  // If we shift both our offset and our divider points later by half an icon
480  // plus one spacing unit, then it becomes very easy to calculate how many
481  // divider points we've passed, because they're the multiples of "one icon
482  // plus padding".
483  int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) +
484      kItemSpacing) / IconWidth(true);
485
486  // We need to figure out how many icons are visible on the relevant row.
487  // In the main container, this will just be the visible actions.
488  int visible_icons_on_row = VisibleBrowserActionsAfterAnimation();
489  if (in_overflow_mode()) {
490    // If this is the final row of the overflow, then this is the remainder of
491    // visible icons. Otherwise, it's a full row (kIconsPerRow).
492    visible_icons_on_row =
493        row_index ==
494            static_cast<size_t>(visible_icons_on_row / kIconsPerMenuRow) ?
495                visible_icons_on_row % kIconsPerMenuRow :
496                kIconsPerMenuRow;
497  }
498
499  // Because the user can drag outside the container bounds, we need to clamp to
500  // the valid range.  Note that the maximum allowable value is (num icons), not
501  // (num icons - 1), because we represent the indicator being past the last
502  // icon as being "before the (last + 1) icon".
503  size_t before_icon_in_row =
504      std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row);
505
506  if (!drop_position_.get() ||
507      !(drop_position_->row == row_index &&
508        drop_position_->icon_in_row == before_icon_in_row)) {
509    drop_position_.reset(new DropPosition(row_index, before_icon_in_row));
510    SchedulePaint();
511  }
512
513  return ui::DragDropTypes::DRAG_MOVE;
514}
515
516void BrowserActionsContainer::OnDragExited() {
517  StopShowFolderDropMenuTimer();
518  drop_position_.reset();
519  SchedulePaint();
520}
521
522int BrowserActionsContainer::OnPerformDrop(
523    const ui::DropTargetEvent& event) {
524  BrowserActionDragData data;
525  if (!data.Read(event.data()))
526    return ui::DragDropTypes::DRAG_NONE;
527
528  // Make sure we have the same view as we started with.
529  DCHECK_EQ(browser_action_views_[data.index()]->extension()->id(),
530            data.id());
531  DCHECK(model_);
532
533  size_t i =
534      drop_position_->row * kIconsPerMenuRow + drop_position_->icon_in_row;
535  if (in_overflow_mode())
536    i += GetFirstVisibleIconIndex();
537  // |i| now points to the item to the right of the drop indicator*, which is
538  // correct when dragging an icon to the left. When dragging to the right,
539  // however, we want the icon being dragged to get the index of the item to
540  // the left of the drop indicator, so we subtract one.
541  // * Well, it can also point to the end, but not when dragging to the left. :)
542  if (i > data.index())
543    --i;
544
545  if (profile_->IsOffTheRecord())
546    i = model_->IncognitoIndexToOriginal(i);
547
548  model_->MoveExtensionIcon(
549      browser_action_views_[data.index()]->extension(), i);
550
551  OnDragExited();  // Perform clean up after dragging.
552  FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
553                    observers_,
554                    OnBrowserActionDragDone());
555  return ui::DragDropTypes::DRAG_MOVE;
556}
557
558void BrowserActionsContainer::GetAccessibleState(
559    ui::AXViewState* state) {
560  state->role = ui::AX_ROLE_GROUP;
561  state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
562}
563
564void BrowserActionsContainer::OnMenuButtonClicked(views::View* source,
565                                                  const gfx::Point& point) {
566  if (source == chevron_) {
567    overflow_menu_ =
568        new BrowserActionOverflowMenuController(this,
569                                                browser_,
570                                                chevron_,
571                                                browser_action_views_,
572                                                VisibleBrowserActions(),
573                                                false);
574    overflow_menu_->set_observer(this);
575    overflow_menu_->RunMenu(GetWidget());
576  }
577}
578
579void BrowserActionsContainer::WriteDragDataForView(View* sender,
580                                                   const gfx::Point& press_pt,
581                                                   OSExchangeData* data) {
582  DCHECK(data);
583
584  for (size_t i = 0; i < browser_action_views_.size(); ++i) {
585    BrowserActionView* view = browser_action_views_[i];
586    if (view == sender) {
587      // Set the dragging image for the icon.
588      gfx::ImageSkia badge(view->GetIconWithBadge());
589      drag_utils::SetDragImageOnDataObject(badge,
590                                           press_pt.OffsetFromOrigin(),
591                                           data);
592
593      // Fill in the remaining info.
594      BrowserActionDragData drag_data(view->extension()->id(), i);
595      drag_data.Write(profile_, data);
596      break;
597    }
598  }
599}
600
601int BrowserActionsContainer::GetDragOperationsForView(View* sender,
602                                                      const gfx::Point& p) {
603  return ui::DragDropTypes::DRAG_MOVE;
604}
605
606bool BrowserActionsContainer::CanStartDragForView(View* sender,
607                                                  const gfx::Point& press_pt,
608                                                  const gfx::Point& p) {
609  // We don't allow dragging while we're highlighting.
610  return !model_->is_highlighting();
611}
612
613void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
614  if (!done_resizing) {
615    resize_amount_ = resize_amount;
616    OnBrowserActionVisibilityChanged();
617    return;
618  }
619
620  // Up until now we've only been modifying the resize_amount, but now it is
621  // time to set the container size to the size we have resized to, and then
622  // animate to the nearest icon count size if necessary (which may be 0).
623  int max_width = IconCountToWidth(-1, false);
624  container_width_ =
625      std::min(std::max(0, container_width_ - resize_amount), max_width);
626
627  // Save off the desired number of visible icons.  We do this now instead of at
628  // the end of the animation so that even if the browser is shut down while
629  // animating, the right value will be restored on next run.
630  // NOTE: Don't save the icon count in incognito because there may be fewer
631  // icons in that mode. The result is that the container in a normal window is
632  // always at least as wide as in an incognito window.
633  int visible_icons = WidthToIconCount(container_width_);
634  if (!profile_->IsOffTheRecord())
635    model_->SetVisibleIconCount(visible_icons);
636
637  Animate(gfx::Tween::EASE_OUT, visible_icons);
638}
639
640void BrowserActionsContainer::AnimationProgressed(
641    const gfx::Animation* animation) {
642  DCHECK_EQ(resize_animation_.get(), animation);
643  resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
644      (container_width_ - animation_target_size_));
645  OnBrowserActionVisibilityChanged();
646}
647
648void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
649  container_width_ = animation_target_size_;
650  animation_target_size_ = 0;
651  resize_amount_ = 0;
652  suppress_chevron_ = false;
653  OnBrowserActionVisibilityChanged();
654
655  FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
656                    observers_,
657                    OnBrowserActionsContainerAnimationEnded());
658}
659
660void BrowserActionsContainer::NotifyMenuDeleted(
661    BrowserActionOverflowMenuController* controller) {
662  DCHECK_EQ(overflow_menu_, controller);
663  overflow_menu_ = NULL;
664}
665
666content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
667  return browser_->tab_strip_model()->GetActiveWebContents();
668}
669
670extensions::ActiveTabPermissionGranter*
671    BrowserActionsContainer::GetActiveTabPermissionGranter() {
672  content::WebContents* web_contents =
673      browser_->tab_strip_model()->GetActiveWebContents();
674  if (!web_contents)
675    return NULL;
676  return extensions::TabHelper::FromWebContents(web_contents)->
677      active_tab_permission_granter();
678}
679
680void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
681                                                size_t new_index) {
682  const Extension* extension = extensions::ExtensionRegistry::Get(profile_)->
683      enabled_extensions().GetByID(extension_id);
684  model_->MoveExtensionIcon(extension, new_index);
685  SchedulePaint();
686}
687
688size_t BrowserActionsContainer::GetFirstVisibleIconIndex() const {
689  return in_overflow_mode() ? model_->GetVisibleIconCount() : 0;
690}
691
692ExtensionPopup* BrowserActionsContainer::TestGetPopup() {
693  return popup_owner_ ? popup_owner_->view_controller()->popup() : NULL;
694}
695
696void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
697  model_->SetVisibleIconCount(icons);
698  chevron_->SetVisible(icons < browser_action_views_.size());
699  container_width_ = IconCountToWidth(icons, chevron_->visible());
700  Layout();
701  SchedulePaint();
702}
703
704void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
705  // If the views haven't been initialized yet, wait for the next call to
706  // paint (one will be triggered by entering highlight mode).
707  if (model_->is_highlighting() && !browser_action_views_.empty()) {
708    views::Painter::PaintPainterAt(
709        canvas, highlight_painter_.get(), GetLocalBounds());
710  }
711
712  // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
713  // dragging (like we do for tab dragging).
714  if (drop_position_.get()) {
715    // The two-pixel width drop indicator.
716    static const int kDropIndicatorWidth = 2;
717
718    // Convert back to a pixel offset into the container.  First find the X
719    // coordinate of the drop icon.
720    int drop_icon_x = browser_action_views_[GetFirstVisibleIconIndex()]->x() +
721        (drop_position_->icon_in_row * IconWidth(true));
722    // Next, find the space before the drop icon. This will either be
723    // kItemSpacing or ToolbarView::kStandardSpacing, depending on whether this
724    // is the first icon.
725    // NOTE: Right now, these are the same. But let's do this right for if they
726    // ever aren't.
727    int space_before_drop_icon = drop_position_->icon_in_row == 0 ?
728        ToolbarView::kStandardSpacing : kItemSpacing;
729    // Now place the drop indicator halfway between this and the end of the
730    // previous icon.  If there is an odd amount of available space between the
731    // two icons (or the icon and the address bar) after subtracting the drop
732    // indicator width, this calculation puts the extra pixel on the left side
733    // of the indicator, since when the indicator is between the address bar and
734    // the first icon, it looks better closer to the icon.
735    int drop_indicator_x = drop_icon_x -
736        ((space_before_drop_icon + kDropIndicatorWidth) / 2);
737    int row_height = IconHeight();
738    int drop_indicator_y = row_height * drop_position_->row;
739    gfx::Rect indicator_bounds(drop_indicator_x,
740                               drop_indicator_y,
741                               kDropIndicatorWidth,
742                               row_height);
743    indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
744
745    // Color of the drop indicator.
746    static const SkColor kDropIndicatorColor = SK_ColorBLACK;
747    canvas->FillRect(indicator_bounds, kDropIndicatorColor);
748  }
749}
750
751void BrowserActionsContainer::OnThemeChanged() {
752  LoadImages();
753}
754
755void BrowserActionsContainer::ViewHierarchyChanged(
756    const ViewHierarchyChangedDetails& details) {
757  // No extensions (e.g., incognito).
758  if (!model_)
759    return;
760
761  if (details.is_add && details.child == this) {
762    // Initial toolbar button creation and placement in the widget hierarchy.
763    // We do this here instead of in the constructor because AddBrowserAction
764    // calls Layout on the Toolbar, which needs this object to be constructed
765    // before its Layout function is called.
766    CreateBrowserActionViews();
767  }
768}
769
770// static
771int BrowserActionsContainer::IconWidth(bool include_padding) {
772  static bool initialized = false;
773  static int icon_width = 0;
774  if (!initialized) {
775    initialized = true;
776    icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
777        IDR_BROWSER_ACTION)->width();
778  }
779  return icon_width + (include_padding ? kItemSpacing : 0);
780}
781
782// static
783int BrowserActionsContainer::IconHeight() {
784  static bool initialized = false;
785  static int icon_height = 0;
786  if (!initialized) {
787    initialized = true;
788    icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
789        IDR_BROWSER_ACTION)->height();
790  }
791  return icon_height;
792}
793
794void BrowserActionsContainer::ToolbarExtensionAdded(const Extension* extension,
795                                                    int index) {
796#if defined(DEBUG)
797  for (size_t i = 0; i < browser_action_views_.size(); ++i) {
798    DCHECK(browser_action_views_[i]->extension() != extension) <<
799           "Asked to add a browser action view for an extension that already "
800           "exists.";
801  }
802#endif
803  CloseOverflowMenu();
804
805  if (!ShouldDisplayBrowserAction(extension))
806    return;
807
808  // Add the new browser action to the vector and the view hierarchy.
809  if (profile_->IsOffTheRecord())
810    index = model_->OriginalIndexToIncognito(index);
811  BrowserActionView* view =
812      new BrowserActionView(extension,
813                            GetExtensionAction(*extension, profile_),
814                            browser_,
815                            this);
816  browser_action_views_.insert(browser_action_views_.begin() + index, view);
817  AddChildViewAt(view, index);
818
819  // If we are still initializing the container, don't bother animating.
820  if (!model_->extensions_initialized())
821    return;
822
823  // Enlarge the container if it was already at maximum size and we're not in
824  // the middle of upgrading.
825  if ((model_->GetVisibleIconCount() < 0) &&
826      !extensions::ExtensionSystem::Get(profile_)->runtime_data()->
827          IsBeingUpgraded(extension)) {
828    suppress_chevron_ = true;
829    Animate(gfx::Tween::LINEAR, browser_action_views_.size());
830  } else {
831    // Just redraw the (possibly modified) visible icon set.
832    OnBrowserActionVisibilityChanged();
833  }
834}
835
836void BrowserActionsContainer::ToolbarExtensionRemoved(
837    const Extension* extension) {
838  CloseOverflowMenu();
839
840  size_t visible_actions = VisibleBrowserActionsAfterAnimation();
841  for (BrowserActionViews::iterator i(browser_action_views_.begin());
842       i != browser_action_views_.end(); ++i) {
843    if ((*i)->extension() == extension) {
844      delete *i;
845      browser_action_views_.erase(i);
846
847      // If the extension is being upgraded we don't want the bar to shrink
848      // because the icon is just going to get re-added to the same location.
849      if (extensions::ExtensionSystem::Get(profile_)->runtime_data()->
850              IsBeingUpgraded(extension))
851        return;
852
853      if (browser_action_views_.size() > visible_actions) {
854        // If we have more icons than we can show, then we must not be changing
855        // the container size (since we either removed an icon from the main
856        // area and one from the overflow list will have shifted in, or we
857        // removed an entry directly from the overflow list).
858        OnBrowserActionVisibilityChanged();
859      } else {
860        // Either we went from overflow to no-overflow, or we shrunk the no-
861        // overflow container by 1.  Either way the size changed, so animate.
862        if (chevron_)
863          chevron_->SetVisible(false);
864        Animate(gfx::Tween::EASE_OUT, browser_action_views_.size());
865      }
866      return;  // We have found the action to remove, bail out.
867    }
868  }
869}
870
871void BrowserActionsContainer::ToolbarExtensionMoved(const Extension* extension,
872                                                    int index) {
873  if (!ShouldDisplayBrowserAction(extension))
874    return;
875
876  if (profile_->IsOffTheRecord())
877    index = model_->OriginalIndexToIncognito(index);
878
879  DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
880
881  BrowserActionViews::iterator iter = browser_action_views_.begin();
882  int old_index = 0;
883  while (iter != browser_action_views_.end() &&
884         (*iter)->extension() != extension) {
885    ++iter;
886    ++old_index;
887  }
888
889  DCHECK(iter != browser_action_views_.end());
890  if (old_index == index)
891    return;  // Already in place.
892
893  BrowserActionView* moved_view = *iter;
894  browser_action_views_.erase(iter);
895  browser_action_views_.insert(
896      browser_action_views_.begin() + index, moved_view);
897
898  Layout();
899  SchedulePaint();
900}
901
902void BrowserActionsContainer::ToolbarExtensionUpdated(
903    const Extension* extension) {
904  BrowserActionView* view = GetViewForExtension(extension);
905  if (view)
906    view->UpdateState();
907}
908
909bool BrowserActionsContainer::ShowExtensionActionPopup(
910    const Extension* extension) {
911  return ShowPopupForExtension(extension, false, false);
912}
913
914void BrowserActionsContainer::ToolbarVisibleCountChanged() {
915  int old_container_width = container_width_;
916  SetContainerWidth();
917  if (old_container_width != container_width_)
918    Animate(gfx::Tween::EASE_OUT, GetIconCount());
919}
920
921void BrowserActionsContainer::ToolbarHighlightModeChanged(
922    bool is_highlighting) {
923  // The visual highlighting is done in OnPaint(). It's a bit of a pain that
924  // we delete and recreate everything here, but given everything else going on
925  // (the lack of highlight, n more extensions appearing, etc), it's not worth
926  // the extra complexity to create and insert only the new extensions.
927  DeleteBrowserActionViews();
928  CreateBrowserActionViews();
929  Animate(gfx::Tween::LINEAR, browser_action_views_.size());
930}
931
932void BrowserActionsContainer::LoadImages() {
933  ui::ThemeProvider* tp = GetThemeProvider();
934  if (!tp || !chevron_)
935    return;
936
937  chevron_->SetImage(views::Button::STATE_NORMAL,
938                     *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
939
940  const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT);
941  highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages));
942}
943
944void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
945  SetVisible(!browser_action_views_.empty());
946  if (owner_view_) {
947    owner_view_->Layout();
948    owner_view_->SchedulePaint();
949  }
950}
951
952void BrowserActionsContainer::SetContainerWidth() {
953  int visible_actions = GetIconCount();
954  if (chevron_) {
955    chevron_->SetVisible(
956      static_cast<size_t>(visible_actions) < model_->toolbar_items().size());
957  }
958  container_width_ =
959      IconCountToWidth(visible_actions, chevron_ && chevron_->visible());
960}
961
962void BrowserActionsContainer::CloseOverflowMenu() {
963  if (overflow_menu_)
964    overflow_menu_->CancelMenu();
965}
966
967void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
968  show_menu_task_factory_.InvalidateWeakPtrs();
969}
970
971void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
972  base::MessageLoop::current()->PostDelayedTask(
973      FROM_HERE,
974      base::Bind(&BrowserActionsContainer::ShowDropFolder,
975                 show_menu_task_factory_.GetWeakPtr()),
976      base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
977}
978
979void BrowserActionsContainer::ShowDropFolder() {
980  DCHECK(!overflow_menu_);
981  drop_position_.reset();
982  overflow_menu_ =
983      new BrowserActionOverflowMenuController(this,
984                                              browser_,
985                                              chevron_,
986                                              browser_action_views_,
987                                              VisibleBrowserActions(),
988                                              true);
989  overflow_menu_->set_observer(this);
990  overflow_menu_->RunMenu(GetWidget());
991}
992
993int BrowserActionsContainer::IconCountToWidth(int icons,
994                                              bool display_chevron) const {
995  if (icons < 0)
996    icons = browser_action_views_.size();
997  if ((icons == 0) && !display_chevron)
998    return ToolbarView::kStandardSpacing;
999  int icons_size =
1000      (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
1001  int chevron_size = chevron_ && display_chevron ?
1002      (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
1003  return ToolbarView::kStandardSpacing + icons_size + chevron_size +
1004      ToolbarView::kStandardSpacing;
1005}
1006
1007size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
1008  // Check for widths large enough to show the entire icon set.
1009  if (pixels >= IconCountToWidth(-1, false))
1010    return browser_action_views_.size();
1011
1012  // We need to reserve space for the resize area, chevron, and the spacing on
1013  // either side of the chevron.
1014  int available_space = pixels - ToolbarView::kStandardSpacing -
1015      (chevron_ ? chevron_->GetPreferredSize().width() : 0) -
1016      kChevronSpacing - ToolbarView::kStandardSpacing;
1017  // Now we add an extra between-item padding value so the space can be divided
1018  // evenly by (size of icon with padding).
1019  return static_cast<size_t>(
1020      std::max(0, available_space + kItemSpacing) / IconWidth(true));
1021}
1022
1023int BrowserActionsContainer::MinimumNonemptyWidth() const {
1024  if (!chevron_)
1025    return ToolbarView::kStandardSpacing;
1026  return (ToolbarView::kStandardSpacing * 2) + kChevronSpacing +
1027      chevron_->GetPreferredSize().width();
1028}
1029
1030void BrowserActionsContainer::Animate(gfx::Tween::Type tween_type,
1031                                      size_t num_visible_icons) {
1032  int target_size = IconCountToWidth(num_visible_icons,
1033      num_visible_icons < browser_action_views_.size());
1034  if (resize_animation_ && !disable_animations_during_testing_) {
1035    // Animate! We have to set the animation_target_size_ after calling Reset(),
1036    // because that could end up calling AnimationEnded which clears the value.
1037    resize_animation_->Reset();
1038    resize_animation_->SetTweenType(tween_type);
1039    animation_target_size_ = target_size;
1040    resize_animation_->Show();
1041  } else {
1042    animation_target_size_ = target_size;
1043    AnimationEnded(resize_animation_.get());
1044  }
1045}
1046
1047bool BrowserActionsContainer::ShouldDisplayBrowserAction(
1048    const Extension* extension) const {
1049  // Only display incognito-enabled extensions while in incognito mode.
1050  return !profile_->IsOffTheRecord() ||
1051      extensions::util::IsIncognitoEnabled(extension->id(), profile_);
1052}
1053
1054bool BrowserActionsContainer::ShowPopupForExtension(
1055    const extensions::Extension* extension,
1056    bool grant_tab_permissions,
1057    bool can_override) {
1058  // If the popup cannot override other views, then no other popups can be
1059  // showing, and it must be shown in the active widow with a visible toolbar.
1060  // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
1061  // fixed.
1062  if (!can_override &&
1063      (popup_owner_ ||
1064       !browser_->window()->IsActive() ||
1065       !browser_->window()->IsToolbarVisible())) {
1066    return false;
1067  }
1068
1069  BrowserActionView* view = GetViewForExtension(extension);
1070  return view ? view->view_controller()->ExecuteAction(
1071                    ExtensionPopup::SHOW, grant_tab_permissions) : false;
1072}
1073
1074BrowserActionView* BrowserActionsContainer::GetViewForExtension(
1075    const Extension* extension) {
1076  for (BrowserActionViews::iterator view = browser_action_views_.begin();
1077       view != browser_action_views_.end(); ++view) {
1078    if ((*view)->extension() == extension)
1079      return *view;
1080  }
1081
1082  return NULL;
1083}
1084
1085size_t BrowserActionsContainer::GetIconCount() const {
1086  if (!model_)
1087    return 0u;
1088  // Find the number of icons which could be displayed.
1089  size_t displayable_icon_count = 0u;
1090  const extensions::ExtensionList& extensions = model_->toolbar_items();
1091  for (extensions::ExtensionList::const_iterator iter = extensions.begin();
1092       iter != extensions.end(); ++iter) {
1093    displayable_icon_count += ShouldDisplayBrowserAction(*iter) ? 1u : 0u;
1094  }
1095  // Find the absolute value for the model's visible count.
1096  int model_size = model_->GetVisibleIconCount();
1097  size_t absolute_model_size =
1098      model_size == -1 ? extensions.size() : model_size;
1099
1100  // The main container will try to show |model_size| icons, but reduce if there
1101  // aren't enough displayable icons to do so.
1102  size_t main_displayed = std::min(displayable_icon_count, absolute_model_size);
1103  // The overflow will display the extras, if any.
1104  return in_overflow_mode() ?
1105      displayable_icon_count - main_displayed : main_displayed;
1106}
1107