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