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/pref_names.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/browser/pref_names.h"
28 #include "extensions/browser/runtime_data.h"
29 #include "grit/generated_resources.h"
30 #include "grit/theme_resources.h"
31 #include "grit/ui_resources.h"
32 #include "ui/base/accessibility/accessible_view_state.h"
33 #include "ui/base/dragdrop/drag_utils.h"
34 #include "ui/base/l10n/l10n_util.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/views/controls/resize_area.h"
40 #include "ui/views/metrics.h"
41 #include "ui/views/widget/widget.h"
43 using extensions::Extension;
47 // Horizontal spacing between most items in the container, as well as after the
48 // last item or chevron (if visible).
49 const int kItemSpacing = ToolbarView::kStandardSpacing;
51 // Horizontal spacing before the chevron (if visible).
52 const int kChevronSpacing = kItemSpacing - 2;
57 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
59 ////////////////////////////////////////////////////////////////////////////////
60 // BrowserActionsContainer
62 BrowserActionsContainer::BrowserActionsContainer(Browser* browser,
64 : profile_(browser->profile()),
66 owner_view_(owner_view),
73 suppress_chevron_(false),
75 animation_target_size_(0),
76 drop_indicator_position_(-1),
78 show_menu_task_factory_(this) {
79 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
81 model_ = ExtensionToolbarModel::Get(browser->profile());
83 model_->AddObserver(this);
85 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
87 owner_view->GetFocusManager(),
88 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
91 resize_animation_.reset(new gfx::SlideAnimation(this));
92 resize_area_ = new views::ResizeArea(this);
93 AddChildView(resize_area_);
95 chevron_ = new views::MenuButton(NULL, base::string16(), this, false);
96 chevron_->SetBorder(views::Border::NullBorder());
97 chevron_->EnableCanvasFlippingForRTLUI(true);
98 chevron_->SetAccessibleName(
99 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
100 chevron_->SetVisible(false);
101 AddChildView(chevron_);
104 BrowserActionsContainer::~BrowserActionsContainer() {
106 overflow_menu_->set_observer(NULL);
108 model_->RemoveObserver(this);
109 StopShowFolderDropMenuTimer();
111 popup_->GetWidget()->RemoveObserver(this);
113 DeleteBrowserActionViews();
116 void BrowserActionsContainer::Init() {
119 // We wait to set the container width until now so that the chevron images
120 // will be loaded. The width calculation needs to know the chevron size.
122 !profile_->GetPrefs()->HasPrefPath(
123 extensions::pref_names::kToolbarSize)) {
124 // Migration code to the new VisibleIconCount pref.
125 // TODO(mpcomplete): remove this after users are upgraded to 5.0.
126 int predefined_width = profile_->GetPrefs()->GetInteger(
127 extensions::pref_names::kBrowserActionContainerWidth);
128 if (predefined_width != 0)
129 model_->SetVisibleIconCount(WidthToIconCount(predefined_width));
131 if (model_ && model_->extensions_initialized())
135 BrowserActionView* BrowserActionsContainer::GetBrowserActionView(
136 ExtensionAction* action) {
137 for (BrowserActionViews::iterator i(browser_action_views_.begin());
138 i != browser_action_views_.end(); ++i) {
139 if ((*i)->button()->browser_action() == action)
145 void BrowserActionsContainer::RefreshBrowserActionViews() {
146 for (size_t i = 0; i < browser_action_views_.size(); ++i)
147 browser_action_views_[i]->button()->UpdateState();
150 void BrowserActionsContainer::CreateBrowserActionViews() {
151 DCHECK(browser_action_views_.empty());
155 const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
156 for (extensions::ExtensionList::const_iterator i(toolbar_items.begin());
157 i != toolbar_items.end(); ++i) {
158 if (!ShouldDisplayBrowserAction(i->get()))
161 BrowserActionView* view = new BrowserActionView(i->get(), browser_, this);
162 browser_action_views_.push_back(view);
167 void BrowserActionsContainer::DeleteBrowserActionViews() {
169 STLDeleteElements(&browser_action_views_);
172 size_t BrowserActionsContainer::VisibleBrowserActions() const {
173 size_t visible_actions = 0;
174 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
175 if (browser_action_views_[i]->visible())
178 return visible_actions;
181 gfx::Size BrowserActionsContainer::GetPreferredSize() {
182 if (browser_action_views_.empty())
183 return gfx::Size(ToolbarView::kStandardSpacing, 0);
185 // We calculate the size of the view by taking the current width and
186 // subtracting resize_amount_ (the latter represents how far the user is
187 // resizing the view or, if animating the snapping, how far to animate it).
188 // But we also clamp it to a minimum size and the maximum size, so that the
189 // container can never shrink too far or take up more space than it needs. In
190 // other words: ContainerMinSize() < width() - resize < ClampTo(MAX).
191 int clamped_width = std::min(
192 std::max(ContainerMinSize(), container_width_ - resize_amount_),
193 IconCountToWidth(-1, false));
194 return gfx::Size(clamped_width, 0);
197 void BrowserActionsContainer::Layout() {
198 if (browser_action_views_.empty()) {
204 resize_area_->SetBounds(0, ToolbarView::kVertSpacing, kItemSpacing,
207 // If the icons don't all fit, show the chevron (unless suppressed).
208 int max_x = GetPreferredSize().width();
209 if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_) {
210 chevron_->SetVisible(true);
211 gfx::Size chevron_size(chevron_->GetPreferredSize());
213 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
215 width() - ToolbarView::kStandardSpacing - chevron_size.width(),
216 ToolbarView::kVertSpacing, chevron_size.width(), chevron_size.height());
218 chevron_->SetVisible(false);
221 // Now draw the icons for the browser actions in the available space.
222 int icon_width = IconWidth(false);
223 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
224 BrowserActionView* view = browser_action_views_[i];
225 int x = ToolbarView::kStandardSpacing + (i * IconWidth(true));
226 if (x + icon_width <= max_x) {
227 view->SetBounds(x, 0, icon_width, height());
228 view->SetVisible(true);
230 view->SetVisible(false);
235 bool BrowserActionsContainer::GetDropFormats(
237 std::set<OSExchangeData::CustomFormat>* custom_formats) {
238 custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
243 bool BrowserActionsContainer::AreDropTypesRequired() {
247 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
248 BrowserActionDragData drop_data;
249 return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false;
252 void BrowserActionsContainer::OnDragEntered(
253 const ui::DropTargetEvent& event) {
256 int BrowserActionsContainer::OnDragUpdated(
257 const ui::DropTargetEvent& event) {
258 // First check if we are above the chevron (overflow) menu.
259 if (GetEventHandlerForPoint(event.location()) == chevron_) {
260 if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_)
261 StartShowFolderDropMenuTimer();
262 return ui::DragDropTypes::DRAG_MOVE;
264 StopShowFolderDropMenuTimer();
266 // Figure out where to display the indicator. This is a complex calculation:
268 // First, we figure out how much space is to the left of the icon area, so we
269 // can calculate the true offset into the icon area.
270 int width_before_icons = ToolbarView::kStandardSpacing +
271 (base::i18n::IsRTL() ?
272 (chevron_->GetPreferredSize().width() + kChevronSpacing) : 0);
273 int offset_into_icon_area = event.x() - width_before_icons;
275 // Next, we determine which icon to place the indicator in front of. We want
276 // to place the indicator in front of icon n when the cursor is between the
277 // midpoints of icons (n - 1) and n. To do this we take the offset into the
278 // icon area and transform it as follows:
283 // |[IC|ON] [IC|ON] [IC|ON]
284 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
285 // Here the "*" represents the offset into the icon area, and since it's
286 // between a and b, we want to return "1".
288 // Transformed "icon area":
291 // |[ICON] |[ICON] |[ICON] |
292 // If we shift both our offset and our divider points later by half an icon
293 // plus one spacing unit, then it becomes very easy to calculate how many
294 // divider points we've passed, because they're the multiples of "one icon
296 int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) +
297 kItemSpacing) / IconWidth(true);
299 // Because the user can drag outside the container bounds, we need to clamp to
300 // the valid range. Note that the maximum allowable value is (num icons), not
301 // (num icons - 1), because we represent the indicator being past the last
302 // icon as being "before the (last + 1) icon".
303 int before_icon = std::min(std::max(before_icon_unclamped, 0),
304 static_cast<int>(VisibleBrowserActions()));
306 // Now we convert back to a pixel offset into the container. We want to place
307 // the center of the drop indicator at the midpoint of the space before our
309 SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) -
312 return ui::DragDropTypes::DRAG_MOVE;
315 void BrowserActionsContainer::OnDragExited() {
316 StopShowFolderDropMenuTimer();
317 drop_indicator_position_ = -1;
321 int BrowserActionsContainer::OnPerformDrop(
322 const ui::DropTargetEvent& event) {
323 BrowserActionDragData data;
324 if (!data.Read(event.data()))
325 return ui::DragDropTypes::DRAG_NONE;
327 // Make sure we have the same view as we started with.
328 DCHECK_EQ(browser_action_views_[data.index()]->button()->extension()->id(),
333 for (; i < browser_action_views_.size(); ++i) {
334 int view_x = browser_action_views_[i]->GetMirroredBounds().x();
335 if (!browser_action_views_[i]->visible() ||
336 (base::i18n::IsRTL() ? (view_x < drop_indicator_position_) :
337 (view_x >= drop_indicator_position_))) {
338 // We have reached the end of the visible icons or found one that has a
339 // higher x position than the drop point.
344 // |i| now points to the item to the right of the drop indicator*, which is
345 // correct when dragging an icon to the left. When dragging to the right,
346 // however, we want the icon being dragged to get the index of the item to
347 // the left of the drop indicator, so we subtract one.
348 // * Well, it can also point to the end, but not when dragging to the left. :)
349 if (i > data.index())
352 if (profile_->IsOffTheRecord())
353 i = model_->IncognitoIndexToOriginal(i);
355 model_->MoveBrowserAction(
356 browser_action_views_[data.index()]->button()->extension(), i);
358 OnDragExited(); // Perform clean up after dragging.
359 return ui::DragDropTypes::DRAG_MOVE;
362 void BrowserActionsContainer::GetAccessibleState(
363 ui::AccessibleViewState* state) {
364 state->role = ui::AccessibilityTypes::ROLE_GROUPING;
365 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
368 void BrowserActionsContainer::OnMenuButtonClicked(views::View* source,
369 const gfx::Point& point) {
370 if (source == chevron_) {
371 overflow_menu_ = new BrowserActionOverflowMenuController(
372 this, browser_, chevron_, browser_action_views_,
373 VisibleBrowserActions());
374 overflow_menu_->set_observer(this);
375 overflow_menu_->RunMenu(GetWidget(), false);
379 void BrowserActionsContainer::WriteDragDataForView(View* sender,
380 const gfx::Point& press_pt,
381 OSExchangeData* data) {
384 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
385 BrowserActionButton* button = browser_action_views_[i]->button();
386 if (button == sender) {
387 // Set the dragging image for the icon.
388 gfx::ImageSkia badge(browser_action_views_[i]->GetIconWithBadge());
389 drag_utils::SetDragImageOnDataObject(badge, button->size(),
390 press_pt.OffsetFromOrigin(),
393 // Fill in the remaining info.
394 BrowserActionDragData drag_data(
395 browser_action_views_[i]->button()->extension()->id(), i);
396 drag_data.Write(profile_, data);
402 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
403 const gfx::Point& p) {
404 return ui::DragDropTypes::DRAG_MOVE;
407 bool BrowserActionsContainer::CanStartDragForView(View* sender,
408 const gfx::Point& press_pt,
409 const gfx::Point& p) {
413 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
414 if (!done_resizing) {
415 resize_amount_ = resize_amount;
416 OnBrowserActionVisibilityChanged();
420 // Up until now we've only been modifying the resize_amount, but now it is
421 // time to set the container size to the size we have resized to, and then
422 // animate to the nearest icon count size if necessary (which may be 0).
423 int max_width = IconCountToWidth(-1, false);
425 std::min(std::max(0, container_width_ - resize_amount), max_width);
426 SaveDesiredSizeAndAnimate(gfx::Tween::EASE_OUT,
427 WidthToIconCount(container_width_));
430 void BrowserActionsContainer::AnimationProgressed(
431 const gfx::Animation* animation) {
432 DCHECK_EQ(resize_animation_.get(), animation);
433 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
434 (container_width_ - animation_target_size_));
435 OnBrowserActionVisibilityChanged();
438 void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
439 container_width_ = animation_target_size_;
440 animation_target_size_ = 0;
442 OnBrowserActionVisibilityChanged();
443 suppress_chevron_ = false;
446 void BrowserActionsContainer::NotifyMenuDeleted(
447 BrowserActionOverflowMenuController* controller) {
448 DCHECK_EQ(overflow_menu_, controller);
449 overflow_menu_ = NULL;
452 void BrowserActionsContainer::OnWidgetDestroying(views::Widget* widget) {
453 DCHECK_EQ(popup_->GetWidget(), widget);
454 popup_->GetWidget()->RemoveObserver(this);
456 // |popup_button_| is NULL if the extension has been removed.
458 popup_button_->SetButtonNotPushed();
459 popup_button_ = NULL;
463 void BrowserActionsContainer::InspectPopup(ExtensionAction* action) {
464 BrowserActionView* view = GetBrowserActionView(action);
465 ShowPopup(view->button(), ExtensionPopup::SHOW_AND_INSPECT, true);
468 int BrowserActionsContainer::GetCurrentTabId() const {
469 content::WebContents* active_tab =
470 browser_->tab_strip_model()->GetActiveWebContents();
474 return SessionTabHelper::FromWebContents(active_tab)->session_id().id();
477 void BrowserActionsContainer::OnBrowserActionExecuted(
478 BrowserActionButton* button) {
479 ShowPopup(button, ExtensionPopup::SHOW, true);
482 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
483 SetVisible(!browser_action_views_.empty());
484 owner_view_->Layout();
485 owner_view_->SchedulePaint();
488 gfx::Point BrowserActionsContainer::GetViewContentOffset() const {
489 return gfx::Point(0, ToolbarView::kVertSpacing);
492 extensions::ActiveTabPermissionGranter*
493 BrowserActionsContainer::GetActiveTabPermissionGranter() {
494 content::WebContents* web_contents =
495 browser_->tab_strip_model()->GetActiveWebContents();
498 return extensions::TabHelper::FromWebContents(web_contents)->
499 active_tab_permission_granter();
502 void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
504 ExtensionService* service =
505 extensions::ExtensionSystem::Get(profile_)->extension_service();
507 const Extension* extension = service->GetExtensionById(extension_id, false);
508 model_->MoveBrowserAction(extension, new_index);
513 void BrowserActionsContainer::HidePopup() {
514 // Remove this as an observer and clear |popup_| and |popup_button_| here,
515 // since we might change them before OnWidgetDestroying() gets called.
517 popup_->GetWidget()->RemoveObserver(this);
518 popup_->GetWidget()->Close();
522 popup_button_->SetButtonNotPushed();
523 popup_button_ = NULL;
527 void BrowserActionsContainer::TestExecuteBrowserAction(int index) {
528 BrowserActionButton* button = browser_action_views_[index]->button();
529 OnBrowserActionExecuted(button);
532 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
533 model_->SetVisibleIconCount(icons);
534 chevron_->SetVisible(icons < browser_action_views_.size());
535 container_width_ = IconCountToWidth(icons, chevron_->visible());
540 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
541 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
542 // dragging (like we do for tab dragging).
543 if (drop_indicator_position_ > -1) {
544 // The two-pixel width drop indicator.
545 static const int kDropIndicatorWidth = 2;
546 gfx::Rect indicator_bounds(
547 drop_indicator_position_ - (kDropIndicatorWidth / 2),
548 ToolbarView::kVertSpacing, kDropIndicatorWidth, IconHeight());
550 // Color of the drop indicator.
551 static const SkColor kDropIndicatorColor = SK_ColorBLACK;
552 canvas->FillRect(indicator_bounds, kDropIndicatorColor);
556 void BrowserActionsContainer::OnThemeChanged() {
560 void BrowserActionsContainer::ViewHierarchyChanged(
561 const ViewHierarchyChangedDetails& details) {
562 // No extensions (e.g., incognito).
566 if (details.is_add && details.child == this) {
567 // Initial toolbar button creation and placement in the widget hierarchy.
568 // We do this here instead of in the constructor because AddBrowserAction
569 // calls Layout on the Toolbar, which needs this object to be constructed
570 // before its Layout function is called.
571 CreateBrowserActionViews();
576 int BrowserActionsContainer::IconWidth(bool include_padding) {
577 static bool initialized = false;
578 static int icon_width = 0;
581 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
582 IDR_BROWSER_ACTION)->width();
584 return icon_width + (include_padding ? kItemSpacing : 0);
588 int BrowserActionsContainer::IconHeight() {
589 static bool initialized = false;
590 static int icon_height = 0;
593 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
594 IDR_BROWSER_ACTION)->height();
599 void BrowserActionsContainer::BrowserActionAdded(const Extension* extension,
602 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
603 DCHECK(browser_action_views_[i]->button()->extension() != extension) <<
604 "Asked to add a browser action view for an extension that already "
610 if (!ShouldDisplayBrowserAction(extension))
613 size_t visible_actions = VisibleBrowserActions();
615 // Add the new browser action to the vector and the view hierarchy.
616 if (profile_->IsOffTheRecord())
617 index = model_->OriginalIndexToIncognito(index);
618 BrowserActionView* view = new BrowserActionView(extension, browser_, this);
619 browser_action_views_.insert(browser_action_views_.begin() + index, view);
620 AddChildViewAt(view, index);
622 // If we are still initializing the container, don't bother animating.
623 if (!model_->extensions_initialized())
626 // Enlarge the container if it was already at maximum size and we're not in
627 // the middle of upgrading.
628 if ((model_->GetVisibleIconCount() < 0) &&
629 !extensions::ExtensionSystem::Get(profile_)->runtime_data()->
630 IsBeingUpgraded(extension)) {
631 suppress_chevron_ = true;
632 SaveDesiredSizeAndAnimate(gfx::Tween::LINEAR, visible_actions + 1);
634 // Just redraw the (possibly modified) visible icon set.
635 OnBrowserActionVisibilityChanged();
639 void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) {
642 if (popup_ && popup_->host()->extension() == extension)
645 size_t visible_actions = VisibleBrowserActions();
646 for (BrowserActionViews::iterator i(browser_action_views_.begin());
647 i != browser_action_views_.end(); ++i) {
648 if ((*i)->button()->extension() == extension) {
650 browser_action_views_.erase(i);
652 // If the extension is being upgraded we don't want the bar to shrink
653 // because the icon is just going to get re-added to the same location.
654 if (extensions::ExtensionSystem::Get(profile_)->runtime_data()->
655 IsBeingUpgraded(extension))
658 if (browser_action_views_.size() > visible_actions) {
659 // If we have more icons than we can show, then we must not be changing
660 // the container size (since we either removed an icon from the main
661 // area and one from the overflow list will have shifted in, or we
662 // removed an entry directly from the overflow list).
663 OnBrowserActionVisibilityChanged();
665 // Either we went from overflow to no-overflow, or we shrunk the no-
666 // overflow container by 1. Either way the size changed, so animate.
667 chevron_->SetVisible(false);
668 SaveDesiredSizeAndAnimate(gfx::Tween::EASE_OUT,
669 browser_action_views_.size());
676 void BrowserActionsContainer::BrowserActionMoved(const Extension* extension,
678 if (!ShouldDisplayBrowserAction(extension))
681 if (profile_->IsOffTheRecord())
682 index = model_->OriginalIndexToIncognito(index);
684 DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
686 DeleteBrowserActionViews();
687 CreateBrowserActionViews();
692 bool BrowserActionsContainer::BrowserActionShowPopup(
693 const extensions::Extension* extension) {
694 // Do not override other popups and only show in active window. The window
695 // must also have a toolbar, otherwise it should not be showing popups.
696 // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
699 !browser_->window()->IsActive() ||
700 !browser_->window()->IsToolbarVisible()) {
704 for (BrowserActionViews::iterator it = browser_action_views_.begin();
705 it != browser_action_views_.end(); ++it) {
706 BrowserActionButton* button = (*it)->button();
707 if (button && button->extension() == extension)
708 return ShowPopup(button, ExtensionPopup::SHOW, false);
713 void BrowserActionsContainer::VisibleCountChanged() {
717 void BrowserActionsContainer::LoadImages() {
718 ui::ThemeProvider* tp = GetThemeProvider();
719 chevron_->SetIcon(*tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
720 chevron_->SetHoverIcon(*tp->GetImageSkiaNamed(
721 IDR_BROWSER_ACTIONS_OVERFLOW_H));
722 chevron_->SetPushedIcon(*tp->GetImageSkiaNamed(
723 IDR_BROWSER_ACTIONS_OVERFLOW_P));
726 void BrowserActionsContainer::SetContainerWidth() {
727 int visible_actions = model_->GetVisibleIconCount();
728 if (visible_actions < 0) // All icons should be visible.
729 visible_actions = model_->toolbar_items().size();
730 chevron_->SetVisible(
731 static_cast<size_t>(visible_actions) < model_->toolbar_items().size());
732 container_width_ = IconCountToWidth(visible_actions, chevron_->visible());
735 void BrowserActionsContainer::CloseOverflowMenu() {
737 overflow_menu_->CancelMenu();
740 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
741 show_menu_task_factory_.InvalidateWeakPtrs();
744 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
745 base::MessageLoop::current()->PostDelayedTask(
747 base::Bind(&BrowserActionsContainer::ShowDropFolder,
748 show_menu_task_factory_.GetWeakPtr()),
749 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
752 void BrowserActionsContainer::ShowDropFolder() {
753 DCHECK(!overflow_menu_);
754 SetDropIndicator(-1);
755 overflow_menu_ = new BrowserActionOverflowMenuController(
756 this, browser_, chevron_, browser_action_views_, VisibleBrowserActions());
757 overflow_menu_->set_observer(this);
758 overflow_menu_->RunMenu(GetWidget(), true);
761 void BrowserActionsContainer::SetDropIndicator(int x_pos) {
762 if (drop_indicator_position_ != x_pos) {
763 drop_indicator_position_ = x_pos;
768 int BrowserActionsContainer::IconCountToWidth(int icons,
769 bool display_chevron) const {
771 icons = browser_action_views_.size();
772 if ((icons == 0) && !display_chevron)
773 return ToolbarView::kStandardSpacing;
775 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
776 int chevron_size = display_chevron ?
777 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
778 return ToolbarView::kStandardSpacing + icons_size + chevron_size +
779 ToolbarView::kStandardSpacing;
782 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
783 // Check for widths large enough to show the entire icon set.
784 if (pixels >= IconCountToWidth(-1, false))
785 return browser_action_views_.size();
787 // We need to reserve space for the resize area, chevron, and the spacing on
788 // either side of the chevron.
789 int available_space = pixels - ToolbarView::kStandardSpacing -
790 chevron_->GetPreferredSize().width() - kChevronSpacing -
791 ToolbarView::kStandardSpacing;
792 // Now we add an extra between-item padding value so the space can be divided
793 // evenly by (size of icon with padding).
794 return static_cast<size_t>(
795 std::max(0, available_space + kItemSpacing) / IconWidth(true));
798 int BrowserActionsContainer::ContainerMinSize() const {
799 return ToolbarView::kStandardSpacing + kChevronSpacing +
800 chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing;
803 void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
804 gfx::Tween::Type tween_type,
805 size_t num_visible_icons) {
806 // Save off the desired number of visible icons. We do this now instead of at
807 // the end of the animation so that even if the browser is shut down while
808 // animating, the right value will be restored on next run.
809 // NOTE: Don't save the icon count in incognito because there may be fewer
810 // icons in that mode. The result is that the container in a normal window is
811 // always at least as wide as in an incognito window.
812 if (!profile_->IsOffTheRecord())
813 model_->SetVisibleIconCount(num_visible_icons);
815 int target_size = IconCountToWidth(num_visible_icons,
816 num_visible_icons < browser_action_views_.size());
817 if (!disable_animations_during_testing_) {
818 // Animate! We have to set the animation_target_size_ after calling Reset(),
819 // because that could end up calling AnimationEnded which clears the value.
820 resize_animation_->Reset();
821 resize_animation_->SetTweenType(tween_type);
822 animation_target_size_ = target_size;
823 resize_animation_->Show();
825 animation_target_size_ = target_size;
826 AnimationEnded(resize_animation_.get());
830 bool BrowserActionsContainer::ShouldDisplayBrowserAction(
831 const Extension* extension) {
832 // Only display incognito-enabled extensions while in incognito mode.
833 return !profile_->IsOffTheRecord() ||
834 extensions::util::IsIncognitoEnabled(extension->id(), profile_);
837 bool BrowserActionsContainer::ShowPopup(
838 BrowserActionButton* button,
839 ExtensionPopup::ShowAction show_action,
841 const Extension* extension = button->extension();
843 if (model_->ExecuteBrowserAction(
844 extension, browser_, &popup_url, should_grant) !=
845 ExtensionToolbarModel::ACTION_SHOW_POPUP) {
849 // If we're showing the same popup, just hide it and return.
850 bool same_showing = popup_ && button == popup_button_;
852 // Always hide the current popup, even if it's not the same.
853 // Only one popup should be visible at a time.
859 // We can get the execute event for browser actions that are not visible,
860 // since buttons can be activated from the overflow menu (chevron). In that
861 // case we show the popup as originating from the chevron.
862 View* reference_view = button->parent()->visible() ? button : chevron_;
863 popup_ = ExtensionPopup::ShowPopup(popup_url, browser_, reference_view,
864 views::BubbleBorder::TOP_RIGHT,
866 popup_->GetWidget()->AddObserver(this);
867 popup_button_ = button;
869 // Only set button as pushed if it was triggered by a user click.
871 popup_button_->SetButtonPushed();