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