[Extensions UI] Adjust extension toolbar redesign surfacing bubble width
[chromium-blink-merge.git] / chrome / browser / ui / views / toolbar / browser_actions_container.cc
blob6213483435d9ddb51e754ca5795804082ea13ebf
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_message_bubble_controller.h"
10 #include "chrome/browser/extensions/extension_toolbar_model.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/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
17 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
18 #include "chrome/browser/ui/toolbar/wrench_menu_badge_controller.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_message_bubble_view.h"
22 #include "chrome/browser/ui/views/extensions/extension_toolbar_icon_surfacing_bubble_views.h"
23 #include "chrome/browser/ui/views/frame/browser_view.h"
24 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
25 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
26 #include "chrome/browser/ui/views/toolbar/wrench_toolbar_button.h"
27 #include "chrome/common/extensions/command.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "extensions/common/feature_switch.h"
30 #include "grit/theme_resources.h"
31 #include "third_party/skia/include/core/SkColor.h"
32 #include "ui/accessibility/ax_view_state.h"
33 #include "ui/base/dragdrop/drag_utils.h"
34 #include "ui/base/l10n/l10n_util.h"
35 #include "ui/base/nine_image_painter_factory.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "ui/base/theme_provider.h"
38 #include "ui/gfx/canvas.h"
39 #include "ui/gfx/geometry/rect.h"
40 #include "ui/resources/grit/ui_resources.h"
41 #include "ui/views/bubble/bubble_delegate.h"
42 #include "ui/views/controls/resize_area.h"
43 #include "ui/views/painter.h"
44 #include "ui/views/widget/widget.h"
46 namespace {
48 // Horizontal spacing before the chevron (if visible).
49 const int kChevronSpacing = ToolbarView::kStandardSpacing - 2;
51 } // namespace
53 ////////////////////////////////////////////////////////////////////////////////
54 // BrowserActionsContainer::DropPosition
56 struct BrowserActionsContainer::DropPosition {
57 DropPosition(size_t row, size_t icon_in_row);
59 // The (0-indexed) row into which the action will be dropped.
60 size_t row;
62 // The (0-indexed) icon in the row before the action will be dropped.
63 size_t icon_in_row;
66 BrowserActionsContainer::DropPosition::DropPosition(
67 size_t row, size_t icon_in_row)
68 : row(row), icon_in_row(icon_in_row) {
71 ////////////////////////////////////////////////////////////////////////////////
72 // BrowserActionsContainer
74 BrowserActionsContainer::BrowserActionsContainer(
75 Browser* browser,
76 BrowserActionsContainer* main_container)
77 : toolbar_actions_bar_(new ToolbarActionsBar(
78 this,
79 browser,
80 main_container ?
81 main_container->toolbar_actions_bar_.get() : nullptr)),
82 browser_(browser),
83 main_container_(main_container),
84 container_width_(0),
85 resize_area_(NULL),
86 chevron_(NULL),
87 suppress_chevron_(false),
88 added_to_view_(false),
89 shown_bubble_(false),
90 resize_amount_(0),
91 animation_target_size_(0),
92 active_bubble_(nullptr) {
93 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
95 bool overflow_experiment =
96 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
97 DCHECK(!in_overflow_mode() || overflow_experiment);
99 if (!in_overflow_mode()) {
100 resize_animation_.reset(new gfx::SlideAnimation(this));
101 resize_area_ = new views::ResizeArea(this);
102 AddChildView(resize_area_);
104 // 'Main' mode doesn't need a chevron overflow when overflow is shown inside
105 // the Chrome menu.
106 if (!overflow_experiment) {
107 // Since the ChevronMenuButton holds a raw pointer to us, we need to
108 // ensure it doesn't outlive us. Having it owned by the view hierarchy as
109 // a child will suffice.
110 chevron_ = new ChevronMenuButton(this);
111 chevron_->EnableCanvasFlippingForRTLUI(true);
112 chevron_->SetAccessibleName(
113 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
114 chevron_->SetVisible(false);
115 AddChildView(chevron_);
120 BrowserActionsContainer::~BrowserActionsContainer() {
121 if (active_bubble_)
122 active_bubble_->GetWidget()->Close();
123 // We should synchronously receive the OnWidgetClosing() event, so we should
124 // always have cleared the active bubble by now.
125 DCHECK(!active_bubble_);
127 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
128 observers_,
129 OnBrowserActionsContainerDestroyed());
131 toolbar_actions_bar_->DeleteActions();
132 // All views should be removed as part of ToolbarActionsBar::DeleteActions().
133 DCHECK(toolbar_action_views_.empty());
136 void BrowserActionsContainer::Init() {
137 LoadImages();
139 // We wait to set the container width until now so that the chevron images
140 // will be loaded. The width calculation needs to know the chevron size.
141 container_width_ = toolbar_actions_bar_->GetPreferredSize().width();
144 const std::string& BrowserActionsContainer::GetIdAt(size_t index) const {
145 return toolbar_action_views_[index]->view_controller()->GetId();
148 ToolbarActionView* BrowserActionsContainer::GetViewForId(
149 const std::string& id) {
150 for (ToolbarActionView* view : toolbar_action_views_) {
151 if (view->view_controller()->GetId() == id)
152 return view;
154 return nullptr;
157 void BrowserActionsContainer::RefreshToolbarActionViews() {
158 toolbar_actions_bar_->Update();
161 size_t BrowserActionsContainer::VisibleBrowserActions() const {
162 size_t visible_actions = 0;
163 for (const ToolbarActionView* view : toolbar_action_views_) {
164 if (view->visible())
165 ++visible_actions;
167 return visible_actions;
170 size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
171 if (!animating())
172 return VisibleBrowserActions();
174 return toolbar_actions_bar_->WidthToIconCount(animation_target_size_);
177 void BrowserActionsContainer::ExecuteExtensionCommand(
178 const extensions::Extension* extension,
179 const extensions::Command& command) {
180 // Global commands are handled by the ExtensionCommandsGlobalRegistry
181 // instance.
182 DCHECK(!command.global());
183 extension_keybinding_registry_->ExecuteCommand(extension->id(),
184 command.accelerator());
187 bool BrowserActionsContainer::ShownInsideMenu() const {
188 return in_overflow_mode();
191 void BrowserActionsContainer::OnToolbarActionViewDragDone() {
192 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
193 observers_,
194 OnBrowserActionDragDone());
197 views::MenuButton* BrowserActionsContainer::GetOverflowReferenceView() {
198 // With traditional overflow, the reference is the chevron. With the
199 // redesign, we use the wrench menu instead.
200 return chevron_ ?
201 static_cast<views::MenuButton*>(chevron_) :
202 static_cast<views::MenuButton*>(BrowserView::GetBrowserViewForBrowser(
203 browser_)->toolbar()->app_menu());
206 void BrowserActionsContainer::OnMouseEnteredToolbarActionView() {
207 if (!shown_bubble_ && !toolbar_action_views_.empty() &&
208 ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
209 browser_->profile())) {
210 ExtensionToolbarIconSurfacingBubble* bubble =
211 new ExtensionToolbarIconSurfacingBubble(
212 this,
213 make_scoped_ptr(new ExtensionToolbarIconSurfacingBubbleDelegate(
214 browser_->profile())));
215 views::BubbleDelegateView::CreateBubble(bubble);
216 bubble->Show();
218 shown_bubble_ = true;
221 void BrowserActionsContainer::AddViewForAction(
222 ToolbarActionViewController* view_controller,
223 size_t index) {
224 if (chevron_)
225 chevron_->CloseMenu();
227 ToolbarActionView* view =
228 new ToolbarActionView(view_controller, browser_->profile(), this);
229 toolbar_action_views_.insert(toolbar_action_views_.begin() + index, view);
230 AddChildViewAt(view, index);
233 void BrowserActionsContainer::RemoveViewForAction(
234 ToolbarActionViewController* action) {
235 if (chevron_)
236 chevron_->CloseMenu();
238 for (ToolbarActionViews::iterator iter = toolbar_action_views_.begin();
239 iter != toolbar_action_views_.end(); ++iter) {
240 if ((*iter)->view_controller() == action) {
241 delete *iter;
242 toolbar_action_views_.erase(iter);
243 break;
248 void BrowserActionsContainer::RemoveAllViews() {
249 STLDeleteElements(&toolbar_action_views_);
252 void BrowserActionsContainer::Redraw(bool order_changed) {
253 if (!added_to_view_) {
254 // We don't want to redraw before the view has been fully added to the
255 // hierarchy.
256 return;
259 std::vector<ToolbarActionViewController*> actions =
260 toolbar_actions_bar_->GetActions();
261 if (order_changed) {
262 // Run through the views and compare them to the desired order. If something
263 // is out of place, find the correct spot for it.
264 for (int i = 0; i < static_cast<int>(actions.size()) - 1; ++i) {
265 if (actions[i] != toolbar_action_views_[i]->view_controller()) {
266 // Find where the correct view is (it's guaranteed to be after our
267 // current index, since everything up to this point is correct).
268 int j = i + 1;
269 while (actions[i] != toolbar_action_views_[j]->view_controller())
270 ++j;
271 std::swap(toolbar_action_views_[i], toolbar_action_views_[j]);
276 if (width() != GetPreferredSize().width() && parent()) {
277 parent()->Layout();
278 parent()->SchedulePaint();
279 } else {
280 Layout();
281 SchedulePaint();
285 void BrowserActionsContainer::ResizeAndAnimate(
286 gfx::Tween::Type tween_type,
287 int target_width,
288 bool suppress_chevron) {
289 if (resize_animation_ && !toolbar_actions_bar_->suppress_animation()) {
290 // Animate! We have to set the animation_target_size_ after calling Reset(),
291 // because that could end up calling AnimationEnded which clears the value.
292 resize_animation_->Reset();
293 suppress_chevron_ = suppress_chevron;
294 resize_animation_->SetTweenType(tween_type);
295 animation_target_size_ = target_width;
296 resize_animation_->Show();
297 } else {
298 animation_target_size_ = target_width;
299 AnimationEnded(resize_animation_.get());
303 void BrowserActionsContainer::SetChevronVisibility(bool visible) {
304 if (chevron_)
305 chevron_->SetVisible(visible);
308 int BrowserActionsContainer::GetWidth() const {
309 return container_width_;
312 bool BrowserActionsContainer::IsAnimating() const {
313 return animating();
316 void BrowserActionsContainer::StopAnimating() {
317 animation_target_size_ = container_width_;
318 resize_animation_->Reset();
321 int BrowserActionsContainer::GetChevronWidth() const {
322 return chevron_ ? chevron_->GetPreferredSize().width() + kChevronSpacing : 0;
325 void BrowserActionsContainer::OnOverflowedActionWantsToRunChanged(
326 bool overflowed_action_wants_to_run) {
327 DCHECK(!in_overflow_mode());
328 BrowserView::GetBrowserViewForBrowser(browser_)->toolbar()->
329 wrench_menu_badge_controller()->SetOverflowedToolbarActionWantsToRun(
330 overflowed_action_wants_to_run);
333 void BrowserActionsContainer::ShowExtensionMessageBubble(
334 scoped_ptr<extensions::ExtensionMessageBubbleController> controller,
335 ToolbarActionViewController* anchor_action) {
336 // The container shouldn't be asked to show a bubble if it's animating.
337 DCHECK(!animating());
339 views::View* reference_view = anchor_action ?
340 static_cast<views::View*>(GetViewForId(anchor_action->GetId())) :
341 BrowserView::GetBrowserViewForBrowser(browser_)->toolbar()->app_menu();
343 extensions::ExtensionMessageBubbleController* weak_controller =
344 controller.get();
345 extensions::ExtensionMessageBubbleView* bubble =
346 new extensions::ExtensionMessageBubbleView(
347 reference_view,
348 views::BubbleBorder::TOP_RIGHT,
349 controller.Pass());
350 views::BubbleDelegateView::CreateBubble(bubble);
351 active_bubble_ = bubble;
352 active_bubble_->GetWidget()->AddObserver(this);
353 weak_controller->Show(bubble);
356 void BrowserActionsContainer::OnWidgetClosing(views::Widget* widget) {
357 ClearActiveBubble(widget);
360 void BrowserActionsContainer::OnWidgetDestroying(views::Widget* widget) {
361 ClearActiveBubble(widget);
364 void BrowserActionsContainer::AddObserver(
365 BrowserActionsContainerObserver* observer) {
366 observers_.AddObserver(observer);
369 void BrowserActionsContainer::RemoveObserver(
370 BrowserActionsContainerObserver* observer) {
371 observers_.RemoveObserver(observer);
374 gfx::Size BrowserActionsContainer::GetPreferredSize() const {
375 if (in_overflow_mode())
376 return toolbar_actions_bar_->GetPreferredSize();
378 // If there are no actions to show, then don't show the container at all.
379 if (toolbar_action_views_.empty())
380 return gfx::Size();
382 // We calculate the size of the view by taking the current width and
383 // subtracting resize_amount_ (the latter represents how far the user is
384 // resizing the view or, if animating the snapping, how far to animate it).
385 // But we also clamp it to a minimum size and the maximum size, so that the
386 // container can never shrink too far or take up more space than it needs.
387 // In other words: minimum_width < width - resize < max_width.
388 int preferred_width = std::min(
389 std::max(toolbar_actions_bar_->GetMinimumWidth(),
390 container_width_ - resize_amount_),
391 toolbar_actions_bar_->GetMaximumWidth());
392 return gfx::Size(preferred_width, ToolbarActionsBar::IconHeight());
395 int BrowserActionsContainer::GetHeightForWidth(int width) const {
396 if (in_overflow_mode())
397 toolbar_actions_bar_->SetOverflowRowWidth(width);
398 return GetPreferredSize().height();
401 gfx::Size BrowserActionsContainer::GetMinimumSize() const {
402 return gfx::Size(toolbar_actions_bar_->GetMinimumWidth(),
403 ToolbarActionsBar::IconHeight());
406 void BrowserActionsContainer::Layout() {
407 if (toolbar_actions_bar_->suppress_layout())
408 return;
410 if (toolbar_action_views_.empty()) {
411 SetVisible(false);
412 return;
415 SetVisible(true);
416 if (resize_area_)
417 resize_area_->SetBounds(0, 0, platform_settings().item_spacing, height());
419 // If the icons don't all fit, show the chevron (unless suppressed).
420 int max_x = GetPreferredSize().width();
421 if (toolbar_actions_bar_->IconCountToWidth(-1) > max_x &&
422 !suppress_chevron_ && chevron_) {
423 chevron_->SetVisible(true);
424 gfx::Size chevron_size(chevron_->GetPreferredSize());
425 max_x -= chevron_size.width() + kChevronSpacing;
426 chevron_->SetBounds(
427 width() - ToolbarView::kStandardSpacing - chevron_size.width(),
429 chevron_size.width(),
430 chevron_size.height());
431 } else if (chevron_) {
432 chevron_->SetVisible(false);
435 // The range of visible icons, from start_index (inclusive) to end_index
436 // (exclusive).
437 size_t start_index = in_overflow_mode() ?
438 toolbar_action_views_.size() - toolbar_actions_bar_->GetIconCount() : 0u;
439 // For the main container's last visible icon, we calculate how many icons we
440 // can display with the given width. We add an extra item_spacing because the
441 // last icon doesn't need padding, but we want it to divide easily.
442 size_t end_index = in_overflow_mode() ?
443 toolbar_action_views_.size() :
444 (max_x - platform_settings().left_padding -
445 platform_settings().right_padding +
446 platform_settings().item_spacing) /
447 ToolbarActionsBar::IconWidth(true);
448 // The maximum length for one row of icons.
449 size_t row_length = in_overflow_mode() ?
450 platform_settings().icons_per_overflow_menu_row : end_index;
452 // Now draw the icons for the actions in the available space. Once all the
453 // variables are in place, the layout works equally well for the main and
454 // overflow container.
455 for (size_t i = 0u; i < toolbar_action_views_.size(); ++i) {
456 ToolbarActionView* view = toolbar_action_views_[i];
457 if (i < start_index || i >= end_index) {
458 view->SetVisible(false);
459 } else {
460 size_t relative_index = i - start_index;
461 size_t index_in_row = relative_index % row_length;
462 size_t row_index = relative_index / row_length;
463 view->SetBounds(platform_settings().left_padding +
464 index_in_row * ToolbarActionsBar::IconWidth(true),
465 row_index * ToolbarActionsBar::IconHeight(),
466 ToolbarActionsBar::IconWidth(false),
467 ToolbarActionsBar::IconHeight());
468 view->SetVisible(true);
473 void BrowserActionsContainer::OnMouseEntered(const ui::MouseEvent& event) {
474 OnMouseEnteredToolbarActionView();
477 bool BrowserActionsContainer::GetDropFormats(
478 int* formats,
479 std::set<OSExchangeData::CustomFormat>* custom_formats) {
480 return BrowserActionDragData::GetDropFormats(custom_formats);
483 bool BrowserActionsContainer::AreDropTypesRequired() {
484 return BrowserActionDragData::AreDropTypesRequired();
487 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
488 return BrowserActionDragData::CanDrop(data, browser_->profile());
491 int BrowserActionsContainer::OnDragUpdated(
492 const ui::DropTargetEvent& event) {
493 size_t row_index = 0;
494 size_t before_icon_in_row = 0;
495 // If there are no visible actions (such as when dragging an icon to an empty
496 // overflow/main container), then 0, 0 for row, column is correct.
497 if (VisibleBrowserActions() != 0) {
498 // Figure out where to display the indicator. This is a complex calculation:
500 // First, we subtract out the padding to the left of the icon area, which is
501 // ToolbarView::kStandardSpacing. If we're right-to-left, we also mirror the
502 // event.x() so that our calculations are consistent with left-to-right.
503 int offset_into_icon_area =
504 GetMirroredXInView(event.x()) - ToolbarView::kStandardSpacing;
506 // Next, figure out what row we're on. This only matters for overflow mode,
507 // but the calculation is the same for both.
508 row_index = event.y() / ToolbarActionsBar::IconHeight();
510 // Sanity check - we should never be on a different row in the main
511 // container.
512 DCHECK(in_overflow_mode() || row_index == 0);
514 // Next, we determine which icon to place the indicator in front of. We want
515 // to place the indicator in front of icon n when the cursor is between the
516 // midpoints of icons (n - 1) and n. To do this we take the offset into the
517 // icon area and transform it as follows:
519 // Real icon area:
520 // 0 a * b c
521 // | | | |
522 // |[IC|ON] [IC|ON] [IC|ON]
523 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
524 // Here the "*" represents the offset into the icon area, and since it's
525 // between a and b, we want to return "1".
527 // Transformed "icon area":
528 // 0 a * b c
529 // | | | |
530 // |[ICON] |[ICON] |[ICON] |
531 // If we shift both our offset and our divider points later by half an icon
532 // plus one spacing unit, then it becomes very easy to calculate how many
533 // divider points we've passed, because they're the multiples of "one icon
534 // plus padding".
535 int before_icon_unclamped =
536 (offset_into_icon_area + (ToolbarActionsBar::IconWidth(false) / 2) +
537 platform_settings().item_spacing) / ToolbarActionsBar::IconWidth(true);
539 // We need to figure out how many icons are visible on the relevant row.
540 // In the main container, this will just be the visible actions.
541 int visible_icons_on_row = VisibleBrowserActionsAfterAnimation();
542 if (in_overflow_mode()) {
543 int icons_per_row = platform_settings().icons_per_overflow_menu_row;
544 // If this is the final row of the overflow, then this is the remainder of
545 // visible icons. Otherwise, it's a full row (kIconsPerRow).
546 visible_icons_on_row =
547 row_index ==
548 static_cast<size_t>(visible_icons_on_row / icons_per_row) ?
549 visible_icons_on_row % icons_per_row : icons_per_row;
552 // Because the user can drag outside the container bounds, we need to clamp
553 // to the valid range. Note that the maximum allowable value is (num icons),
554 // not (num icons - 1), because we represent the indicator being past the
555 // last icon as being "before the (last + 1) icon".
556 before_icon_in_row =
557 std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row);
560 if (!drop_position_.get() ||
561 !(drop_position_->row == row_index &&
562 drop_position_->icon_in_row == before_icon_in_row)) {
563 drop_position_.reset(new DropPosition(row_index, before_icon_in_row));
564 SchedulePaint();
567 return ui::DragDropTypes::DRAG_MOVE;
570 void BrowserActionsContainer::OnDragExited() {
571 drop_position_.reset();
572 SchedulePaint();
575 int BrowserActionsContainer::OnPerformDrop(
576 const ui::DropTargetEvent& event) {
577 BrowserActionDragData data;
578 if (!data.Read(event.data()))
579 return ui::DragDropTypes::DRAG_NONE;
581 // Make sure we have the same view as we started with.
582 DCHECK_EQ(GetIdAt(data.index()), data.id());
584 size_t i = drop_position_->row *
585 platform_settings().icons_per_overflow_menu_row +
586 drop_position_->icon_in_row;
587 if (in_overflow_mode())
588 i += main_container_->VisibleBrowserActionsAfterAnimation();
589 // |i| now points to the item to the right of the drop indicator*, which is
590 // correct when dragging an icon to the left. When dragging to the right,
591 // however, we want the icon being dragged to get the index of the item to
592 // the left of the drop indicator, so we subtract one.
593 // * Well, it can also point to the end, but not when dragging to the left. :)
594 if (i > data.index())
595 --i;
597 ToolbarActionsBar::DragType drag_type = ToolbarActionsBar::DRAG_TO_SAME;
598 if (!toolbar_action_views_[data.index()]->visible())
599 drag_type = in_overflow_mode() ? ToolbarActionsBar::DRAG_TO_OVERFLOW :
600 ToolbarActionsBar::DRAG_TO_MAIN;
602 toolbar_actions_bar_->OnDragDrop(data.index(), i, drag_type);
604 OnDragExited(); // Perform clean up after dragging.
605 return ui::DragDropTypes::DRAG_MOVE;
608 void BrowserActionsContainer::GetAccessibleState(
609 ui::AXViewState* state) {
610 state->role = ui::AX_ROLE_GROUP;
611 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
614 void BrowserActionsContainer::WriteDragDataForView(View* sender,
615 const gfx::Point& press_pt,
616 OSExchangeData* data) {
617 DCHECK(data);
619 ToolbarActionViews::iterator iter = std::find(toolbar_action_views_.begin(),
620 toolbar_action_views_.end(),
621 sender);
622 DCHECK(iter != toolbar_action_views_.end());
623 ToolbarActionViewController* view_controller = (*iter)->view_controller();
624 gfx::Size size(ToolbarActionsBar::IconWidth(false),
625 ToolbarActionsBar::IconHeight());
626 drag_utils::SetDragImageOnDataObject(
627 view_controller->GetIcon(GetCurrentWebContents(), size).AsImageSkia(),
628 press_pt.OffsetFromOrigin(),
629 data);
630 // Fill in the remaining info.
631 BrowserActionDragData drag_data(view_controller->GetId(),
632 iter - toolbar_action_views_.begin());
633 drag_data.Write(browser_->profile(), data);
636 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
637 const gfx::Point& p) {
638 return ui::DragDropTypes::DRAG_MOVE;
641 bool BrowserActionsContainer::CanStartDragForView(View* sender,
642 const gfx::Point& press_pt,
643 const gfx::Point& p) {
644 // We don't allow dragging while we're highlighting.
645 return !toolbar_actions_bar_->is_highlighting();
648 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
649 // We don't allow resize while the toolbar is highlighting a subset of
650 // actions, since this is a temporary and entirely browser-driven sequence in
651 // order to warn the user about potentially dangerous items.
652 if (toolbar_actions_bar_->is_highlighting())
653 return;
655 if (!done_resizing) {
656 resize_amount_ = resize_amount;
657 Redraw(false);
658 return;
661 // Up until now we've only been modifying the resize_amount, but now it is
662 // time to set the container size to the size we have resized to, and then
663 // animate to the nearest icon count size if necessary (which may be 0).
664 container_width_ =
665 std::min(std::max(toolbar_actions_bar_->GetMinimumWidth(),
666 container_width_ - resize_amount),
667 toolbar_actions_bar_->GetMaximumWidth());
668 toolbar_actions_bar_->OnResizeComplete(container_width_);
671 void BrowserActionsContainer::AnimationProgressed(
672 const gfx::Animation* animation) {
673 DCHECK_EQ(resize_animation_.get(), animation);
674 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
675 (container_width_ - animation_target_size_));
676 Redraw(false);
679 void BrowserActionsContainer::AnimationCanceled(
680 const gfx::Animation* animation) {
681 AnimationEnded(animation);
684 void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
685 container_width_ = animation_target_size_;
686 animation_target_size_ = 0;
687 resize_amount_ = 0;
688 suppress_chevron_ = false;
689 Redraw(false);
690 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
691 observers_,
692 OnBrowserActionsContainerAnimationEnded());
694 toolbar_actions_bar_->OnAnimationEnded();
697 content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
698 return browser_->tab_strip_model()->GetActiveWebContents();
701 extensions::ActiveTabPermissionGranter*
702 BrowserActionsContainer::GetActiveTabPermissionGranter() {
703 content::WebContents* web_contents = GetCurrentWebContents();
704 if (!web_contents)
705 return NULL;
706 return extensions::TabHelper::FromWebContents(web_contents)->
707 active_tab_permission_granter();
710 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
711 // If the views haven't been initialized yet, wait for the next call to
712 // paint (one will be triggered by entering highlight mode).
713 if (toolbar_actions_bar_->is_highlighting() &&
714 !toolbar_action_views_.empty() && !in_overflow_mode()) {
715 extensions::ExtensionToolbarModel::HighlightType highlight_type =
716 toolbar_actions_bar_->highlight_type();
717 views::Painter* painter =
718 highlight_type == extensions::ExtensionToolbarModel::HIGHLIGHT_INFO
719 ? info_highlight_painter_.get()
720 : warning_highlight_painter_.get();
721 views::Painter::PaintPainterAt(canvas, painter, GetLocalBounds());
724 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
725 // dragging (like we do for tab dragging).
726 if (drop_position_.get()) {
727 // The two-pixel width drop indicator.
728 static const int kDropIndicatorWidth = 2;
730 // Convert back to a pixel offset into the container. First find the X
731 // coordinate of the drop icon.
732 int drop_icon_x = ToolbarView::kStandardSpacing +
733 (drop_position_->icon_in_row * ToolbarActionsBar::IconWidth(true));
734 // Next, find the space before the drop icon. This will either be
735 // left padding or item spacing, depending on whether this is the first
736 // icon.
737 // NOTE: Right now, these are the same. But let's do this right for if they
738 // ever aren't.
739 int space_before_drop_icon = drop_position_->icon_in_row == 0 ?
740 platform_settings().left_padding : platform_settings().item_spacing;
741 // Now place the drop indicator halfway between this and the end of the
742 // previous icon. If there is an odd amount of available space between the
743 // two icons (or the icon and the address bar) after subtracting the drop
744 // indicator width, this calculation puts the extra pixel on the left side
745 // of the indicator, since when the indicator is between the address bar and
746 // the first icon, it looks better closer to the icon.
747 int drop_indicator_x = drop_icon_x -
748 ((space_before_drop_icon + kDropIndicatorWidth) / 2);
749 int row_height = ToolbarActionsBar::IconHeight();
750 int drop_indicator_y = row_height * drop_position_->row;
751 gfx::Rect indicator_bounds(drop_indicator_x,
752 drop_indicator_y,
753 kDropIndicatorWidth,
754 row_height);
755 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
757 // Color of the drop indicator.
758 static const SkColor kDropIndicatorColor = SK_ColorBLACK;
759 canvas->FillRect(indicator_bounds, kDropIndicatorColor);
763 void BrowserActionsContainer::OnThemeChanged() {
764 LoadImages();
767 void BrowserActionsContainer::ViewHierarchyChanged(
768 const ViewHierarchyChangedDetails& details) {
769 if (!toolbar_actions_bar_->enabled())
770 return;
772 if (details.is_add && details.child == this) {
773 if (!in_overflow_mode() && // We only need one keybinding registry.
774 parent()->GetFocusManager()) { // focus manager can be null in tests.
775 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
776 browser_->profile(),
777 parent()->GetFocusManager(),
778 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
779 this));
782 // Initial toolbar button creation and placement in the widget hierarchy.
783 // We do this here instead of in the constructor because adding views
784 // calls Layout on the Toolbar, which needs this object to be constructed
785 // before its Layout function is called.
786 toolbar_actions_bar_->CreateActions();
788 added_to_view_ = true;
792 void BrowserActionsContainer::LoadImages() {
793 if (in_overflow_mode())
794 return; // Overflow mode has neither a chevron nor highlighting.
796 ui::ThemeProvider* tp = GetThemeProvider();
797 if (tp && chevron_) {
798 chevron_->SetImage(views::Button::STATE_NORMAL,
799 *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
802 const int kInfoImages[] = IMAGE_GRID(IDR_TOOLBAR_ACTION_HIGHLIGHT);
803 info_highlight_painter_.reset(
804 views::Painter::CreateImageGridPainter(kInfoImages));
805 const int kWarningImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT);
806 warning_highlight_painter_.reset(
807 views::Painter::CreateImageGridPainter(kWarningImages));
810 void BrowserActionsContainer::ClearActiveBubble(views::Widget* widget) {
811 DCHECK(active_bubble_);
812 DCHECK_EQ(active_bubble_->GetWidget(), widget);
813 widget->RemoveObserver(this);
814 active_bubble_ = nullptr;