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/prefs/pref_service.h"
9 #include "base/stl_util.h"
10 #include "chrome/browser/extensions/extension_service.h"
11 #include "chrome/browser/extensions/extension_util.h"
12 #include "chrome/browser/extensions/extension_view_host.h"
13 #include "chrome/browser/extensions/tab_helper.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/sessions/session_tab_helper.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_window.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/browser/ui/view_ids.h"
20 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
21 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
22 #include "chrome/browser/ui/views/extensions/extension_popup.h"
23 #include "chrome/browser/ui/views/toolbar/browser_action_view.h"
24 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
25 #include "chrome/common/extensions/command.h"
26 #include "chrome/common/pref_names.h"
27 #include "extensions/browser/extension_system.h"
28 #include "extensions/browser/pref_names.h"
29 #include "extensions/browser/runtime_data.h"
30 #include "grit/generated_resources.h"
31 #include "grit/theme_resources.h"
32 #include "grit/ui_resources.h"
33 #include "third_party/skia/include/core/SkColor.h"
34 #include "ui/accessibility/ax_view_state.h"
35 #include "ui/base/dragdrop/drag_utils.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/nine_image_painter_factory.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/base/theme_provider.h"
40 #include "ui/gfx/animation/slide_animation.h"
41 #include "ui/gfx/canvas.h"
42 #include "ui/gfx/geometry/rect.h"
43 #include "ui/views/controls/resize_area.h"
44 #include "ui/views/metrics.h"
45 #include "ui/views/painter.h"
46 #include "ui/views/widget/widget.h"
48 using extensions::Extension
;
52 // Horizontal spacing between most items in the container, as well as after the
53 // last item or chevron (if visible).
54 const int kItemSpacing
= ToolbarView::kStandardSpacing
;
56 // Horizontal spacing before the chevron (if visible).
57 const int kChevronSpacing
= kItemSpacing
- 2;
62 bool BrowserActionsContainer::disable_animations_during_testing_
= false;
64 ////////////////////////////////////////////////////////////////////////////////
65 // BrowserActionsContainer
67 BrowserActionsContainer::BrowserActionsContainer(Browser
* browser
,
69 : profile_(browser
->profile()),
71 owner_view_(owner_view
),
78 suppress_chevron_(false),
80 animation_target_size_(0),
81 drop_indicator_position_(-1),
83 show_menu_task_factory_(this) {
84 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR
);
86 model_
= extensions::ExtensionToolbarModel::Get(browser
->profile());
88 model_
->AddObserver(this);
90 extension_keybinding_registry_
.reset(new ExtensionKeybindingRegistryViews(
92 owner_view
->GetFocusManager(),
93 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS
,
96 resize_animation_
.reset(new gfx::SlideAnimation(this));
97 resize_area_
= new views::ResizeArea(this);
98 AddChildView(resize_area_
);
100 chevron_
= new views::MenuButton(NULL
, base::string16(), this, false);
101 chevron_
->SetBorder(views::Border::NullBorder());
102 chevron_
->EnableCanvasFlippingForRTLUI(true);
103 chevron_
->SetAccessibleName(
104 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON
));
105 chevron_
->SetVisible(false);
106 AddChildView(chevron_
);
109 BrowserActionsContainer::~BrowserActionsContainer() {
110 FOR_EACH_OBSERVER(BrowserActionsContainerObserver
,
112 OnBrowserActionsContainerDestroyed());
115 overflow_menu_
->set_observer(NULL
);
117 model_
->RemoveObserver(this);
118 StopShowFolderDropMenuTimer();
120 popup_
->GetWidget()->RemoveObserver(this);
122 DeleteBrowserActionViews();
125 void BrowserActionsContainer::Init() {
128 // We wait to set the container width until now so that the chevron images
129 // will be loaded. The width calculation needs to know the chevron size.
131 !profile_
->GetPrefs()->HasPrefPath(
132 extensions::pref_names::kToolbarSize
)) {
133 // Migration code to the new VisibleIconCount pref.
134 // TODO(mpcomplete): remove this after users are upgraded to 5.0.
135 int predefined_width
= profile_
->GetPrefs()->GetInteger(
136 extensions::pref_names::kBrowserActionContainerWidth
);
137 if (predefined_width
!= 0)
138 model_
->SetVisibleIconCount(WidthToIconCount(predefined_width
));
140 if (model_
&& model_
->extensions_initialized())
144 BrowserActionView
* BrowserActionsContainer::GetBrowserActionView(
145 ExtensionAction
* action
) {
146 for (BrowserActionViews::iterator
i(browser_action_views_
.begin());
147 i
!= browser_action_views_
.end(); ++i
) {
148 if ((*i
)->button()->browser_action() == action
)
154 void BrowserActionsContainer::RefreshBrowserActionViews() {
155 for (size_t i
= 0; i
< browser_action_views_
.size(); ++i
)
156 browser_action_views_
[i
]->button()->UpdateState();
159 void BrowserActionsContainer::CreateBrowserActionViews() {
160 DCHECK(browser_action_views_
.empty());
164 const extensions::ExtensionList
& toolbar_items
= model_
->toolbar_items();
165 for (extensions::ExtensionList::const_iterator
i(toolbar_items
.begin());
166 i
!= toolbar_items
.end(); ++i
) {
167 if (!ShouldDisplayBrowserAction(i
->get()))
170 BrowserActionView
* view
= new BrowserActionView(i
->get(), browser_
, this);
171 browser_action_views_
.push_back(view
);
176 void BrowserActionsContainer::DeleteBrowserActionViews() {
178 STLDeleteElements(&browser_action_views_
);
181 size_t BrowserActionsContainer::VisibleBrowserActions() const {
182 size_t visible_actions
= 0;
183 for (size_t i
= 0; i
< browser_action_views_
.size(); ++i
) {
184 if (browser_action_views_
[i
]->visible())
187 VLOG(4) << "BAC::VisibleBrowserActions() returns " << visible_actions
188 << " with size=" << browser_action_views_
.size();
189 return visible_actions
;
192 void BrowserActionsContainer::ExecuteExtensionCommand(
193 const extensions::Extension
* extension
,
194 const extensions::Command
& command
) {
195 // Global commands are handled by the ExtensionCommandsGlobalRegistry
197 DCHECK(!command
.global());
198 extension_keybinding_registry_
->ExecuteCommand(extension
->id(),
199 command
.accelerator());
202 void BrowserActionsContainer::AddObserver(
203 BrowserActionsContainerObserver
* observer
) {
204 observers_
.AddObserver(observer
);
207 void BrowserActionsContainer::RemoveObserver(
208 BrowserActionsContainerObserver
* observer
) {
209 observers_
.RemoveObserver(observer
);
212 gfx::Size
BrowserActionsContainer::GetPreferredSize() const {
213 // We calculate the size of the view by taking the current width and
214 // subtracting resize_amount_ (the latter represents how far the user is
215 // resizing the view or, if animating the snapping, how far to animate it).
216 // But we also clamp it to a minimum size and the maximum size, so that the
217 // container can never shrink too far or take up more space than it needs. In
218 // other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX).
219 int preferred_width
= std::min(
220 std::max(MinimumNonemptyWidth(), container_width_
- resize_amount_
),
221 IconCountToWidth(-1, false));
222 // Height will be ignored by the ToolbarView.
223 return gfx::Size(preferred_width
, 0);
226 gfx::Size
BrowserActionsContainer::GetMinimumSize() const {
227 int min_width
= std::min(MinimumNonemptyWidth(), IconCountToWidth(-1, false));
228 // Height will be ignored by the ToolbarView.
229 return gfx::Size(min_width
, 0);
232 void BrowserActionsContainer::Layout() {
233 if (browser_action_views_
.empty()) {
239 resize_area_
->SetBounds(0, 0, kItemSpacing
, height());
241 // If the icons don't all fit, show the chevron (unless suppressed).
242 int max_x
= GetPreferredSize().width();
243 if ((IconCountToWidth(-1, false) > max_x
) && !suppress_chevron_
) {
244 chevron_
->SetVisible(true);
245 gfx::Size
chevron_size(chevron_
->GetPreferredSize());
247 ToolbarView::kStandardSpacing
+ chevron_size
.width() + kChevronSpacing
;
249 width() - ToolbarView::kStandardSpacing
- chevron_size
.width(),
251 chevron_size
.width(),
252 chevron_size
.height());
254 chevron_
->SetVisible(false);
257 // Now draw the icons for the browser actions in the available space.
258 int icon_width
= IconWidth(false);
259 for (size_t i
= 0; i
< browser_action_views_
.size(); ++i
) {
260 BrowserActionView
* view
= browser_action_views_
[i
];
261 int x
= ToolbarView::kStandardSpacing
+ (i
* IconWidth(true));
262 if (x
+ icon_width
<= max_x
) {
263 view
->SetBounds(x
, 0, icon_width
, height());
264 view
->SetVisible(true);
266 view
->SetVisible(false);
271 bool BrowserActionsContainer::GetDropFormats(
273 std::set
<OSExchangeData::CustomFormat
>* custom_formats
) {
274 custom_formats
->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
279 bool BrowserActionsContainer::AreDropTypesRequired() {
283 bool BrowserActionsContainer::CanDrop(const OSExchangeData
& data
) {
284 BrowserActionDragData drop_data
;
285 return drop_data
.Read(data
) ? drop_data
.IsFromProfile(profile_
) : false;
288 void BrowserActionsContainer::OnDragEntered(
289 const ui::DropTargetEvent
& event
) {
292 int BrowserActionsContainer::OnDragUpdated(
293 const ui::DropTargetEvent
& event
) {
294 // First check if we are above the chevron (overflow) menu.
295 if (GetEventHandlerForPoint(event
.location()) == chevron_
) {
296 if (!show_menu_task_factory_
.HasWeakPtrs() && !overflow_menu_
)
297 StartShowFolderDropMenuTimer();
298 return ui::DragDropTypes::DRAG_MOVE
;
300 StopShowFolderDropMenuTimer();
302 // Figure out where to display the indicator. This is a complex calculation:
304 // First, we figure out how much space is to the left of the icon area, so we
305 // can calculate the true offset into the icon area.
306 int width_before_icons
= ToolbarView::kStandardSpacing
+
307 (base::i18n::IsRTL() ?
308 (chevron_
->GetPreferredSize().width() + kChevronSpacing
) : 0);
309 int offset_into_icon_area
= event
.x() - width_before_icons
;
311 // Next, we determine which icon to place the indicator in front of. We want
312 // to place the indicator in front of icon n when the cursor is between the
313 // midpoints of icons (n - 1) and n. To do this we take the offset into the
314 // icon area and transform it as follows:
319 // |[IC|ON] [IC|ON] [IC|ON]
320 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
321 // Here the "*" represents the offset into the icon area, and since it's
322 // between a and b, we want to return "1".
324 // Transformed "icon area":
327 // |[ICON] |[ICON] |[ICON] |
328 // If we shift both our offset and our divider points later by half an icon
329 // plus one spacing unit, then it becomes very easy to calculate how many
330 // divider points we've passed, because they're the multiples of "one icon
332 int before_icon_unclamped
= (offset_into_icon_area
+ (IconWidth(false) / 2) +
333 kItemSpacing
) / IconWidth(true);
335 // Because the user can drag outside the container bounds, we need to clamp to
336 // the valid range. Note that the maximum allowable value is (num icons), not
337 // (num icons - 1), because we represent the indicator being past the last
338 // icon as being "before the (last + 1) icon".
339 int before_icon
= std::min(std::max(before_icon_unclamped
, 0),
340 static_cast<int>(VisibleBrowserActions()));
342 // Now we convert back to a pixel offset into the container. We want to place
343 // the center of the drop indicator at the midpoint of the space before our
345 SetDropIndicator(width_before_icons
+ (before_icon
* IconWidth(true)) -
348 return ui::DragDropTypes::DRAG_MOVE
;
351 void BrowserActionsContainer::OnDragExited() {
352 StopShowFolderDropMenuTimer();
353 drop_indicator_position_
= -1;
357 int BrowserActionsContainer::OnPerformDrop(
358 const ui::DropTargetEvent
& event
) {
359 BrowserActionDragData data
;
360 if (!data
.Read(event
.data()))
361 return ui::DragDropTypes::DRAG_NONE
;
363 // Make sure we have the same view as we started with.
364 DCHECK_EQ(browser_action_views_
[data
.index()]->button()->extension()->id(),
369 for (; i
< browser_action_views_
.size(); ++i
) {
370 int view_x
= browser_action_views_
[i
]->GetMirroredBounds().x();
371 if (!browser_action_views_
[i
]->visible() ||
372 (base::i18n::IsRTL() ? (view_x
< drop_indicator_position_
) :
373 (view_x
>= drop_indicator_position_
))) {
374 // We have reached the end of the visible icons or found one that has a
375 // higher x position than the drop point.
380 // |i| now points to the item to the right of the drop indicator*, which is
381 // correct when dragging an icon to the left. When dragging to the right,
382 // however, we want the icon being dragged to get the index of the item to
383 // the left of the drop indicator, so we subtract one.
384 // * Well, it can also point to the end, but not when dragging to the left. :)
385 if (i
> data
.index())
388 if (profile_
->IsOffTheRecord())
389 i
= model_
->IncognitoIndexToOriginal(i
);
391 model_
->MoveBrowserAction(
392 browser_action_views_
[data
.index()]->button()->extension(), i
);
394 OnDragExited(); // Perform clean up after dragging.
395 return ui::DragDropTypes::DRAG_MOVE
;
398 void BrowserActionsContainer::GetAccessibleState(
399 ui::AXViewState
* state
) {
400 state
->role
= ui::AX_ROLE_GROUP
;
401 state
->name
= l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS
);
404 void BrowserActionsContainer::OnMenuButtonClicked(views::View
* source
,
405 const gfx::Point
& point
) {
406 if (source
== chevron_
) {
407 overflow_menu_
= new BrowserActionOverflowMenuController(
408 this, browser_
, chevron_
, browser_action_views_
,
409 VisibleBrowserActions());
410 overflow_menu_
->set_observer(this);
411 overflow_menu_
->RunMenu(GetWidget(), false);
415 void BrowserActionsContainer::WriteDragDataForView(View
* sender
,
416 const gfx::Point
& press_pt
,
417 OSExchangeData
* data
) {
420 for (size_t i
= 0; i
< browser_action_views_
.size(); ++i
) {
421 BrowserActionButton
* button
= browser_action_views_
[i
]->button();
422 if (button
== sender
) {
423 // Set the dragging image for the icon.
424 gfx::ImageSkia
badge(browser_action_views_
[i
]->GetIconWithBadge());
425 drag_utils::SetDragImageOnDataObject(badge
, button
->size(),
426 press_pt
.OffsetFromOrigin(),
429 // Fill in the remaining info.
430 BrowserActionDragData
drag_data(
431 browser_action_views_
[i
]->button()->extension()->id(), i
);
432 drag_data
.Write(profile_
, data
);
438 int BrowserActionsContainer::GetDragOperationsForView(View
* sender
,
439 const gfx::Point
& p
) {
440 return ui::DragDropTypes::DRAG_MOVE
;
443 bool BrowserActionsContainer::CanStartDragForView(View
* sender
,
444 const gfx::Point
& press_pt
,
445 const gfx::Point
& p
) {
446 // We don't allow dragging while we're highlighting.
447 return !model_
->is_highlighting();
450 void BrowserActionsContainer::OnResize(int resize_amount
, bool done_resizing
) {
451 if (!done_resizing
) {
452 resize_amount_
= resize_amount
;
453 OnBrowserActionVisibilityChanged();
457 // Up until now we've only been modifying the resize_amount, but now it is
458 // time to set the container size to the size we have resized to, and then
459 // animate to the nearest icon count size if necessary (which may be 0).
460 int max_width
= IconCountToWidth(-1, false);
462 std::min(std::max(0, container_width_
- resize_amount
), max_width
);
463 SaveDesiredSizeAndAnimate(gfx::Tween::EASE_OUT
,
464 WidthToIconCount(container_width_
));
467 void BrowserActionsContainer::AnimationProgressed(
468 const gfx::Animation
* animation
) {
469 DCHECK_EQ(resize_animation_
.get(), animation
);
470 resize_amount_
= static_cast<int>(resize_animation_
->GetCurrentValue() *
471 (container_width_
- animation_target_size_
));
472 OnBrowserActionVisibilityChanged();
475 void BrowserActionsContainer::AnimationEnded(const gfx::Animation
* animation
) {
476 container_width_
= animation_target_size_
;
477 animation_target_size_
= 0;
479 suppress_chevron_
= false;
480 OnBrowserActionVisibilityChanged();
482 FOR_EACH_OBSERVER(BrowserActionsContainerObserver
,
484 OnBrowserActionsContainerAnimationEnded());
487 void BrowserActionsContainer::NotifyMenuDeleted(
488 BrowserActionOverflowMenuController
* controller
) {
489 DCHECK_EQ(overflow_menu_
, controller
);
490 overflow_menu_
= NULL
;
493 void BrowserActionsContainer::OnWidgetDestroying(views::Widget
* widget
) {
494 DCHECK_EQ(popup_
->GetWidget(), widget
);
495 popup_
->GetWidget()->RemoveObserver(this);
497 // |popup_button_| is NULL if the extension has been removed.
499 popup_button_
->SetButtonNotPushed();
500 popup_button_
= NULL
;
504 void BrowserActionsContainer::InspectPopup(ExtensionAction
* action
) {
505 BrowserActionView
* view
= GetBrowserActionView(action
);
506 ShowPopup(view
->button(), ExtensionPopup::SHOW_AND_INSPECT
, true);
509 int BrowserActionsContainer::GetCurrentTabId() const {
510 content::WebContents
* active_tab
=
511 browser_
->tab_strip_model()->GetActiveWebContents();
515 return SessionTabHelper::FromWebContents(active_tab
)->session_id().id();
518 void BrowserActionsContainer::OnBrowserActionExecuted(
519 BrowserActionButton
* button
) {
520 ShowPopup(button
, ExtensionPopup::SHOW
, true);
523 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
524 SetVisible(!browser_action_views_
.empty());
525 owner_view_
->Layout();
526 owner_view_
->SchedulePaint();
529 extensions::ActiveTabPermissionGranter
*
530 BrowserActionsContainer::GetActiveTabPermissionGranter() {
531 content::WebContents
* web_contents
=
532 browser_
->tab_strip_model()->GetActiveWebContents();
535 return extensions::TabHelper::FromWebContents(web_contents
)->
536 active_tab_permission_granter();
539 void BrowserActionsContainer::MoveBrowserAction(const std::string
& extension_id
,
541 ExtensionService
* service
=
542 extensions::ExtensionSystem::Get(profile_
)->extension_service();
544 const Extension
* extension
= service
->GetExtensionById(extension_id
, false);
545 model_
->MoveBrowserAction(extension
, new_index
);
550 bool BrowserActionsContainer::ShowPopup(const extensions::Extension
* extension
,
552 // Do not override other popups and only show in active window. The window
553 // must also have a toolbar, otherwise it should not be showing popups.
554 // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
557 !browser_
->window()->IsActive() ||
558 !browser_
->window()->IsToolbarVisible()) {
562 for (BrowserActionViews::iterator it
= browser_action_views_
.begin();
563 it
!= browser_action_views_
.end(); ++it
) {
564 BrowserActionButton
* button
= (*it
)->button();
565 if (button
&& button
->extension() == extension
)
566 return ShowPopup(button
, ExtensionPopup::SHOW
, should_grant
);
571 void BrowserActionsContainer::HidePopup() {
572 // Remove this as an observer and clear |popup_| and |popup_button_| here,
573 // since we might change them before OnWidgetDestroying() gets called.
575 popup_
->GetWidget()->RemoveObserver(this);
576 popup_
->GetWidget()->Close();
580 popup_button_
->SetButtonNotPushed();
581 popup_button_
= NULL
;
585 void BrowserActionsContainer::TestExecuteBrowserAction(int index
) {
586 BrowserActionButton
* button
= browser_action_views_
[index
]->button();
587 OnBrowserActionExecuted(button
);
590 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons
) {
591 model_
->SetVisibleIconCount(icons
);
592 chevron_
->SetVisible(icons
< browser_action_views_
.size());
593 container_width_
= IconCountToWidth(icons
, chevron_
->visible());
598 void BrowserActionsContainer::OnPaint(gfx::Canvas
* canvas
) {
599 // If the views haven't been initialized yet, wait for the next call to
600 // paint (one will be triggered by entering highlight mode).
601 if (model_
->is_highlighting() && !browser_action_views_
.empty()) {
602 views::Painter::PaintPainterAt(
603 canvas
, highlight_painter_
.get(), GetLocalBounds());
606 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
607 // dragging (like we do for tab dragging).
608 if (drop_indicator_position_
> -1) {
609 // The two-pixel width drop indicator.
610 static const int kDropIndicatorWidth
= 2;
611 gfx::Rect
indicator_bounds(
612 drop_indicator_position_
- (kDropIndicatorWidth
/ 2),
617 // Color of the drop indicator.
618 static const SkColor kDropIndicatorColor
= SK_ColorBLACK
;
619 canvas
->FillRect(indicator_bounds
, kDropIndicatorColor
);
623 void BrowserActionsContainer::OnThemeChanged() {
627 void BrowserActionsContainer::ViewHierarchyChanged(
628 const ViewHierarchyChangedDetails
& details
) {
629 // No extensions (e.g., incognito).
633 if (details
.is_add
&& details
.child
== this) {
634 // Initial toolbar button creation and placement in the widget hierarchy.
635 // We do this here instead of in the constructor because AddBrowserAction
636 // calls Layout on the Toolbar, which needs this object to be constructed
637 // before its Layout function is called.
638 CreateBrowserActionViews();
643 int BrowserActionsContainer::IconWidth(bool include_padding
) {
644 static bool initialized
= false;
645 static int icon_width
= 0;
648 icon_width
= ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
649 IDR_BROWSER_ACTION
)->width();
651 return icon_width
+ (include_padding
? kItemSpacing
: 0);
655 int BrowserActionsContainer::IconHeight() {
656 static bool initialized
= false;
657 static int icon_height
= 0;
660 icon_height
= ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
661 IDR_BROWSER_ACTION
)->height();
666 void BrowserActionsContainer::BrowserActionAdded(const Extension
* extension
,
669 for (size_t i
= 0; i
< browser_action_views_
.size(); ++i
) {
670 DCHECK(browser_action_views_
[i
]->button()->extension() != extension
) <<
671 "Asked to add a browser action view for an extension that already "
677 if (!ShouldDisplayBrowserAction(extension
)) {
678 VLOG(4) << "Should not display: " << extension
->name().c_str();
682 size_t visible_actions
= VisibleBrowserActions();
683 VLOG(4) << "Got back " << visible_actions
<< " visible.";
685 // Add the new browser action to the vector and the view hierarchy.
686 if (profile_
->IsOffTheRecord())
687 index
= model_
->OriginalIndexToIncognito(index
);
688 BrowserActionView
* view
= new BrowserActionView(extension
, browser_
, this);
689 browser_action_views_
.insert(browser_action_views_
.begin() + index
, view
);
690 AddChildViewAt(view
, index
);
692 // If we are still initializing the container, don't bother animating.
693 if (!model_
->extensions_initialized()) {
694 VLOG(4) << "Still initializing";
698 // Enlarge the container if it was already at maximum size and we're not in
699 // the middle of upgrading.
700 if ((model_
->GetVisibleIconCount() < 0) &&
701 !extensions::ExtensionSystem::Get(profile_
)->runtime_data()->
702 IsBeingUpgraded(extension
)) {
703 VLOG(4) << "At max, Save and animate";
704 suppress_chevron_
= true;
705 SaveDesiredSizeAndAnimate(gfx::Tween::LINEAR
, visible_actions
+ 1);
707 VLOG(4) << "Not at max";
708 // Just redraw the (possibly modified) visible icon set.
709 OnBrowserActionVisibilityChanged();
713 void BrowserActionsContainer::BrowserActionRemoved(const Extension
* extension
) {
716 if (popup_
&& popup_
->host()->extension() == extension
)
719 size_t visible_actions
= VisibleBrowserActions();
720 for (BrowserActionViews::iterator
i(browser_action_views_
.begin());
721 i
!= browser_action_views_
.end(); ++i
) {
722 if ((*i
)->button()->extension() == extension
) {
724 browser_action_views_
.erase(i
);
726 // If the extension is being upgraded we don't want the bar to shrink
727 // because the icon is just going to get re-added to the same location.
728 if (extensions::ExtensionSystem::Get(profile_
)->runtime_data()->
729 IsBeingUpgraded(extension
))
732 if (browser_action_views_
.size() > visible_actions
) {
733 // If we have more icons than we can show, then we must not be changing
734 // the container size (since we either removed an icon from the main
735 // area and one from the overflow list will have shifted in, or we
736 // removed an entry directly from the overflow list).
737 OnBrowserActionVisibilityChanged();
739 // Either we went from overflow to no-overflow, or we shrunk the no-
740 // overflow container by 1. Either way the size changed, so animate.
741 chevron_
->SetVisible(false);
742 SaveDesiredSizeAndAnimate(gfx::Tween::EASE_OUT
,
743 browser_action_views_
.size());
750 void BrowserActionsContainer::BrowserActionMoved(const Extension
* extension
,
752 if (!ShouldDisplayBrowserAction(extension
))
755 if (profile_
->IsOffTheRecord())
756 index
= model_
->OriginalIndexToIncognito(index
);
758 DCHECK(index
>= 0 && index
< static_cast<int>(browser_action_views_
.size()));
760 DeleteBrowserActionViews();
761 CreateBrowserActionViews();
766 bool BrowserActionsContainer::BrowserActionShowPopup(
767 const extensions::Extension
* extension
) {
768 return ShowPopup(extension
, false);
771 void BrowserActionsContainer::VisibleCountChanged() {
775 void BrowserActionsContainer::HighlightModeChanged(bool is_highlighting
) {
776 // The visual highlighting is done in OnPaint(). It's a bit of a pain that
777 // we delete and recreate everything here, but that's how it's done in
778 // BrowserActionMoved(), too. If we want to optimize it, we could move the
779 // existing icons, instead of deleting it all.
780 DeleteBrowserActionViews();
781 CreateBrowserActionViews();
782 SaveDesiredSizeAndAnimate(gfx::Tween::LINEAR
, browser_action_views_
.size());
785 void BrowserActionsContainer::LoadImages() {
786 ui::ThemeProvider
* tp
= GetThemeProvider();
787 chevron_
->SetIcon(*tp
->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW
));
788 chevron_
->SetHoverIcon(*tp
->GetImageSkiaNamed(
789 IDR_BROWSER_ACTIONS_OVERFLOW_H
));
790 chevron_
->SetPushedIcon(*tp
->GetImageSkiaNamed(
791 IDR_BROWSER_ACTIONS_OVERFLOW_P
));
793 const int kImages
[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT
);
794 highlight_painter_
.reset(views::Painter::CreateImageGridPainter(kImages
));
797 void BrowserActionsContainer::SetContainerWidth() {
798 int visible_actions
= model_
->GetVisibleIconCount();
799 if (visible_actions
< 0) // All icons should be visible.
800 visible_actions
= model_
->toolbar_items().size();
801 chevron_
->SetVisible(
802 static_cast<size_t>(visible_actions
) < model_
->toolbar_items().size());
803 container_width_
= IconCountToWidth(visible_actions
, chevron_
->visible());
806 void BrowserActionsContainer::CloseOverflowMenu() {
808 overflow_menu_
->CancelMenu();
811 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
812 show_menu_task_factory_
.InvalidateWeakPtrs();
815 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
816 base::MessageLoop::current()->PostDelayedTask(
818 base::Bind(&BrowserActionsContainer::ShowDropFolder
,
819 show_menu_task_factory_
.GetWeakPtr()),
820 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
823 void BrowserActionsContainer::ShowDropFolder() {
824 DCHECK(!overflow_menu_
);
825 SetDropIndicator(-1);
826 overflow_menu_
= new BrowserActionOverflowMenuController(
827 this, browser_
, chevron_
, browser_action_views_
, VisibleBrowserActions());
828 overflow_menu_
->set_observer(this);
829 overflow_menu_
->RunMenu(GetWidget(), true);
832 void BrowserActionsContainer::SetDropIndicator(int x_pos
) {
833 if (drop_indicator_position_
!= x_pos
) {
834 drop_indicator_position_
= x_pos
;
839 int BrowserActionsContainer::IconCountToWidth(int icons
,
840 bool display_chevron
) const {
842 icons
= browser_action_views_
.size();
843 if ((icons
== 0) && !display_chevron
)
844 return ToolbarView::kStandardSpacing
;
846 (icons
== 0) ? 0 : ((icons
* IconWidth(true)) - kItemSpacing
);
847 int chevron_size
= display_chevron
?
848 (kChevronSpacing
+ chevron_
->GetPreferredSize().width()) : 0;
849 return ToolbarView::kStandardSpacing
+ icons_size
+ chevron_size
+
850 ToolbarView::kStandardSpacing
;
853 size_t BrowserActionsContainer::WidthToIconCount(int pixels
) const {
854 // Check for widths large enough to show the entire icon set.
855 if (pixels
>= IconCountToWidth(-1, false))
856 return browser_action_views_
.size();
858 // We need to reserve space for the resize area, chevron, and the spacing on
859 // either side of the chevron.
860 int available_space
= pixels
- ToolbarView::kStandardSpacing
-
861 chevron_
->GetPreferredSize().width() - kChevronSpacing
-
862 ToolbarView::kStandardSpacing
;
863 // Now we add an extra between-item padding value so the space can be divided
864 // evenly by (size of icon with padding).
865 return static_cast<size_t>(
866 std::max(0, available_space
+ kItemSpacing
) / IconWidth(true));
869 int BrowserActionsContainer::MinimumNonemptyWidth() const {
870 return ToolbarView::kStandardSpacing
+ kChevronSpacing
+
871 chevron_
->GetPreferredSize().width() + ToolbarView::kStandardSpacing
;
874 void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
875 gfx::Tween::Type tween_type
,
876 size_t num_visible_icons
) {
877 // Save off the desired number of visible icons. We do this now instead of at
878 // the end of the animation so that even if the browser is shut down while
879 // animating, the right value will be restored on next run.
880 // NOTE: Don't save the icon count in incognito because there may be fewer
881 // icons in that mode. The result is that the container in a normal window is
882 // always at least as wide as in an incognito window.
883 if (!profile_
->IsOffTheRecord()) {
884 model_
->SetVisibleIconCount(num_visible_icons
);
885 VLOG(4) << "Setting visible count: " << num_visible_icons
;
887 VLOG(4) << "|Skipping| setting visible count: " << num_visible_icons
;
889 int target_size
= IconCountToWidth(num_visible_icons
,
890 num_visible_icons
< browser_action_views_
.size());
891 if (!disable_animations_during_testing_
) {
892 // Animate! We have to set the animation_target_size_ after calling Reset(),
893 // because that could end up calling AnimationEnded which clears the value.
894 resize_animation_
->Reset();
895 resize_animation_
->SetTweenType(tween_type
);
896 animation_target_size_
= target_size
;
897 resize_animation_
->Show();
899 animation_target_size_
= target_size
;
900 AnimationEnded(resize_animation_
.get());
904 bool BrowserActionsContainer::ShouldDisplayBrowserAction(
905 const Extension
* extension
) {
906 // Only display incognito-enabled extensions while in incognito mode.
907 return !profile_
->IsOffTheRecord() ||
908 extensions::util::IsIncognitoEnabled(extension
->id(), profile_
);
911 bool BrowserActionsContainer::ShowPopup(
912 BrowserActionButton
* button
,
913 ExtensionPopup::ShowAction show_action
,
915 const Extension
* extension
= button
->extension();
917 if (model_
->ExecuteBrowserAction(
918 extension
, browser_
, &popup_url
, should_grant
) !=
919 extensions::ExtensionToolbarModel::ACTION_SHOW_POPUP
) {
923 // If we're showing the same popup, just hide it and return.
924 bool same_showing
= popup_
&& button
== popup_button_
;
926 // Always hide the current popup, even if it's not the same.
927 // Only one popup should be visible at a time.
933 // We can get the execute event for browser actions that are not visible,
934 // since buttons can be activated from the overflow menu (chevron). In that
935 // case we show the popup as originating from the chevron.
936 View
* reference_view
= button
->parent()->visible() ? button
: chevron_
;
937 popup_
= ExtensionPopup::ShowPopup(popup_url
, browser_
, reference_view
,
938 views::BubbleBorder::TOP_RIGHT
,
940 popup_
->GetWidget()->AddObserver(this);
941 popup_button_
= button
;
943 // Only set button as pushed if it was triggered by a user click.
945 popup_button_
->SetButtonPushed();