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