Revert "Create a platform-independent ToolbarActionsBar"
[chromium-blink-merge.git] / chrome / browser / ui / views / toolbar / browser_actions_container.cc
blob6c195235e537ff29181d48f033a353cdef178ae0
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.
5 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
7 #include "base/compiler_specific.h"
8 #include "base/stl_util.h"
9 #include "chrome/browser/extensions/extension_action_manager.h"
10 #include "chrome/browser/extensions/tab_helper.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_window.h"
14 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.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_popup.h"
20 #include "chrome/browser/ui/views/frame/browser_view.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 "chrome/grit/generated_resources.h"
25 #include "components/crx_file/id_util.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/browser/runtime_data.h"
28 #include "extensions/common/feature_switch.h"
29 #include "grit/theme_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/resources/grit/ui_resources.h"
41 #include "ui/views/controls/resize_area.h"
42 #include "ui/views/painter.h"
43 #include "ui/views/widget/widget.h"
45 using extensions::Extension;
47 namespace {
49 // Horizontal spacing before the chevron (if visible).
50 const int kChevronSpacing = ToolbarView::kStandardSpacing - 2;
52 // Returns the set of controllers for all actions.
53 // TODO(devlin): We should move this to the model, once it supports component
54 // actions.
55 ScopedVector<ToolbarActionViewController> GetToolbarActions(
56 extensions::ExtensionToolbarModel* model,
57 Browser* browser) {
58 ScopedVector<ToolbarActionViewController> actions;
60 // Extension actions come first.
61 extensions::ExtensionActionManager* action_manager =
62 extensions::ExtensionActionManager::Get(browser->profile());
63 const extensions::ExtensionList& toolbar_items = model->GetItemOrderForTab(
64 browser->tab_strip_model()->GetActiveWebContents());
65 for (const scoped_refptr<const Extension>& extension : toolbar_items) {
66 actions.push_back(new ExtensionActionViewController(
67 extension.get(),
68 browser,
69 action_manager->GetExtensionAction(*extension)));
72 // Component actions come second.
73 ScopedVector<ToolbarActionViewController> component_actions =
74 ComponentToolbarActionsFactory::GetInstance()->
75 GetComponentToolbarActions();
76 DCHECK(extensions::FeatureSwitch::extension_action_redesign()->IsEnabled() ||
77 component_actions.empty());
78 actions.insert(
79 actions.end(), component_actions.begin(), component_actions.end());
80 component_actions.weak_clear();
81 return actions.Pass();
84 } // namespace
86 ////////////////////////////////////////////////////////////////////////////////
87 // BrowserActionsContainer::DropPosition
89 struct BrowserActionsContainer::DropPosition {
90 DropPosition(size_t row, size_t icon_in_row);
92 // The (0-indexed) row into which the action will be dropped.
93 size_t row;
95 // The (0-indexed) icon in the row before the action will be dropped.
96 size_t icon_in_row;
99 BrowserActionsContainer::DropPosition::DropPosition(
100 size_t row, size_t icon_in_row)
101 : row(row), icon_in_row(icon_in_row) {
104 ////////////////////////////////////////////////////////////////////////////////
105 // BrowserActionsContainer
107 // static
108 int BrowserActionsContainer::icons_per_overflow_menu_row_ = 1;
110 // static
111 const int BrowserActionsContainer::kItemSpacing = ToolbarView::kStandardSpacing;
113 // static
114 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
116 BrowserActionsContainer::BrowserActionsContainer(
117 Browser* browser,
118 BrowserActionsContainer* main_container)
119 : initialized_(false),
120 profile_(browser->profile()),
121 browser_(browser),
122 main_container_(main_container),
123 popup_owner_(NULL),
124 model_(extensions::ExtensionToolbarModel::Get(browser->profile())),
125 container_width_(0),
126 resize_area_(NULL),
127 chevron_(NULL),
128 suppress_chevron_(false),
129 suppress_animation_(false),
130 suppress_layout_(false),
131 resize_amount_(0),
132 animation_target_size_(0) {
133 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
134 if (model_) // |model_| can be NULL in views unittests.
135 model_->AddObserver(this);
137 bool overflow_experiment =
138 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
139 DCHECK(!in_overflow_mode() || overflow_experiment);
141 if (!in_overflow_mode()) {
142 resize_animation_.reset(new gfx::SlideAnimation(this));
143 resize_area_ = new views::ResizeArea(this);
144 AddChildView(resize_area_);
146 // 'Main' mode doesn't need a chevron overflow when overflow is shown inside
147 // the Chrome menu.
148 if (!overflow_experiment) {
149 // Since the ChevronMenuButton holds a raw pointer to us, we need to
150 // ensure it doesn't outlive us. Having it owned by the view hierarchy as
151 // a child will suffice.
152 chevron_ = new ChevronMenuButton(this);
153 chevron_->EnableCanvasFlippingForRTLUI(true);
154 chevron_->SetAccessibleName(
155 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
156 chevron_->SetVisible(false);
157 AddChildView(chevron_);
162 BrowserActionsContainer::~BrowserActionsContainer() {
163 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
164 observers_,
165 OnBrowserActionsContainerDestroyed());
167 if (model_)
168 model_->RemoveObserver(this);
169 HideActivePopup();
170 DeleteToolbarActionViews();
173 void BrowserActionsContainer::Init() {
174 LoadImages();
176 // We wait to set the container width until now so that the chevron images
177 // will be loaded. The width calculation needs to know the chevron size.
178 if (model_ && model_->extensions_initialized()) {
179 container_width_ = GetPreferredWidth();
180 SetChevronVisibility();
183 initialized_ = true;
186 const std::string& BrowserActionsContainer::GetIdAt(size_t index) {
187 return toolbar_action_views_[index]->view_controller()->GetId();
190 ToolbarActionView* BrowserActionsContainer::GetViewForExtension(
191 const Extension* extension) {
192 for (ToolbarActionView* view : toolbar_action_views_) {
193 if (view->view_controller()->GetId() == extension->id())
194 return view;
196 return nullptr;
199 void BrowserActionsContainer::RefreshToolbarActionViews() {
200 if (toolbar_action_views_.empty())
201 return; // Nothing to do.
203 // When we do a bulk-refresh of views (such as when we switch tabs), we don't
204 // animate the difference. We only animate when it's a change driven by the
205 // action.
206 base::AutoReset<bool> animation_resetter(&suppress_animation_, true);
209 // Don't layout until the end.
210 base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
211 for (ToolbarActionView* view : toolbar_action_views_)
212 view->UpdateState();
215 ReorderViews(); // Also triggers a layout.
218 void BrowserActionsContainer::CreateToolbarActionViews() {
219 DCHECK(toolbar_action_views_.empty());
220 if (!model_)
221 return;
224 // We don't Layout while creating views. Instead, Layout() once at the end.
225 base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
227 ScopedVector<ToolbarActionViewController> actions =
228 GetToolbarActions(model_, browser_);
229 for (ToolbarActionViewController* controller : actions) {
230 ToolbarActionView* view =
231 new ToolbarActionView(make_scoped_ptr(controller), browser_, this);
232 toolbar_action_views_.push_back(view);
233 AddChildView(view);
235 actions.weak_clear();
238 Layout();
239 SchedulePaint();
242 void BrowserActionsContainer::DeleteToolbarActionViews() {
243 HideActivePopup();
244 STLDeleteElements(&toolbar_action_views_);
247 size_t BrowserActionsContainer::VisibleBrowserActions() const {
248 size_t visible_actions = 0;
249 for (const ToolbarActionView* view : toolbar_action_views_) {
250 if (view->visible())
251 ++visible_actions;
253 return visible_actions;
256 size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
257 if (!animating())
258 return VisibleBrowserActions();
260 return WidthToIconCount(animation_target_size_);
263 void BrowserActionsContainer::ExecuteExtensionCommand(
264 const extensions::Extension* extension,
265 const extensions::Command& command) {
266 // Global commands are handled by the ExtensionCommandsGlobalRegistry
267 // instance.
268 DCHECK(!command.global());
269 extension_keybinding_registry_->ExecuteCommand(extension->id(),
270 command.accelerator());
273 void BrowserActionsContainer::NotifyActionMovedToOverflow() {
274 // When an action is moved to overflow, we shrink the size of the container
275 // by 1.
276 size_t icon_count = model_->visible_icon_count();
277 // Since this happens when an icon moves from the main bar to overflow, we
278 // can't possibly have had no visible icons on the main bar.
279 DCHECK_NE(0u, icon_count);
280 model_->SetVisibleIconCount(icon_count - 1);
283 bool BrowserActionsContainer::ShownInsideMenu() const {
284 return in_overflow_mode();
287 void BrowserActionsContainer::OnToolbarActionViewDragDone() {
288 ToolbarVisibleCountChanged();
289 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
290 observers_,
291 OnBrowserActionDragDone());
294 views::MenuButton* BrowserActionsContainer::GetOverflowReferenceView() {
295 // With traditional overflow, the reference is the chevron. With the
296 // redesign, we use the wrench menu instead.
297 return chevron_ ?
298 chevron_ :
299 BrowserView::GetBrowserViewForBrowser(browser_)->toolbar()->app_menu();
302 void BrowserActionsContainer::SetPopupOwner(ToolbarActionView* popup_owner) {
303 // We should never be setting a popup owner when one already exists, and
304 // never unsetting one when one wasn't set.
305 DCHECK((!popup_owner_ && popup_owner) ||
306 (popup_owner_ && !popup_owner));
307 popup_owner_ = popup_owner;
310 void BrowserActionsContainer::HideActivePopup() {
311 if (popup_owner_)
312 popup_owner_->view_controller()->HidePopup();
315 ToolbarActionView* BrowserActionsContainer::GetMainViewForAction(
316 ToolbarActionView* view) {
317 if (!in_overflow_mode())
318 return view; // This is the main view.
320 // The overflow container and main container each have the same views and
321 // view indices, so we can return the view of the index that |view| has in
322 // this container.
323 ToolbarActionViews::const_iterator iter =
324 std::find(toolbar_action_views_.begin(),
325 toolbar_action_views_.end(),
326 view);
327 DCHECK(iter != toolbar_action_views_.end());
328 size_t index = iter - toolbar_action_views_.begin();
329 return main_container_->toolbar_action_views_[index];
332 void BrowserActionsContainer::AddObserver(
333 BrowserActionsContainerObserver* observer) {
334 observers_.AddObserver(observer);
337 void BrowserActionsContainer::RemoveObserver(
338 BrowserActionsContainerObserver* observer) {
339 observers_.RemoveObserver(observer);
342 gfx::Size BrowserActionsContainer::GetPreferredSize() const {
343 if (in_overflow_mode()) {
344 int icon_count = GetIconCount();
345 // In overflow, we always have a preferred size of a full row (even if we
346 // don't use it), and always of at least one row. The parent may decide to
347 // show us even when empty, e.g. as a drag target for dragging in icons from
348 // the main container.
349 int row_count =
350 ((std::max(0, icon_count - 1)) / icons_per_overflow_menu_row_) + 1;
351 return gfx::Size(IconCountToWidth(icons_per_overflow_menu_row_),
352 row_count * IconHeight());
355 // If there are no actions to show, then don't show the container at all.
356 if (toolbar_action_views_.empty())
357 return gfx::Size();
359 // We calculate the size of the view by taking the current width and
360 // subtracting resize_amount_ (the latter represents how far the user is
361 // resizing the view or, if animating the snapping, how far to animate it).
362 // But we also clamp it to a minimum size and the maximum size, so that the
363 // container can never shrink too far or take up more space than it needs.
364 // In other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX).
365 int preferred_width = std::min(
366 std::max(MinimumNonemptyWidth(), container_width_ - resize_amount_),
367 IconCountToWidth(-1));
368 return gfx::Size(preferred_width, IconHeight());
371 int BrowserActionsContainer::GetHeightForWidth(int width) const {
372 if (in_overflow_mode())
373 icons_per_overflow_menu_row_ = (width - kItemSpacing) / IconWidth(true);
374 return GetPreferredSize().height();
377 gfx::Size BrowserActionsContainer::GetMinimumSize() const {
378 int min_width = std::min(MinimumNonemptyWidth(), IconCountToWidth(-1));
379 return gfx::Size(min_width, IconHeight());
382 void BrowserActionsContainer::Layout() {
383 if (suppress_layout_)
384 return;
386 if (toolbar_action_views_.empty()) {
387 SetVisible(false);
388 return;
391 SetVisible(true);
392 if (resize_area_)
393 resize_area_->SetBounds(0, 0, kItemSpacing, height());
395 // If the icons don't all fit, show the chevron (unless suppressed).
396 int max_x = GetPreferredSize().width();
397 if (IconCountToWidth(-1) > max_x && !suppress_chevron_ && chevron_) {
398 chevron_->SetVisible(true);
399 gfx::Size chevron_size(chevron_->GetPreferredSize());
400 max_x -= chevron_size.width() + kChevronSpacing;
401 chevron_->SetBounds(
402 width() - ToolbarView::kStandardSpacing - chevron_size.width(),
404 chevron_size.width(),
405 chevron_size.height());
406 } else if (chevron_) {
407 chevron_->SetVisible(false);
410 // The padding before the first icon and after the last icon in the container.
411 int container_padding =
412 in_overflow_mode() ? kItemSpacing : ToolbarView::kStandardSpacing;
413 // The range of visible icons, from start_index (inclusive) to end_index
414 // (exclusive).
415 size_t start_index = in_overflow_mode() ?
416 main_container_->VisibleBrowserActionsAfterAnimation() : 0u;
417 // For the main container's last visible icon, we calculate how many icons we
418 // can display with the given width. We add an extra kItemSpacing because the
419 // last icon doesn't need padding, but we want it to divide easily.
420 size_t end_index = in_overflow_mode() ?
421 toolbar_action_views_.size() :
422 (max_x - 2 * container_padding + kItemSpacing) / IconWidth(true);
423 // The maximum length for one row of icons.
424 size_t row_length =
425 in_overflow_mode() ? icons_per_overflow_menu_row_ : end_index;
427 // Now draw the icons for the actions in the available space. Once all the
428 // variables are in place, the layout works equally well for the main and
429 // overflow container.
430 for (size_t i = 0u; i < toolbar_action_views_.size(); ++i) {
431 ToolbarActionView* view = toolbar_action_views_[i];
432 if (i < start_index || i >= end_index) {
433 view->SetVisible(false);
434 } else {
435 size_t relative_index = i - start_index;
436 size_t index_in_row = relative_index % row_length;
437 size_t row_index = relative_index / row_length;
438 view->SetBounds(container_padding + index_in_row * IconWidth(true),
439 row_index * IconHeight(),
440 IconWidth(false),
441 IconHeight());
442 view->SetVisible(true);
447 bool BrowserActionsContainer::GetDropFormats(
448 int* formats,
449 std::set<OSExchangeData::CustomFormat>* custom_formats) {
450 return BrowserActionDragData::GetDropFormats(custom_formats);
453 bool BrowserActionsContainer::AreDropTypesRequired() {
454 return BrowserActionDragData::AreDropTypesRequired();
457 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
458 return BrowserActionDragData::CanDrop(data, profile_);
461 int BrowserActionsContainer::OnDragUpdated(
462 const ui::DropTargetEvent& event) {
463 size_t row_index = 0;
464 size_t before_icon_in_row = 0;
465 // If there are no visible actions (such as when dragging an icon to an empty
466 // overflow/main container), then 0, 0 for row, column is correct.
467 if (VisibleBrowserActions() != 0) {
468 // Figure out where to display the indicator. This is a complex calculation:
470 // First, we subtract out the padding to the left of the icon area, which is
471 // ToolbarView::kStandardSpacing. If we're right-to-left, we also mirror the
472 // event.x() so that our calculations are consistent with left-to-right.
473 int offset_into_icon_area =
474 GetMirroredXInView(event.x()) - ToolbarView::kStandardSpacing;
476 // Next, figure out what row we're on. This only matters for overflow mode,
477 // but the calculation is the same for both.
478 row_index = event.y() / IconHeight();
480 // Sanity check - we should never be on a different row in the main
481 // container.
482 DCHECK(in_overflow_mode() || row_index == 0);
484 // Next, we determine which icon to place the indicator in front of. We want
485 // to place the indicator in front of icon n when the cursor is between the
486 // midpoints of icons (n - 1) and n. To do this we take the offset into the
487 // icon area and transform it as follows:
489 // Real icon area:
490 // 0 a * b c
491 // | | | |
492 // |[IC|ON] [IC|ON] [IC|ON]
493 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
494 // Here the "*" represents the offset into the icon area, and since it's
495 // between a and b, we want to return "1".
497 // Transformed "icon area":
498 // 0 a * b c
499 // | | | |
500 // |[ICON] |[ICON] |[ICON] |
501 // If we shift both our offset and our divider points later by half an icon
502 // plus one spacing unit, then it becomes very easy to calculate how many
503 // divider points we've passed, because they're the multiples of "one icon
504 // plus padding".
505 int before_icon_unclamped =
506 (offset_into_icon_area + (IconWidth(false) / 2) +
507 kItemSpacing) / IconWidth(true);
509 // We need to figure out how many icons are visible on the relevant row.
510 // In the main container, this will just be the visible actions.
511 int visible_icons_on_row = VisibleBrowserActionsAfterAnimation();
512 if (in_overflow_mode()) {
513 // If this is the final row of the overflow, then this is the remainder of
514 // visible icons. Otherwise, it's a full row (kIconsPerRow).
515 visible_icons_on_row =
516 row_index ==
517 static_cast<size_t>(visible_icons_on_row /
518 icons_per_overflow_menu_row_) ?
519 visible_icons_on_row % icons_per_overflow_menu_row_ :
520 icons_per_overflow_menu_row_;
523 // Because the user can drag outside the container bounds, we need to clamp
524 // to the valid range. Note that the maximum allowable value is (num icons),
525 // not (num icons - 1), because we represent the indicator being past the
526 // last icon as being "before the (last + 1) icon".
527 before_icon_in_row =
528 std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row);
531 if (!drop_position_.get() ||
532 !(drop_position_->row == row_index &&
533 drop_position_->icon_in_row == before_icon_in_row)) {
534 drop_position_.reset(new DropPosition(row_index, before_icon_in_row));
535 SchedulePaint();
538 return ui::DragDropTypes::DRAG_MOVE;
541 void BrowserActionsContainer::OnDragExited() {
542 drop_position_.reset();
543 SchedulePaint();
546 int BrowserActionsContainer::OnPerformDrop(
547 const ui::DropTargetEvent& event) {
548 BrowserActionDragData data;
549 if (!data.Read(event.data()))
550 return ui::DragDropTypes::DRAG_NONE;
552 // Make sure we have the same view as we started with.
553 DCHECK_EQ(GetIdAt(data.index()), data.id());
554 DCHECK(model_);
556 size_t i = drop_position_->row * icons_per_overflow_menu_row_ +
557 drop_position_->icon_in_row;
558 if (in_overflow_mode())
559 i += main_container_->VisibleBrowserActionsAfterAnimation();
560 // |i| now points to the item to the right of the drop indicator*, which is
561 // correct when dragging an icon to the left. When dragging to the right,
562 // however, we want the icon being dragged to get the index of the item to
563 // the left of the drop indicator, so we subtract one.
564 // * Well, it can also point to the end, but not when dragging to the left. :)
565 if (i > data.index())
566 --i;
568 // If this was a drag between containers, we will have to adjust the number of
569 // visible icons.
570 bool drag_between_containers =
571 !toolbar_action_views_[data.index()]->visible();
572 model_->MoveExtensionIcon(GetIdAt(data.index()), i);
574 if (drag_between_containers) {
575 // Let the main container update the model.
576 if (in_overflow_mode())
577 main_container_->NotifyActionMovedToOverflow();
578 else // This is the main container.
579 model_->SetVisibleIconCount(model_->visible_icon_count() + 1);
582 OnDragExited(); // Perform clean up after dragging.
583 return ui::DragDropTypes::DRAG_MOVE;
586 void BrowserActionsContainer::GetAccessibleState(
587 ui::AXViewState* state) {
588 state->role = ui::AX_ROLE_GROUP;
589 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
592 void BrowserActionsContainer::WriteDragDataForView(View* sender,
593 const gfx::Point& press_pt,
594 OSExchangeData* data) {
595 DCHECK(data);
597 ToolbarActionViews::iterator iter = std::find(toolbar_action_views_.begin(),
598 toolbar_action_views_.end(),
599 sender);
600 DCHECK(iter != toolbar_action_views_.end());
601 ToolbarActionViewController* view_controller = (*iter)->view_controller();
602 drag_utils::SetDragImageOnDataObject(
603 view_controller->GetIconWithBadge(),
604 press_pt.OffsetFromOrigin(),
605 data);
606 // Fill in the remaining info.
607 BrowserActionDragData drag_data(view_controller->GetId(),
608 iter - toolbar_action_views_.begin());
609 drag_data.Write(profile_, data);
612 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
613 const gfx::Point& p) {
614 return ui::DragDropTypes::DRAG_MOVE;
617 bool BrowserActionsContainer::CanStartDragForView(View* sender,
618 const gfx::Point& press_pt,
619 const gfx::Point& p) {
620 // We don't allow dragging while we're highlighting.
621 return !model_->is_highlighting();
624 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
625 if (!done_resizing) {
626 resize_amount_ = resize_amount;
627 OnBrowserActionVisibilityChanged();
628 return;
631 // Up until now we've only been modifying the resize_amount, but now it is
632 // time to set the container size to the size we have resized to, and then
633 // animate to the nearest icon count size if necessary (which may be 0).
634 int max_width = IconCountToWidth(-1);
635 container_width_ =
636 std::min(std::max(0, container_width_ - resize_amount), max_width);
638 // Save off the desired number of visible icons. We do this now instead of at
639 // the end of the animation so that even if the browser is shut down while
640 // animating, the right value will be restored on next run.
641 int visible_icons = WidthToIconCount(container_width_);
642 model_->SetVisibleIconCount(visible_icons);
645 void BrowserActionsContainer::AnimationProgressed(
646 const gfx::Animation* animation) {
647 DCHECK_EQ(resize_animation_.get(), animation);
648 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
649 (container_width_ - animation_target_size_));
650 OnBrowserActionVisibilityChanged();
653 void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
654 container_width_ = animation_target_size_;
655 animation_target_size_ = 0;
656 resize_amount_ = 0;
657 suppress_chevron_ = false;
658 SetChevronVisibility();
659 OnBrowserActionVisibilityChanged();
661 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
662 observers_,
663 OnBrowserActionsContainerAnimationEnded());
666 content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
667 return browser_->tab_strip_model()->GetActiveWebContents();
670 extensions::ActiveTabPermissionGranter*
671 BrowserActionsContainer::GetActiveTabPermissionGranter() {
672 content::WebContents* web_contents = GetCurrentWebContents();
673 if (!web_contents)
674 return NULL;
675 return extensions::TabHelper::FromWebContents(web_contents)->
676 active_tab_permission_granter();
679 gfx::NativeView BrowserActionsContainer::TestGetPopup() {
680 return popup_owner_ ?
681 popup_owner_->view_controller()->GetPopupNativeView() :
682 NULL;
685 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
686 // If the views haven't been initialized yet, wait for the next call to
687 // paint (one will be triggered by entering highlight mode).
688 if (model_->is_highlighting() && !toolbar_action_views_.empty() &&
689 !in_overflow_mode()) {
690 views::Painter::PaintPainterAt(
691 canvas, highlight_painter_.get(), GetLocalBounds());
694 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
695 // dragging (like we do for tab dragging).
696 if (drop_position_.get()) {
697 // The two-pixel width drop indicator.
698 static const int kDropIndicatorWidth = 2;
700 // Convert back to a pixel offset into the container. First find the X
701 // coordinate of the drop icon.
702 int drop_icon_x = ToolbarView::kStandardSpacing +
703 (drop_position_->icon_in_row * IconWidth(true));
704 // Next, find the space before the drop icon. This will either be
705 // kItemSpacing or ToolbarView::kStandardSpacing, depending on whether this
706 // is the first icon.
707 // NOTE: Right now, these are the same. But let's do this right for if they
708 // ever aren't.
709 int space_before_drop_icon = drop_position_->icon_in_row == 0 ?
710 ToolbarView::kStandardSpacing : kItemSpacing;
711 // Now place the drop indicator halfway between this and the end of the
712 // previous icon. If there is an odd amount of available space between the
713 // two icons (or the icon and the address bar) after subtracting the drop
714 // indicator width, this calculation puts the extra pixel on the left side
715 // of the indicator, since when the indicator is between the address bar and
716 // the first icon, it looks better closer to the icon.
717 int drop_indicator_x = drop_icon_x -
718 ((space_before_drop_icon + kDropIndicatorWidth) / 2);
719 int row_height = IconHeight();
720 int drop_indicator_y = row_height * drop_position_->row;
721 gfx::Rect indicator_bounds(drop_indicator_x,
722 drop_indicator_y,
723 kDropIndicatorWidth,
724 row_height);
725 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
727 // Color of the drop indicator.
728 static const SkColor kDropIndicatorColor = SK_ColorBLACK;
729 canvas->FillRect(indicator_bounds, kDropIndicatorColor);
733 void BrowserActionsContainer::OnThemeChanged() {
734 LoadImages();
737 void BrowserActionsContainer::ViewHierarchyChanged(
738 const ViewHierarchyChangedDetails& details) {
739 if (!model_)
740 return;
742 if (details.is_add && details.child == this) {
743 if (!in_overflow_mode()) { // We only need one keybinding registry.
744 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
745 browser_->profile(),
746 parent()->GetFocusManager(),
747 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
748 this));
751 // Initial toolbar button creation and placement in the widget hierarchy.
752 // We do this here instead of in the constructor because AddBrowserAction
753 // calls Layout on the Toolbar, which needs this object to be constructed
754 // before its Layout function is called.
755 CreateToolbarActionViews();
759 // static
760 int BrowserActionsContainer::IconWidth(bool include_padding) {
761 static bool initialized = false;
762 static int icon_width = 0;
763 if (!initialized) {
764 initialized = true;
765 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
766 IDR_BROWSER_ACTION)->width();
768 return icon_width + (include_padding ? kItemSpacing : 0);
771 // static
772 int BrowserActionsContainer::IconHeight() {
773 static bool initialized = false;
774 static int icon_height = 0;
775 if (!initialized) {
776 initialized = true;
777 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
778 IDR_BROWSER_ACTION)->height();
780 return icon_height;
783 void BrowserActionsContainer::ToolbarExtensionAdded(const Extension* extension,
784 int index) {
785 DCHECK(!GetViewForExtension(extension)) <<
786 "Asked to add a browser action view for an extension that already exists";
787 if (chevron_)
788 chevron_->CloseMenu();
790 // Add the new action to the vector and the view hierarchy.
791 ToolbarActionView* view = new ToolbarActionView(
792 make_scoped_ptr(new ExtensionActionViewController(
793 extension,
794 browser_,
795 extensions::ExtensionActionManager::Get(profile_)->
796 GetExtensionAction(*extension))),
797 browser_,
798 this);
799 toolbar_action_views_.insert(toolbar_action_views_.begin() + index, view);
800 AddChildViewAt(view, index);
802 // If we are still initializing the container, don't bother animating.
803 if (!model_->extensions_initialized())
804 return;
806 // If this is just an upgrade, then don't worry about resizing.
807 if (!extensions::ExtensionSystem::Get(profile_)->runtime_data()->
808 IsBeingUpgraded(extension)) {
809 // We need to resize if either:
810 // - The container is set to display all icons, or
811 // - The container will need to expand to include the chevron. This can
812 // happen when the container is set to display <n> icons, where <n> is
813 // the number of icons before the new icon. With the new icon, the chevron
814 // will need to be displayed.
815 if (model_->all_icons_visible() ||
816 (model_->visible_icon_count() < toolbar_action_views_.size() &&
817 (chevron_ && !chevron_->visible()))) {
818 suppress_chevron_ = true;
819 Animate(gfx::Tween::LINEAR, GetIconCount());
820 return;
824 // Otherwise, we don't have to resize, so just redraw the (possibly modified)
825 // visible icon set.
826 OnBrowserActionVisibilityChanged();
829 void BrowserActionsContainer::ToolbarExtensionRemoved(
830 const Extension* extension) {
831 if (chevron_)
832 chevron_->CloseMenu();
834 size_t visible_actions = VisibleBrowserActionsAfterAnimation();
835 for (ToolbarActionViews::iterator i(toolbar_action_views_.begin());
836 i != toolbar_action_views_.end(); ++i) {
837 if ((*i)->view_controller()->GetId() == extension->id()) {
838 delete *i;
839 toolbar_action_views_.erase(i);
841 // If the extension is being upgraded we don't want the bar to shrink
842 // because the icon is just going to get re-added to the same location.
843 if (extensions::ExtensionSystem::Get(profile_)->runtime_data()->
844 IsBeingUpgraded(extension))
845 return;
847 if (toolbar_action_views_.size() > visible_actions) {
848 // If we have more icons than we can show, then we must not be changing
849 // the container size (since we either removed an icon from the main
850 // area and one from the overflow list will have shifted in, or we
851 // removed an entry directly from the overflow list).
852 OnBrowserActionVisibilityChanged();
853 } else {
854 // Either we went from overflow to no-overflow, or we shrunk the no-
855 // overflow container by 1. Either way the size changed, so animate.
856 if (chevron_)
857 chevron_->SetVisible(false);
858 Animate(gfx::Tween::EASE_OUT, toolbar_action_views_.size());
860 return; // We have found the action to remove, bail out.
865 void BrowserActionsContainer::ToolbarExtensionMoved(const Extension* extension,
866 int index) {
867 DCHECK(index >= 0 && index < static_cast<int>(toolbar_action_views_.size()));
869 ToolbarActionViews::iterator iter = toolbar_action_views_.begin();
870 while (iter != toolbar_action_views_.end() &&
871 (*iter)->view_controller()->GetId() != extension->id())
872 ++iter;
874 DCHECK(iter != toolbar_action_views_.end());
875 if (iter - toolbar_action_views_.begin() == index)
876 return; // Already in place.
878 ToolbarActionView* moved_view = *iter;
879 toolbar_action_views_.erase(iter);
880 toolbar_action_views_.insert(
881 toolbar_action_views_.begin() + index, moved_view);
883 Layout();
884 SchedulePaint();
887 void BrowserActionsContainer::ToolbarExtensionUpdated(
888 const Extension* extension) {
889 ToolbarActionView* view = GetViewForExtension(extension);
890 // There might not be a view in cases where we are highlighting or if we
891 // haven't fully initialized extensions.
892 if (view)
893 view->UpdateState();
896 bool BrowserActionsContainer::ShowExtensionActionPopup(
897 const Extension* extension,
898 bool grant_active_tab) {
899 // Don't override another popup, and only show in the active window.
900 if (popup_owner_ || !browser_->window()->IsActive())
901 return false;
903 ToolbarActionView* view = GetViewForExtension(extension);
904 return view && view->view_controller()->ExecuteAction(grant_active_tab);
907 void BrowserActionsContainer::ToolbarVisibleCountChanged() {
908 if (GetPreferredWidth() != container_width_) {
909 Animate(gfx::Tween::EASE_OUT, GetIconCount());
910 } else if (animation_target_size_ != 0) {
911 // It's possible that we're right where we're supposed to be in terms of
912 // icon count, but that we're also currently resizing. If this is the case,
913 // end the current animation with the current width.
914 animation_target_size_ = container_width_;
915 resize_animation_->Reset();
919 void BrowserActionsContainer::ToolbarHighlightModeChanged(
920 bool is_highlighting) {
921 // The visual highlighting is done in OnPaint(). It's a bit of a pain that
922 // we delete and recreate everything here, but given everything else going on
923 // (the lack of highlight, n more extensions appearing, etc), it's not worth
924 // the extra complexity to create and insert only the new extensions.
925 DeleteToolbarActionViews();
926 CreateToolbarActionViews();
927 Animate(gfx::Tween::LINEAR, GetIconCount());
930 void BrowserActionsContainer::OnToolbarReorderNecessary(
931 content::WebContents* web_contents) {
932 if (GetCurrentWebContents() == web_contents)
933 ReorderViews();
936 Browser* BrowserActionsContainer::GetBrowser() {
937 return browser_;
940 void BrowserActionsContainer::LoadImages() {
941 if (in_overflow_mode())
942 return; // Overflow mode has neither a chevron nor highlighting.
944 ui::ThemeProvider* tp = GetThemeProvider();
945 if (tp && chevron_) {
946 chevron_->SetImage(views::Button::STATE_NORMAL,
947 *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
950 const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT);
951 highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages));
954 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
955 SetVisible(!toolbar_action_views_.empty());
956 if (parent()) { // Parent can be null in testing.
957 parent()->Layout();
958 parent()->SchedulePaint();
962 int BrowserActionsContainer::GetPreferredWidth() {
963 return IconCountToWidth(GetIconCount());
966 void BrowserActionsContainer::SetChevronVisibility() {
967 if (chevron_) {
968 chevron_->SetVisible(
969 VisibleBrowserActionsAfterAnimation() < toolbar_action_views_.size());
973 int BrowserActionsContainer::IconCountToWidth(int icons) const {
974 if (icons < 0)
975 icons = toolbar_action_views_.size();
976 bool display_chevron =
977 chevron_ && static_cast<size_t>(icons) < toolbar_action_views_.size();
978 if (icons == 0 && !display_chevron)
979 return ToolbarView::kStandardSpacing;
980 int icons_size =
981 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
982 int chevron_size = display_chevron ?
983 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
984 // In overflow mode, our padding is to use item spacing on either end (just so
985 // we can see the drop indicator). Otherwise we use the standard toolbar
986 // spacing.
987 // Note: These are actually the same thing, but, on the offchance one
988 // changes, let's get it right.
989 int padding =
990 2 * (in_overflow_mode() ? kItemSpacing : ToolbarView::kStandardSpacing);
991 return icons_size + chevron_size + padding;
994 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
995 // Check for widths large enough to show the entire icon set.
996 if (pixels >= IconCountToWidth(-1))
997 return toolbar_action_views_.size();
999 // We reserve space for the padding on either side of the toolbar...
1000 int available_space = pixels - (ToolbarView::kStandardSpacing * 2);
1001 // ... and, if the chevron is enabled, the chevron.
1002 if (chevron_)
1003 available_space -= (chevron_->GetPreferredSize().width() + kChevronSpacing);
1005 // Now we add an extra between-item padding value so the space can be divided
1006 // evenly by (size of icon with padding).
1007 return static_cast<size_t>(
1008 std::max(0, available_space + kItemSpacing) / IconWidth(true));
1011 int BrowserActionsContainer::MinimumNonemptyWidth() const {
1012 if (!chevron_)
1013 return ToolbarView::kStandardSpacing;
1014 return (ToolbarView::kStandardSpacing * 2) + kChevronSpacing +
1015 chevron_->GetPreferredSize().width();
1018 void BrowserActionsContainer::Animate(gfx::Tween::Type tween_type,
1019 size_t num_visible_icons) {
1020 int target_size = IconCountToWidth(num_visible_icons);
1021 if (resize_animation_ && !disable_animations_during_testing_ &&
1022 !suppress_animation_) {
1023 // Animate! We have to set the animation_target_size_ after calling Reset(),
1024 // because that could end up calling AnimationEnded which clears the value.
1025 resize_animation_->Reset();
1026 resize_animation_->SetTweenType(tween_type);
1027 animation_target_size_ = target_size;
1028 resize_animation_->Show();
1029 } else {
1030 animation_target_size_ = target_size;
1031 AnimationEnded(resize_animation_.get());
1035 void BrowserActionsContainer::ReorderViews() {
1036 extensions::ExtensionList new_order =
1037 model_->GetItemOrderForTab(GetCurrentWebContents());
1038 if (new_order.empty())
1039 return; // Nothing to do.
1041 #if DCHECK_IS_ON
1042 // Make sure the lists are in sync. There should be a view for each action in
1043 // the new order.
1044 // |toolbar_action_views_| may have more views than actions are present in
1045 // |new_order| if there are any component toolbar actions.
1046 // TODO(devlin): Change this to DCHECK_EQ when all toolbar actions are shown
1047 // in the model.
1048 DCHECK_LE(new_order.size(), toolbar_action_views_.size());
1049 for (const scoped_refptr<const Extension>& extension : new_order)
1050 DCHECK(GetViewForExtension(extension.get()));
1051 #endif
1053 // Run through the views and compare them to the desired order. If something
1054 // is out of place, find the correct spot for it.
1055 for (size_t i = 0; i < new_order.size() - 1; ++i) {
1056 if (new_order[i]->id() !=
1057 toolbar_action_views_[i]->view_controller()->GetId()) {
1058 // Find where the correct view is (it's guaranteed to be after our current
1059 // index, since everything up to this point is correct).
1060 size_t j = i + 1;
1061 while (new_order[i]->id() !=
1062 toolbar_action_views_[j]->view_controller()->GetId())
1063 ++j;
1064 std::swap(toolbar_action_views_[i], toolbar_action_views_[j]);
1068 // Our visible browser actions may have changed - re-Layout() and check the
1069 // size.
1070 ToolbarVisibleCountChanged();
1071 OnBrowserActionVisibilityChanged();
1074 size_t BrowserActionsContainer::GetIconCount() const {
1075 if (!model_)
1076 return 0u;
1078 // Find the absolute value for the model's visible count.
1079 size_t model_visible_size = model_->GetVisibleIconCountForTab(
1080 browser_->tab_strip_model()->GetActiveWebContents());
1082 #if DCHECK_IS_ON
1083 // Good time for some sanity checks: We should never try to display more
1084 // icons than we have, and we should always have a view per item in the model.
1085 // (The only exception is if this is in initialization.)
1086 if (initialized_ && !suppress_layout_) {
1087 size_t num_extension_actions = 0u;
1088 for (ToolbarActionView* view : toolbar_action_views_) {
1089 // No component action should ever have a valid extension id, so we can
1090 // use this to check the extension amount.
1091 // TODO(devlin): Fix this to just check model size when the model also
1092 // includes component actions.
1093 if (crx_file::id_util::IdIsValid(view->view_controller()->GetId()))
1094 ++num_extension_actions;
1096 DCHECK_LE(model_visible_size, num_extension_actions);
1097 DCHECK_EQ(model_->toolbar_items().size(), num_extension_actions);
1099 #endif
1101 // The overflow displays any icons not shown by the main bar.
1102 return in_overflow_mode() ?
1103 model_->toolbar_items().size() - model_visible_size : model_visible_size;