1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
7 #include "base/compiler_specific.h"
8 #include "base/stl_util.h"
9 #include "chrome/browser/extensions/extension_util.h"
10 #include "chrome/browser/extensions/extension_view_host.h"
11 #include "chrome/browser/extensions/tab_helper.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/browser/ui/view_ids.h"
17 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
18 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
19 #include "chrome/browser/ui/views/extensions/extension_popup.h"
20 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
21 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
22 #include "chrome/common/extensions/command.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/browser/runtime_data.h"
26 #include "extensions/common/feature_switch.h"
27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h"
29 #include "grit/ui_resources.h"
30 #include "third_party/skia/include/core/SkColor.h"
31 #include "ui/accessibility/ax_view_state.h"
32 #include "ui/base/dragdrop/drag_utils.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/nine_image_painter_factory.h"
35 #include "ui/base/resource/resource_bundle.h"
36 #include "ui/base/theme_provider.h"
37 #include "ui/gfx/animation/slide_animation.h"
38 #include "ui/gfx/canvas.h"
39 #include "ui/gfx/geometry/rect.h"
40 #include "ui/views/controls/button/label_button_border.h"
41 #include "ui/views/controls/button/menu_button.h"
42 #include "ui/views/controls/resize_area.h"
43 #include "ui/views/metrics.h"
44 #include "ui/views/painter.h"
45 #include "ui/views/widget/widget.h"
47 using extensions::Extension;
51 // Horizontal spacing between most items in the container, as well as after the
52 // last item or chevron (if visible).
53 const int kItemSpacing = ToolbarView::kStandardSpacing;
55 // Horizontal spacing before the chevron (if visible).
56 const int kChevronSpacing = kItemSpacing - 2;
58 // The maximum number of icons to show per row when in overflow mode (showing
59 // icons in the application menu).
60 // TODO(devlin): Compute the right number of icons to show, depending on the
63 const int kIconsPerMenuRow = 8; // The menu on Linux is wider.
65 const int kIconsPerMenuRow = 7;
68 // A version of MenuButton with almost empty insets to fit properly on the
70 class ChevronMenuButton : public views::MenuButton {
72 ChevronMenuButton(views::ButtonListener* listener,
73 const base::string16& text,
74 views::MenuButtonListener* menu_button_listener,
75 bool show_menu_marker)
76 : views::MenuButton(listener,
82 virtual ~ChevronMenuButton() {}
84 virtual scoped_ptr<views::LabelButtonBorder> CreateDefaultBorder() const
86 // The chevron resource was designed to not have any insets.
87 scoped_ptr<views::LabelButtonBorder> border =
88 views::MenuButton::CreateDefaultBorder();
89 border->set_insets(gfx::Insets());
94 DISALLOW_COPY_AND_ASSIGN(ChevronMenuButton);
99 ////////////////////////////////////////////////////////////////////////////////
100 // BrowserActionsContainer::DropPosition
102 struct BrowserActionsContainer::DropPosition {
103 DropPosition(size_t row, size_t icon_in_row);
105 // The (0-indexed) row into which the action will be dropped.
108 // The (0-indexed) icon in the row before the action will be dropped.
112 BrowserActionsContainer::DropPosition::DropPosition(
113 size_t row, size_t icon_in_row)
114 : row(row), icon_in_row(icon_in_row) {
117 ////////////////////////////////////////////////////////////////////////////////
118 // BrowserActionsContainer
121 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
123 BrowserActionsContainer::BrowserActionsContainer(
126 BrowserActionsContainer* main_container)
127 : profile_(browser->profile()),
129 owner_view_(owner_view),
130 main_container_(main_container),
136 overflow_menu_(NULL),
137 suppress_chevron_(false),
139 animation_target_size_(0),
141 show_menu_task_factory_(this) {
142 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
144 model_ = extensions::ExtensionToolbarModel::Get(browser->profile());
146 model_->AddObserver(this);
148 bool overflow_experiment =
149 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
150 DCHECK(!in_overflow_mode() || overflow_experiment);
152 if (!in_overflow_mode()) {
153 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
155 owner_view->GetFocusManager(),
156 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
159 resize_animation_.reset(new gfx::SlideAnimation(this));
160 resize_area_ = new views::ResizeArea(this);
161 AddChildView(resize_area_);
163 // 'Main' mode doesn't need a chevron overflow when overflow is shown inside
165 if (!overflow_experiment) {
166 chevron_ = new ChevronMenuButton(NULL, base::string16(), this, false);
167 chevron_->EnableCanvasFlippingForRTLUI(true);
168 chevron_->SetAccessibleName(
169 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
170 chevron_->SetVisible(false);
171 AddChildView(chevron_);
176 BrowserActionsContainer::~BrowserActionsContainer() {
177 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
179 OnBrowserActionsContainerDestroyed());
182 overflow_menu_->set_observer(NULL);
184 model_->RemoveObserver(this);
185 StopShowFolderDropMenuTimer();
187 DeleteBrowserActionViews();
190 void BrowserActionsContainer::Init() {
193 // We wait to set the container width until now so that the chevron images
194 // will be loaded. The width calculation needs to know the chevron size.
195 if (model_ && model_->extensions_initialized())
199 BrowserActionView* BrowserActionsContainer::GetBrowserActionView(
200 ExtensionAction* action) {
201 for (BrowserActionViews::iterator i(browser_action_views_.begin());
202 i != browser_action_views_.end(); ++i) {
203 if ((*i)->extension_action() == action)
209 void BrowserActionsContainer::RefreshBrowserActionViews() {
210 for (size_t i = 0; i < browser_action_views_.size(); ++i)
211 browser_action_views_[i]->UpdateState();
214 void BrowserActionsContainer::CreateBrowserActionViews() {
215 DCHECK(browser_action_views_.empty());
219 const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
220 for (extensions::ExtensionList::const_iterator i(toolbar_items.begin());
221 i != toolbar_items.end(); ++i) {
222 if (!ShouldDisplayBrowserAction(i->get()))
225 BrowserActionView* view = new BrowserActionView(i->get(), browser_, this);
226 browser_action_views_.push_back(view);
231 void BrowserActionsContainer::DeleteBrowserActionViews() {
234 overflow_menu_->NotifyBrowserActionViewsDeleting();
235 STLDeleteElements(&browser_action_views_);
238 size_t BrowserActionsContainer::VisibleBrowserActions() const {
239 size_t visible_actions = 0;
240 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
241 if (browser_action_views_[i]->visible())
244 return visible_actions;
247 size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
249 return VisibleBrowserActions();
251 return WidthToIconCount(animation_target_size_);
254 void BrowserActionsContainer::ExecuteExtensionCommand(
255 const extensions::Extension* extension,
256 const extensions::Command& command) {
257 // Global commands are handled by the ExtensionCommandsGlobalRegistry
259 DCHECK(!command.global());
260 extension_keybinding_registry_->ExecuteCommand(extension->id(),
261 command.accelerator());
264 bool BrowserActionsContainer::ShownInsideMenu() const {
265 return in_overflow_mode();
268 void BrowserActionsContainer::OnBrowserActionViewDragDone() {
269 // We notify here as well as in OnPerformDrop because the dragged view is
270 // removed in OnPerformDrop, so it will never get its OnDragDone() call.
271 // TODO(devlin): we should see about fixing that.
272 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
274 OnBrowserActionDragDone());
277 views::View* BrowserActionsContainer::GetOverflowReferenceView() {
278 // We should only need an overflow reference when using the traditional
284 void BrowserActionsContainer::SetPopupOwner(BrowserActionView* popup_owner) {
285 // We should never be setting a popup owner when one already exists.
286 DCHECK(!popup_owner_ || !popup_owner);
287 popup_owner_ = popup_owner;
290 void BrowserActionsContainer::HideActivePopup() {
292 popup_owner_->view_controller()->HidePopup();
295 void BrowserActionsContainer::AddObserver(
296 BrowserActionsContainerObserver* observer) {
297 observers_.AddObserver(observer);
300 void BrowserActionsContainer::RemoveObserver(
301 BrowserActionsContainerObserver* observer) {
302 observers_.RemoveObserver(observer);
305 gfx::Size BrowserActionsContainer::GetPreferredSize() const {
306 size_t icon_count = browser_action_views_.size() -
307 (in_overflow_mode() ? main_container_->VisibleBrowserActions() : 0);
309 // If there are no actions to show, or we are in overflow mode and the main
310 // container is already showing them all, then no further work is required.
314 if (in_overflow_mode()) {
315 // When in overflow, y is multiline, so the pixel count is IconHeight()
316 // times the number of rows needed.
318 IconCountToWidth(kIconsPerMenuRow, false),
319 (((icon_count - 1) / kIconsPerMenuRow) + 1) * IconHeight());
322 // We calculate the size of the view by taking the current width and
323 // subtracting resize_amount_ (the latter represents how far the user is
324 // resizing the view or, if animating the snapping, how far to animate it).
325 // But we also clamp it to a minimum size and the maximum size, so that the
326 // container can never shrink too far or take up more space than it needs.
327 // In other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX).
328 int preferred_width = std::min(
329 std::max(MinimumNonemptyWidth(), container_width_ - resize_amount_),
330 IconCountToWidth(-1, false));
331 return gfx::Size(preferred_width, IconHeight());
334 gfx::Size BrowserActionsContainer::GetMinimumSize() const {
335 int min_width = std::min(MinimumNonemptyWidth(), IconCountToWidth(-1, false));
336 return gfx::Size(min_width, IconHeight());
339 void BrowserActionsContainer::Layout() {
340 if (browser_action_views_.empty()) {
347 resize_area_->SetBounds(0, 0, kItemSpacing, height());
349 // If the icons don't all fit, show the chevron (unless suppressed).
350 int max_x = GetPreferredSize().width();
351 if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_ && chevron_) {
352 chevron_->SetVisible(true);
353 gfx::Size chevron_size(chevron_->GetPreferredSize());
355 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
357 width() - ToolbarView::kStandardSpacing - chevron_size.width(),
359 chevron_size.width(),
360 chevron_size.height());
361 } else if (chevron_) {
362 chevron_->SetVisible(false);
365 // Now draw the icons for the browser actions in the available space.
366 int icon_width = IconWidth(false);
367 if (in_overflow_mode()) {
369 i < main_container_->VisibleBrowserActionsAfterAnimation(); ++i) {
370 // Ensure that any browser actions shown in the main view are hidden in
371 // the overflow view.
372 browser_action_views_[i]->SetVisible(false);
375 for (size_t i = main_container_->VisibleBrowserActionsAfterAnimation();
376 i < browser_action_views_.size(); ++i) {
377 BrowserActionView* view = browser_action_views_[i];
378 size_t index = i - main_container_->VisibleBrowserActionsAfterAnimation();
379 int row_index = static_cast<int>(index) / kIconsPerMenuRow;
380 int x = kItemSpacing + (index * IconWidth(true)) -
381 (row_index * IconWidth(true) * kIconsPerMenuRow);
382 gfx::Rect rect_bounds(
383 x, IconHeight() * row_index, icon_width, IconHeight());
384 view->SetBoundsRect(rect_bounds);
385 view->SetVisible(true);
388 for (BrowserActionViews::const_iterator it = browser_action_views_.begin();
389 it < browser_action_views_.end(); ++it) {
390 BrowserActionView* view = *it;
391 int x = ToolbarView::kStandardSpacing +
392 ((it - browser_action_views_.begin()) * IconWidth(true));
393 view->SetVisible(x + icon_width <= max_x);
395 view->SetBounds(x, 0, icon_width, IconHeight());
400 bool BrowserActionsContainer::GetDropFormats(
402 std::set<OSExchangeData::CustomFormat>* custom_formats) {
403 return BrowserActionDragData::GetDropFormats(custom_formats);
406 bool BrowserActionsContainer::AreDropTypesRequired() {
407 return BrowserActionDragData::AreDropTypesRequired();
410 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
411 return BrowserActionDragData::CanDrop(data, profile_);
414 void BrowserActionsContainer::OnDragEntered(
415 const ui::DropTargetEvent& event) {
418 int BrowserActionsContainer::OnDragUpdated(
419 const ui::DropTargetEvent& event) {
420 // First check if we are above the chevron (overflow) menu.
421 if (GetEventHandlerForPoint(event.location()) == chevron_) {
422 if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_)
423 StartShowFolderDropMenuTimer();
424 return ui::DragDropTypes::DRAG_MOVE;
426 StopShowFolderDropMenuTimer();
428 // Figure out where to display the indicator. This is a complex calculation:
430 // First, we figure out how much space is to the left of the icon area, so we
431 // can calculate the true offset into the icon area. The easiest way to do
432 // this is to just find where the first icon starts.
433 int width_before_icons =
434 browser_action_views_[GetFirstVisibleIconIndex()]->x();
436 // If we're right-to-left, we flip the mirror the event.x() so that our
437 // calculations are consistent with left-to-right.
438 int offset_into_icon_area =
439 GetMirroredXInView(event.x()) - width_before_icons;
441 // Next, figure out what row we're on. This only matters for overflow mode,
442 // but the calculation is the same for both.
443 size_t row_index = event.y() / IconHeight();
445 // Sanity check - we should never be on a different row in the main container.
446 DCHECK(in_overflow_mode() || row_index == 0);
448 // Next, we determine which icon to place the indicator in front of. We want
449 // to place the indicator in front of icon n when the cursor is between the
450 // midpoints of icons (n - 1) and n. To do this we take the offset into the
451 // icon area and transform it as follows:
456 // |[IC|ON] [IC|ON] [IC|ON]
457 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
458 // Here the "*" represents the offset into the icon area, and since it's
459 // between a and b, we want to return "1".
461 // Transformed "icon area":
464 // |[ICON] |[ICON] |[ICON] |
465 // If we shift both our offset and our divider points later by half an icon
466 // plus one spacing unit, then it becomes very easy to calculate how many
467 // divider points we've passed, because they're the multiples of "one icon
469 int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) +
470 kItemSpacing) / IconWidth(true);
472 // We need to figure out how many icons are visible on the relevant row.
473 // In the main container, this will just be the visible actions.
474 int visible_icons_on_row = VisibleBrowserActionsAfterAnimation();
475 if (in_overflow_mode()) {
476 // If this is the final row of the overflow, then this is the remainder of
477 // visible icons. Otherwise, it's a full row (kIconsPerRow).
478 visible_icons_on_row =
480 static_cast<size_t>(visible_icons_on_row / kIconsPerMenuRow) ?
481 visible_icons_on_row % kIconsPerMenuRow :
485 // Because the user can drag outside the container bounds, we need to clamp to
486 // the valid range. Note that the maximum allowable value is (num icons), not
487 // (num icons - 1), because we represent the indicator being past the last
488 // icon as being "before the (last + 1) icon".
489 size_t before_icon_in_row =
490 std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row);
492 if (!drop_position_.get() ||
493 !(drop_position_->row == row_index &&
494 drop_position_->icon_in_row == before_icon_in_row)) {
495 drop_position_.reset(new DropPosition(row_index, before_icon_in_row));
499 return ui::DragDropTypes::DRAG_MOVE;
502 void BrowserActionsContainer::OnDragExited() {
503 StopShowFolderDropMenuTimer();
504 drop_position_.reset();
508 int BrowserActionsContainer::OnPerformDrop(
509 const ui::DropTargetEvent& event) {
510 BrowserActionDragData data;
511 if (!data.Read(event.data()))
512 return ui::DragDropTypes::DRAG_NONE;
514 // Make sure we have the same view as we started with.
515 DCHECK_EQ(browser_action_views_[data.index()]->extension()->id(),
520 drop_position_->row * kIconsPerMenuRow + drop_position_->icon_in_row;
521 if (in_overflow_mode())
522 i += GetFirstVisibleIconIndex();
523 // |i| now points to the item to the right of the drop indicator*, which is
524 // correct when dragging an icon to the left. When dragging to the right,
525 // however, we want the icon being dragged to get the index of the item to
526 // the left of the drop indicator, so we subtract one.
527 // * Well, it can also point to the end, but not when dragging to the left. :)
528 if (i > data.index())
531 if (profile_->IsOffTheRecord())
532 i = model_->IncognitoIndexToOriginal(i);
534 model_->MoveBrowserAction(
535 browser_action_views_[data.index()]->extension(), i);
537 OnDragExited(); // Perform clean up after dragging.
538 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
540 OnBrowserActionDragDone());
541 return ui::DragDropTypes::DRAG_MOVE;
544 void BrowserActionsContainer::GetAccessibleState(
545 ui::AXViewState* state) {
546 state->role = ui::AX_ROLE_GROUP;
547 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
550 void BrowserActionsContainer::OnMenuButtonClicked(views::View* source,
551 const gfx::Point& point) {
552 if (source == chevron_) {
554 new BrowserActionOverflowMenuController(this,
557 browser_action_views_,
558 VisibleBrowserActions(),
560 overflow_menu_->set_observer(this);
561 overflow_menu_->RunMenu(GetWidget());
565 void BrowserActionsContainer::WriteDragDataForView(View* sender,
566 const gfx::Point& press_pt,
567 OSExchangeData* data) {
570 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
571 BrowserActionView* view = browser_action_views_[i];
572 if (view == sender) {
573 // Set the dragging image for the icon.
574 gfx::ImageSkia badge(view->GetIconWithBadge());
575 drag_utils::SetDragImageOnDataObject(badge,
576 press_pt.OffsetFromOrigin(),
579 // Fill in the remaining info.
580 BrowserActionDragData drag_data(view->extension()->id(), i);
581 drag_data.Write(profile_, data);
587 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
588 const gfx::Point& p) {
589 return ui::DragDropTypes::DRAG_MOVE;
592 bool BrowserActionsContainer::CanStartDragForView(View* sender,
593 const gfx::Point& press_pt,
594 const gfx::Point& p) {
595 // We don't allow dragging while we're highlighting.
596 return !model_->is_highlighting();
599 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
600 if (!done_resizing) {
601 resize_amount_ = resize_amount;
602 OnBrowserActionVisibilityChanged();
606 // Up until now we've only been modifying the resize_amount, but now it is
607 // time to set the container size to the size we have resized to, and then
608 // animate to the nearest icon count size if necessary (which may be 0).
609 int max_width = IconCountToWidth(-1, false);
611 std::min(std::max(0, container_width_ - resize_amount), max_width);
612 SaveDesiredSizeAndAnimate(gfx::Tween::EASE_OUT,
613 WidthToIconCount(container_width_));
616 void BrowserActionsContainer::AnimationProgressed(
617 const gfx::Animation* animation) {
618 DCHECK_EQ(resize_animation_.get(), animation);
619 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
620 (container_width_ - animation_target_size_));
621 OnBrowserActionVisibilityChanged();
624 void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
625 container_width_ = animation_target_size_;
626 animation_target_size_ = 0;
628 suppress_chevron_ = false;
629 OnBrowserActionVisibilityChanged();
631 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
633 OnBrowserActionsContainerAnimationEnded());
636 void BrowserActionsContainer::NotifyMenuDeleted(
637 BrowserActionOverflowMenuController* controller) {
638 DCHECK_EQ(overflow_menu_, controller);
639 overflow_menu_ = NULL;
642 content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
643 return browser_->tab_strip_model()->GetActiveWebContents();
646 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
647 SetVisible(!browser_action_views_.empty());
649 owner_view_->Layout();
650 owner_view_->SchedulePaint();
654 extensions::ActiveTabPermissionGranter*
655 BrowserActionsContainer::GetActiveTabPermissionGranter() {
656 content::WebContents* web_contents =
657 browser_->tab_strip_model()->GetActiveWebContents();
660 return extensions::TabHelper::FromWebContents(web_contents)->
661 active_tab_permission_granter();
664 void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
666 const Extension* extension = extensions::ExtensionRegistry::Get(profile_)->
667 enabled_extensions().GetByID(extension_id);
668 model_->MoveBrowserAction(extension, new_index);
672 size_t BrowserActionsContainer::GetFirstVisibleIconIndex() const {
673 return in_overflow_mode() ? model_->GetVisibleIconCount() : 0;
676 ExtensionPopup* BrowserActionsContainer::TestGetPopup() {
677 return popup_owner_ ? popup_owner_->view_controller()->popup() : NULL;
680 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
681 model_->SetVisibleIconCount(icons);
682 chevron_->SetVisible(icons < browser_action_views_.size());
683 container_width_ = IconCountToWidth(icons, chevron_->visible());
688 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
689 // If the views haven't been initialized yet, wait for the next call to
690 // paint (one will be triggered by entering highlight mode).
691 if (model_->is_highlighting() && !browser_action_views_.empty()) {
692 views::Painter::PaintPainterAt(
693 canvas, highlight_painter_.get(), GetLocalBounds());
696 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
697 // dragging (like we do for tab dragging).
698 if (drop_position_.get()) {
699 // The two-pixel width drop indicator.
700 static const int kDropIndicatorWidth = 2;
702 // Convert back to a pixel offset into the container. First find the X
703 // coordinate of the drop icon.
704 int drop_icon_x = browser_action_views_[GetFirstVisibleIconIndex()]->x() +
705 (drop_position_->icon_in_row * IconWidth(true));
706 // Next, find the space before the drop icon. This will either be
707 // kItemSpacing or ToolbarView::kStandardSpacing, depending on whether this
708 // is the first icon.
709 // NOTE: Right now, these are the same. But let's do this right for if they
711 int space_before_drop_icon = drop_position_->icon_in_row == 0 ?
712 ToolbarView::kStandardSpacing : kItemSpacing;
713 // Now place the drop indicator halfway between this and the end of the
714 // previous icon. If there is an odd amount of available space between the
715 // two icons (or the icon and the address bar) after subtracting the drop
716 // indicator width, this calculation puts the extra pixel on the left side
717 // of the indicator, since when the indicator is between the address bar and
718 // the first icon, it looks better closer to the icon.
719 int drop_indicator_x = drop_icon_x -
720 ((space_before_drop_icon + kDropIndicatorWidth) / 2);
721 int row_height = IconHeight();
722 int drop_indicator_y = row_height * drop_position_->row;
723 gfx::Rect indicator_bounds(drop_indicator_x,
727 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
729 // Color of the drop indicator.
730 static const SkColor kDropIndicatorColor = SK_ColorBLACK;
731 canvas->FillRect(indicator_bounds, kDropIndicatorColor);
735 void BrowserActionsContainer::OnThemeChanged() {
739 void BrowserActionsContainer::ViewHierarchyChanged(
740 const ViewHierarchyChangedDetails& details) {
741 // No extensions (e.g., incognito).
745 if (details.is_add && details.child == this) {
746 // Initial toolbar button creation and placement in the widget hierarchy.
747 // We do this here instead of in the constructor because AddBrowserAction
748 // calls Layout on the Toolbar, which needs this object to be constructed
749 // before its Layout function is called.
750 CreateBrowserActionViews();
755 int BrowserActionsContainer::IconWidth(bool include_padding) {
756 static bool initialized = false;
757 static int icon_width = 0;
760 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
761 IDR_BROWSER_ACTION)->width();
763 return icon_width + (include_padding ? kItemSpacing : 0);
767 int BrowserActionsContainer::IconHeight() {
768 static bool initialized = false;
769 static int icon_height = 0;
772 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
773 IDR_BROWSER_ACTION)->height();
778 void BrowserActionsContainer::BrowserActionAdded(const Extension* extension,
781 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
782 DCHECK(browser_action_views_[i]->extension() != extension) <<
783 "Asked to add a browser action view for an extension that already "
789 if (!ShouldDisplayBrowserAction(extension))
792 size_t visible_actions = VisibleBrowserActionsAfterAnimation();
794 // Add the new browser action to the vector and the view hierarchy.
795 if (profile_->IsOffTheRecord())
796 index = model_->OriginalIndexToIncognito(index);
797 BrowserActionView* view = new BrowserActionView(extension, browser_, this);
798 browser_action_views_.insert(browser_action_views_.begin() + index, view);
799 AddChildViewAt(view, index);
801 // If we are still initializing the container, don't bother animating.
802 if (!model_->extensions_initialized())
805 // Enlarge the container if it was already at maximum size and we're not in
806 // the middle of upgrading.
807 if ((model_->GetVisibleIconCount() < 0) &&
808 !extensions::ExtensionSystem::Get(profile_)->runtime_data()->
809 IsBeingUpgraded(extension)) {
810 suppress_chevron_ = true;
811 SaveDesiredSizeAndAnimate(gfx::Tween::LINEAR, visible_actions + 1);
813 // Just redraw the (possibly modified) visible icon set.
814 OnBrowserActionVisibilityChanged();
818 void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) {
821 size_t visible_actions = VisibleBrowserActionsAfterAnimation();
822 for (BrowserActionViews::iterator i(browser_action_views_.begin());
823 i != browser_action_views_.end(); ++i) {
824 if ((*i)->extension() == extension) {
826 browser_action_views_.erase(i);
828 // If the extension is being upgraded we don't want the bar to shrink
829 // because the icon is just going to get re-added to the same location.
830 if (extensions::ExtensionSystem::Get(profile_)->runtime_data()->
831 IsBeingUpgraded(extension))
834 if (browser_action_views_.size() > visible_actions) {
835 // If we have more icons than we can show, then we must not be changing
836 // the container size (since we either removed an icon from the main
837 // area and one from the overflow list will have shifted in, or we
838 // removed an entry directly from the overflow list).
839 OnBrowserActionVisibilityChanged();
841 // Either we went from overflow to no-overflow, or we shrunk the no-
842 // overflow container by 1. Either way the size changed, so animate.
844 chevron_->SetVisible(false);
845 SaveDesiredSizeAndAnimate(gfx::Tween::EASE_OUT,
846 browser_action_views_.size());
848 return; // We have found the action to remove, bail out.
853 void BrowserActionsContainer::BrowserActionMoved(const Extension* extension,
855 if (!ShouldDisplayBrowserAction(extension))
858 if (profile_->IsOffTheRecord())
859 index = model_->OriginalIndexToIncognito(index);
861 DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
863 DeleteBrowserActionViews();
864 CreateBrowserActionViews();
869 bool BrowserActionsContainer::BrowserActionShowPopup(
870 const Extension* extension) {
871 return ShowPopupForExtension(extension, false, false);
874 void BrowserActionsContainer::VisibleCountChanged() {
878 void BrowserActionsContainer::HighlightModeChanged(bool is_highlighting) {
879 // The visual highlighting is done in OnPaint(). It's a bit of a pain that
880 // we delete and recreate everything here, but that's how it's done in
881 // BrowserActionMoved(), too. If we want to optimize it, we could move the
882 // existing icons, instead of deleting it all.
883 DeleteBrowserActionViews();
884 CreateBrowserActionViews();
885 SaveDesiredSizeAndAnimate(gfx::Tween::LINEAR, browser_action_views_.size());
888 void BrowserActionsContainer::LoadImages() {
889 ui::ThemeProvider* tp = GetThemeProvider();
890 if (!tp || !chevron_)
893 chevron_->SetImage(views::Button::STATE_NORMAL,
894 *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
896 const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT);
897 highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages));
900 void BrowserActionsContainer::SetContainerWidth() {
901 // The slave only draws the overflow (what isn't visible in the other
903 int visible_actions = in_overflow_mode() ?
904 model_->toolbar_items().size() - model_->GetVisibleIconCount() :
905 model_->GetVisibleIconCount();
906 if (visible_actions < 0) // All icons should be visible.
907 visible_actions = model_->toolbar_items().size();
909 chevron_->SetVisible(
910 static_cast<size_t>(visible_actions) < model_->toolbar_items().size());
913 IconCountToWidth(visible_actions, chevron_ && chevron_->visible());
916 void BrowserActionsContainer::CloseOverflowMenu() {
918 overflow_menu_->CancelMenu();
921 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
922 show_menu_task_factory_.InvalidateWeakPtrs();
925 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
926 base::MessageLoop::current()->PostDelayedTask(
928 base::Bind(&BrowserActionsContainer::ShowDropFolder,
929 show_menu_task_factory_.GetWeakPtr()),
930 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
933 void BrowserActionsContainer::ShowDropFolder() {
934 DCHECK(!overflow_menu_);
936 new BrowserActionOverflowMenuController(this,
939 browser_action_views_,
940 VisibleBrowserActions(),
942 overflow_menu_->set_observer(this);
943 overflow_menu_->RunMenu(GetWidget());
946 int BrowserActionsContainer::IconCountToWidth(int icons,
947 bool display_chevron) const {
949 icons = browser_action_views_.size();
950 if ((icons == 0) && !display_chevron)
951 return ToolbarView::kStandardSpacing;
953 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
954 int chevron_size = chevron_ && display_chevron ?
955 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
956 return ToolbarView::kStandardSpacing + icons_size + chevron_size +
957 ToolbarView::kStandardSpacing;
960 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
961 // Check for widths large enough to show the entire icon set.
962 if (pixels >= IconCountToWidth(-1, false))
963 return browser_action_views_.size();
965 // We need to reserve space for the resize area, chevron, and the spacing on
966 // either side of the chevron.
967 int available_space = pixels - ToolbarView::kStandardSpacing -
968 (chevron_ ? chevron_->GetPreferredSize().width() : 0) -
969 kChevronSpacing - ToolbarView::kStandardSpacing;
970 // Now we add an extra between-item padding value so the space can be divided
971 // evenly by (size of icon with padding).
972 return static_cast<size_t>(
973 std::max(0, available_space + kItemSpacing) / IconWidth(true));
976 int BrowserActionsContainer::MinimumNonemptyWidth() const {
978 return ToolbarView::kStandardSpacing;
979 return (ToolbarView::kStandardSpacing * 2) + kChevronSpacing +
980 chevron_->GetPreferredSize().width();
983 void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
984 gfx::Tween::Type tween_type,
985 size_t num_visible_icons) {
986 // Save off the desired number of visible icons. We do this now instead of at
987 // the end of the animation so that even if the browser is shut down while
988 // animating, the right value will be restored on next run.
989 // NOTE: Don't save the icon count in incognito because there may be fewer
990 // icons in that mode. The result is that the container in a normal window is
991 // always at least as wide as in an incognito window.
992 if (!profile_->IsOffTheRecord())
993 model_->SetVisibleIconCount(num_visible_icons);
994 int target_size = IconCountToWidth(num_visible_icons,
995 num_visible_icons < browser_action_views_.size());
996 if (resize_animation_ && !disable_animations_during_testing_) {
997 // Animate! We have to set the animation_target_size_ after calling Reset(),
998 // because that could end up calling AnimationEnded which clears the value.
999 resize_animation_->Reset();
1000 resize_animation_->SetTweenType(tween_type);
1001 animation_target_size_ = target_size;
1002 resize_animation_->Show();
1004 animation_target_size_ = target_size;
1005 AnimationEnded(resize_animation_.get());
1009 bool BrowserActionsContainer::ShouldDisplayBrowserAction(
1010 const Extension* extension) {
1011 // Only display incognito-enabled extensions while in incognito mode.
1012 return !profile_->IsOffTheRecord() ||
1013 extensions::util::IsIncognitoEnabled(extension->id(), profile_);
1016 bool BrowserActionsContainer::ShowPopupForExtension(
1017 const extensions::Extension* extension,
1018 bool grant_tab_permissions,
1019 bool can_override) {
1020 // If the popup cannot override other views, then no other popups can be
1021 // showing, and it must be shown in the active widow with a visible toolbar.
1022 // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
1024 if (!can_override &&
1026 !browser_->window()->IsActive() ||
1027 !browser_->window()->IsToolbarVisible())) {
1031 for (BrowserActionViews::iterator iter = browser_action_views_.begin();
1032 iter != browser_action_views_.end(); ++iter) {
1033 BrowserActionView* view = (*iter);
1034 if (view->extension() == extension)
1035 return view->view_controller()->ExecuteAction(
1036 ExtensionPopup::SHOW, grant_tab_permissions);