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
;
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
55 ScopedVector
<ToolbarActionViewController
> GetToolbarActions(
56 extensions::ExtensionToolbarModel
* model
,
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(
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());
79 actions
.end(), component_actions
.begin(), component_actions
.end());
80 component_actions
.weak_clear();
81 return actions
.Pass();
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.
95 // The (0-indexed) icon in the row before the action will be dropped.
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
108 int BrowserActionsContainer::icons_per_overflow_menu_row_
= 1;
111 const int BrowserActionsContainer::kItemSpacing
= ToolbarView::kStandardSpacing
;
114 bool BrowserActionsContainer::disable_animations_during_testing_
= false;
116 BrowserActionsContainer::BrowserActionsContainer(
118 BrowserActionsContainer
* main_container
)
119 : initialized_(false),
120 profile_(browser
->profile()),
122 main_container_(main_container
),
124 model_(extensions::ExtensionToolbarModel::Get(browser
->profile())),
128 suppress_chevron_(false),
129 suppress_animation_(false),
130 suppress_layout_(false),
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
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
,
165 OnBrowserActionsContainerDestroyed());
168 model_
->RemoveObserver(this);
170 DeleteToolbarActionViews();
173 void BrowserActionsContainer::Init() {
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();
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())
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
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_
)
215 ReorderViews(); // Also triggers a layout.
218 void BrowserActionsContainer::CreateToolbarActionViews() {
219 DCHECK(toolbar_action_views_
.empty());
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
);
235 actions
.weak_clear();
242 void BrowserActionsContainer::DeleteToolbarActionViews() {
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_
) {
253 return visible_actions
;
256 size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
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
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
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
,
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.
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() {
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
323 ToolbarActionViews::const_iterator iter
=
324 std::find(toolbar_action_views_
.begin(),
325 toolbar_action_views_
.end(),
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.
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())
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_
)
386 if (toolbar_action_views_
.empty()) {
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
;
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
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.
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);
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(),
442 view
->SetVisible(true);
447 bool BrowserActionsContainer::GetDropFormats(
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
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:
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":
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
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
=
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".
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
));
538 return ui::DragDropTypes::DRAG_MOVE
;
541 void BrowserActionsContainer::OnDragExited() {
542 drop_position_
.reset();
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());
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())
568 // If this was a drag between containers, we will have to adjust the number of
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
) {
597 ToolbarActionViews::iterator iter
= std::find(toolbar_action_views_
.begin(),
598 toolbar_action_views_
.end(),
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(),
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();
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);
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;
657 suppress_chevron_
= false;
658 SetChevronVisibility();
659 OnBrowserActionVisibilityChanged();
661 FOR_EACH_OBSERVER(BrowserActionsContainerObserver
,
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();
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() :
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
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
,
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() {
737 void BrowserActionsContainer::ViewHierarchyChanged(
738 const ViewHierarchyChangedDetails
& details
) {
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(
746 parent()->GetFocusManager(),
747 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS
,
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();
760 int BrowserActionsContainer::IconWidth(bool include_padding
) {
761 static bool initialized
= false;
762 static int icon_width
= 0;
765 icon_width
= ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
766 IDR_BROWSER_ACTION
)->width();
768 return icon_width
+ (include_padding
? kItemSpacing
: 0);
772 int BrowserActionsContainer::IconHeight() {
773 static bool initialized
= false;
774 static int icon_height
= 0;
777 icon_height
= ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
778 IDR_BROWSER_ACTION
)->height();
783 void BrowserActionsContainer::ToolbarExtensionAdded(const Extension
* extension
,
785 DCHECK(!GetViewForExtension(extension
)) <<
786 "Asked to add a browser action view for an extension that already exists";
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(
795 extensions::ExtensionActionManager::Get(profile_
)->
796 GetExtensionAction(*extension
))),
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())
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());
824 // Otherwise, we don't have to resize, so just redraw the (possibly modified)
826 OnBrowserActionVisibilityChanged();
829 void BrowserActionsContainer::ToolbarExtensionRemoved(
830 const Extension
* extension
) {
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()) {
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
))
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();
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.
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
,
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())
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
);
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.
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())
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
)
936 Browser
* BrowserActionsContainer::GetBrowser() {
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.
958 parent()->SchedulePaint();
962 int BrowserActionsContainer::GetPreferredWidth() {
963 return IconCountToWidth(GetIconCount());
966 void BrowserActionsContainer::SetChevronVisibility() {
968 chevron_
->SetVisible(
969 VisibleBrowserActionsAfterAnimation() < toolbar_action_views_
.size());
973 int BrowserActionsContainer::IconCountToWidth(int icons
) const {
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
;
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
987 // Note: These are actually the same thing, but, on the offchance one
988 // changes, let's get it right.
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.
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 {
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();
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.
1042 // Make sure the lists are in sync. There should be a view for each action in
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
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()));
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).
1061 while (new_order
[i
]->id() !=
1062 toolbar_action_views_
[j
]->view_controller()->GetId())
1064 std::swap(toolbar_action_views_
[i
], toolbar_action_views_
[j
]);
1068 // Our visible browser actions may have changed - re-Layout() and check the
1070 ToolbarVisibleCountChanged();
1071 OnBrowserActionVisibilityChanged();
1074 size_t BrowserActionsContainer::GetIconCount() const {
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());
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
);
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
;