Temporarily re-enabling SizeAfterPrefChange test with traces (this time for Linux...
[chromium-blink-merge.git] / chrome / browser / ui / views / toolbar / browser_actions_container.cc
blobbd821453967a43803e945dc6506234d9a9ec1877
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;
50 namespace {
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;
59 } // namespace
61 // static
62 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
64 ////////////////////////////////////////////////////////////////////////////////
65 // BrowserActionsContainer
67 BrowserActionsContainer::BrowserActionsContainer(Browser* browser,
68 View* owner_view)
69 : profile_(browser->profile()),
70 browser_(browser),
71 owner_view_(owner_view),
72 popup_(NULL),
73 popup_button_(NULL),
74 model_(NULL),
75 container_width_(0),
76 chevron_(NULL),
77 overflow_menu_(NULL),
78 suppress_chevron_(false),
79 resize_amount_(0),
80 animation_target_size_(0),
81 drop_indicator_position_(-1),
82 task_factory_(this),
83 show_menu_task_factory_(this) {
84 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
86 model_ = extensions::ExtensionToolbarModel::Get(browser->profile());
87 if (model_)
88 model_->AddObserver(this);
90 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
91 browser->profile(),
92 owner_view->GetFocusManager(),
93 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
94 this));
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,
111 observers_,
112 OnBrowserActionsContainerDestroyed());
114 if (overflow_menu_)
115 overflow_menu_->set_observer(NULL);
116 if (model_)
117 model_->RemoveObserver(this);
118 StopShowFolderDropMenuTimer();
119 if (popup_)
120 popup_->GetWidget()->RemoveObserver(this);
121 HidePopup();
122 DeleteBrowserActionViews();
125 void BrowserActionsContainer::Init() {
126 LoadImages();
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.
130 if (model_ &&
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())
141 SetContainerWidth();
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)
149 return *i;
151 return NULL;
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());
161 if (!model_)
162 return;
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()))
168 continue;
170 BrowserActionView* view = new BrowserActionView(i->get(), browser_, this);
171 browser_action_views_.push_back(view);
172 AddChildView(view);
176 void BrowserActionsContainer::DeleteBrowserActionViews() {
177 HidePopup();
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())
185 ++visible_actions;
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
196 // instance.
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()) {
234 SetVisible(false);
235 return;
238 SetVisible(true);
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());
246 max_x -=
247 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
248 chevron_->SetBounds(
249 width() - ToolbarView::kStandardSpacing - chevron_size.width(),
251 chevron_size.width(),
252 chevron_size.height());
253 } else {
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);
265 } else {
266 view->SetVisible(false);
271 bool BrowserActionsContainer::GetDropFormats(
272 int* formats,
273 std::set<OSExchangeData::CustomFormat>* custom_formats) {
274 custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
276 return true;
279 bool BrowserActionsContainer::AreDropTypesRequired() {
280 return true;
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:
316 // Real icon area:
317 // 0 a * b c
318 // | | | |
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":
325 // 0 a * b c
326 // | | | |
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
331 // plus padding".
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
344 // chosen icon.
345 SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) -
346 (kItemSpacing / 2));
348 return ui::DragDropTypes::DRAG_MOVE;
351 void BrowserActionsContainer::OnDragExited() {
352 StopShowFolderDropMenuTimer();
353 drop_indicator_position_ = -1;
354 SchedulePaint();
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(),
365 data.id());
366 DCHECK(model_);
368 size_t i = 0;
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.
376 break;
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())
386 --i;
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) {
418 DCHECK(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(),
427 data);
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);
433 break;
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();
454 return;
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);
461 container_width_ =
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;
478 resize_amount_ = 0;
479 suppress_chevron_ = false;
480 OnBrowserActionVisibilityChanged();
482 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
483 observers_,
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);
496 popup_ = NULL;
497 // |popup_button_| is NULL if the extension has been removed.
498 if (popup_button_) {
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();
512 if (!active_tab)
513 return -1;
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();
533 if (!web_contents)
534 return NULL;
535 return extensions::TabHelper::FromWebContents(web_contents)->
536 active_tab_permission_granter();
539 void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
540 size_t new_index) {
541 ExtensionService* service =
542 extensions::ExtensionSystem::Get(profile_)->extension_service();
543 if (service) {
544 const Extension* extension = service->GetExtensionById(extension_id, false);
545 model_->MoveBrowserAction(extension, new_index);
546 SchedulePaint();
550 bool BrowserActionsContainer::ShowPopup(const extensions::Extension* extension,
551 bool should_grant) {
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
555 // fixed.
556 if (popup_ ||
557 !browser_->window()->IsActive() ||
558 !browser_->window()->IsToolbarVisible()) {
559 return false;
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);
568 return false;
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.
574 if (popup_) {
575 popup_->GetWidget()->RemoveObserver(this);
576 popup_->GetWidget()->Close();
577 popup_ = NULL;
579 if (popup_button_) {
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());
594 Layout();
595 SchedulePaint();
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),
614 kDropIndicatorWidth,
615 height());
617 // Color of the drop indicator.
618 static const SkColor kDropIndicatorColor = SK_ColorBLACK;
619 canvas->FillRect(indicator_bounds, kDropIndicatorColor);
623 void BrowserActionsContainer::OnThemeChanged() {
624 LoadImages();
627 void BrowserActionsContainer::ViewHierarchyChanged(
628 const ViewHierarchyChangedDetails& details) {
629 // No extensions (e.g., incognito).
630 if (!model_)
631 return;
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();
642 // static
643 int BrowserActionsContainer::IconWidth(bool include_padding) {
644 static bool initialized = false;
645 static int icon_width = 0;
646 if (!initialized) {
647 initialized = true;
648 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
649 IDR_BROWSER_ACTION)->width();
651 return icon_width + (include_padding ? kItemSpacing : 0);
654 // static
655 int BrowserActionsContainer::IconHeight() {
656 static bool initialized = false;
657 static int icon_height = 0;
658 if (!initialized) {
659 initialized = true;
660 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
661 IDR_BROWSER_ACTION)->height();
663 return icon_height;
666 void BrowserActionsContainer::BrowserActionAdded(const Extension* extension,
667 int index) {
668 #if defined(DEBUG)
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 "
672 "exists.";
674 #endif
675 CloseOverflowMenu();
677 if (!ShouldDisplayBrowserAction(extension)) {
678 VLOG(4) << "Should not display: " << extension->name().c_str();
679 return;
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";
695 return;
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);
706 } else {
707 VLOG(4) << "Not at max";
708 // Just redraw the (possibly modified) visible icon set.
709 OnBrowserActionVisibilityChanged();
713 void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) {
714 CloseOverflowMenu();
716 if (popup_ && popup_->host()->extension() == extension)
717 HidePopup();
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) {
723 delete *i;
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))
730 return;
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();
738 } else {
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());
745 return;
750 void BrowserActionsContainer::BrowserActionMoved(const Extension* extension,
751 int index) {
752 if (!ShouldDisplayBrowserAction(extension))
753 return;
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();
762 Layout();
763 SchedulePaint();
766 bool BrowserActionsContainer::BrowserActionShowPopup(
767 const extensions::Extension* extension) {
768 return ShowPopup(extension, false);
771 void BrowserActionsContainer::VisibleCountChanged() {
772 SetContainerWidth();
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() {
807 if (overflow_menu_)
808 overflow_menu_->CancelMenu();
811 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
812 show_menu_task_factory_.InvalidateWeakPtrs();
815 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
816 base::MessageLoop::current()->PostDelayedTask(
817 FROM_HERE,
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;
835 SchedulePaint();
839 int BrowserActionsContainer::IconCountToWidth(int icons,
840 bool display_chevron) const {
841 if (icons < 0)
842 icons = browser_action_views_.size();
843 if ((icons == 0) && !display_chevron)
844 return ToolbarView::kStandardSpacing;
845 int icons_size =
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;
886 } else {
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();
898 } else {
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,
914 bool should_grant) {
915 const Extension* extension = button->extension();
916 GURL popup_url;
917 if (model_->ExecuteBrowserAction(
918 extension, browser_, &popup_url, should_grant) !=
919 extensions::ExtensionToolbarModel::ACTION_SHOW_POPUP) {
920 return false;
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.
928 HidePopup();
930 if (same_showing)
931 return false;
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,
939 show_action);
940 popup_->GetWidget()->AddObserver(this);
941 popup_button_ = button;
943 // Only set button as pushed if it was triggered by a user click.
944 if (should_grant)
945 popup_button_->SetButtonPushed();
946 return true;