browser_actions_container.cc revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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.
4
5#include "chrome/browser/views/browser_actions_container.h"
6
7#include "app/l10n_util.h"
8#include "app/resource_bundle.h"
9#include "base/stl_util-inl.h"
10#include "base/string_util.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/browser_window.h"
13#include "chrome/browser/extensions/extension_browser_event_router.h"
14#include "chrome/browser/extensions/extension_host.h"
15#include "chrome/browser/extensions/extension_tabs_module.h"
16#include "chrome/browser/extensions/extension_service.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/renderer_host/render_view_host.h"
19#include "chrome/browser/renderer_host/render_widget_host_view.h"
20#include "chrome/browser/tab_contents/tab_contents.h"
21#include "chrome/browser/themes/browser_theme_provider.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/view_ids.h"
24#include "chrome/browser/ui/views/detachable_toolbar_view.h"
25#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
26#include "chrome/browser/ui/views/extensions/extension_popup.h"
27#include "chrome/browser/ui/views/toolbar_view.h"
28#include "chrome/common/extensions/extension_action.h"
29#include "chrome/common/extensions/extension_resource.h"
30#include "chrome/common/notification_source.h"
31#include "chrome/common/notification_type.h"
32#include "chrome/common/pref_names.h"
33#include "gfx/canvas.h"
34#include "gfx/canvas_skia.h"
35#include "grit/app_resources.h"
36#include "grit/generated_resources.h"
37#include "third_party/skia/include/core/SkBitmap.h"
38#include "third_party/skia/include/core/SkTypeface.h"
39#include "third_party/skia/include/effects/SkGradientShader.h"
40#include "ui/base/animation/slide_animation.h"
41#include "views/controls/button/menu_button.h"
42#include "views/controls/button/text_button.h"
43#include "views/controls/menu/menu_2.h"
44#include "views/drag_utils.h"
45#include "views/window/window.h"
46
47#include "grit/theme_resources.h"
48
49// Horizontal spacing between most items in the container, as well as after the
50// last item or chevron (if visible).
51static const int kItemSpacing = ToolbarView::kStandardSpacing;
52// Horizontal spacing before the chevron (if visible).
53static const int kChevronSpacing = kItemSpacing - 2;
54
55// static
56bool BrowserActionsContainer::disable_animations_during_testing_ = false;
57
58////////////////////////////////////////////////////////////////////////////////
59// BrowserActionButton
60
61BrowserActionButton::BrowserActionButton(const Extension* extension,
62                                         BrowserActionsContainer* panel)
63    : ALLOW_THIS_IN_INITIALIZER_LIST(
64          MenuButton(this, std::wstring(), NULL, false)),
65      browser_action_(extension->browser_action()),
66      extension_(extension),
67      ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)),
68      showing_context_menu_(false),
69      panel_(panel) {
70  set_border(NULL);
71  set_alignment(TextButton::ALIGN_CENTER);
72
73  // No UpdateState() here because View hierarchy not setup yet. Our parent
74  // should call UpdateState() after creation.
75
76  registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED,
77                 Source<ExtensionAction>(browser_action_));
78}
79
80void BrowserActionButton::Destroy() {
81  if (showing_context_menu_) {
82    context_menu_menu_->CancelMenu();
83    MessageLoop::current()->DeleteSoon(FROM_HERE, this);
84  } else {
85    delete this;
86  }
87}
88
89void BrowserActionButton::ViewHierarchyChanged(
90    bool is_add, View* parent, View* child) {
91  if (is_add && child == this) {
92    // The Browser Action API does not allow the default icon path to be
93    // changed at runtime, so we can load this now and cache it.
94    std::string relative_path = browser_action_->default_icon_path();
95    if (relative_path.empty())
96      return;
97
98    // LoadImage is not guaranteed to be synchronous, so we might see the
99    // callback OnImageLoaded execute immediately. It (through UpdateState)
100    // expects GetParent() to return the owner for this button, so this
101    // function is as early as we can start this request.
102    tracker_.LoadImage(extension_, extension_->GetResource(relative_path),
103                       gfx::Size(Extension::kBrowserActionIconMaxSize,
104                                 Extension::kBrowserActionIconMaxSize),
105                       ImageLoadingTracker::DONT_CACHE);
106  }
107
108  MenuButton::ViewHierarchyChanged(is_add, parent, child);
109}
110
111void BrowserActionButton::ButtonPressed(views::Button* sender,
112                                        const views::Event& event) {
113  panel_->OnBrowserActionExecuted(this, false);
114}
115
116void BrowserActionButton::OnImageLoaded(SkBitmap* image,
117                                        ExtensionResource resource,
118                                        int index) {
119  if (image)
120    default_icon_ = *image;
121
122  // Call back to UpdateState() because a more specific icon might have been set
123  // while the load was outstanding.
124  UpdateState();
125}
126
127void BrowserActionButton::UpdateState() {
128  int tab_id = panel_->GetCurrentTabId();
129  if (tab_id < 0)
130    return;
131
132  SkBitmap icon(browser_action()->GetIcon(tab_id));
133  if (icon.isNull())
134    icon = default_icon_;
135  if (!icon.isNull()) {
136    SkPaint paint;
137    paint.setXfermode(SkXfermode::Create(SkXfermode::kSrcOver_Mode));
138    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
139
140    SkBitmap bg;
141    rb.GetBitmapNamed(IDR_BROWSER_ACTION)->copyTo(&bg,
142        SkBitmap::kARGB_8888_Config);
143    SkCanvas bg_canvas(bg);
144    bg_canvas.drawBitmap(icon, SkIntToScalar((bg.width() - icon.width()) / 2),
145        SkIntToScalar((bg.height() - icon.height()) / 2), &paint);
146    SetIcon(bg);
147
148    SkBitmap bg_h;
149    rb.GetBitmapNamed(IDR_BROWSER_ACTION_H)->copyTo(&bg_h,
150        SkBitmap::kARGB_8888_Config);
151    SkCanvas bg_h_canvas(bg_h);
152    bg_h_canvas.drawBitmap(icon,
153        SkIntToScalar((bg_h.width() - icon.width()) / 2),
154        SkIntToScalar((bg_h.height() - icon.height()) / 2), &paint);
155    SetHoverIcon(bg_h);
156
157    SkBitmap bg_p;
158    rb.GetBitmapNamed(IDR_BROWSER_ACTION_P)->copyTo(&bg_p,
159        SkBitmap::kARGB_8888_Config);
160    SkCanvas bg_p_canvas(bg_p);
161    bg_p_canvas.drawBitmap(icon,
162        SkIntToScalar((bg_p.width() - icon.width()) / 2),
163        SkIntToScalar((bg_p.height() - icon.height()) / 2), &paint);
164    SetPushedIcon(bg_p);
165  }
166
167  // If the browser action name is empty, show the extension name instead.
168  std::wstring name = UTF8ToWide(browser_action()->GetTitle(tab_id));
169  if (name.empty())
170    name = UTF8ToWide(extension()->name());
171  SetTooltipText(name);
172  SetAccessibleName(name);
173  GetParent()->SchedulePaint();
174}
175
176void BrowserActionButton::Observe(NotificationType type,
177                                  const NotificationSource& source,
178                                  const NotificationDetails& details) {
179  DCHECK(type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED);
180  UpdateState();
181  // The browser action may have become visible/hidden so we need to make
182  // sure the state gets updated.
183  panel_->OnBrowserActionVisibilityChanged();
184}
185
186bool BrowserActionButton::IsPopup() {
187  int tab_id = panel_->GetCurrentTabId();
188  return (tab_id < 0) ? false : browser_action_->HasPopup(tab_id);
189}
190
191GURL BrowserActionButton::GetPopupUrl() {
192  int tab_id = panel_->GetCurrentTabId();
193  return (tab_id < 0) ? GURL() : browser_action_->GetPopupUrl(tab_id);
194}
195
196bool BrowserActionButton::Activate() {
197  if (!IsPopup())
198    return true;
199
200  panel_->OnBrowserActionExecuted(this, false);
201
202  // TODO(erikkay): Run a nested modal loop while the mouse is down to
203  // enable menu-like drag-select behavior.
204
205  // The return value of this method is returned via OnMousePressed.
206  // We need to return false here since we're handing off focus to another
207  // widget/view, and true will grab it right back and try to send events
208  // to us.
209  return false;
210}
211
212bool BrowserActionButton::OnMousePressed(const views::MouseEvent& e) {
213  if (!e.IsRightMouseButton()) {
214    return IsPopup() ?
215        MenuButton::OnMousePressed(e) : TextButton::OnMousePressed(e);
216  }
217
218  // Get the top left point of this button in screen coordinates.
219  gfx::Point point = gfx::Point(0, 0);
220  ConvertPointToScreen(this, &point);
221
222  // Make the menu appear below the button.
223  point.Offset(0, height());
224
225  ShowContextMenu(point, true);
226  return false;
227}
228
229void BrowserActionButton::OnMouseReleased(const views::MouseEvent& e,
230                                          bool canceled) {
231  if (IsPopup() || showing_context_menu_) {
232    // TODO(erikkay) this never actually gets called (probably because of the
233    // loss of focus).
234    MenuButton::OnMouseReleased(e, canceled);
235  } else {
236    TextButton::OnMouseReleased(e, canceled);
237  }
238}
239
240bool BrowserActionButton::OnKeyReleased(const views::KeyEvent& e) {
241  return IsPopup() ?
242      MenuButton::OnKeyReleased(e) : TextButton::OnKeyReleased(e);
243}
244
245void BrowserActionButton::OnMouseExited(const views::MouseEvent& e) {
246  if (IsPopup() || showing_context_menu_)
247    MenuButton::OnMouseExited(e);
248  else
249    TextButton::OnMouseExited(e);
250}
251
252void BrowserActionButton::ShowContextMenu(const gfx::Point& p,
253                                          bool is_mouse_gesture) {
254  showing_context_menu_ = true;
255  SetButtonPushed();
256
257  // Reconstructs the menu every time because the menu's contents are dynamic.
258  context_menu_contents_ =
259      new ExtensionContextMenuModel(extension(), panel_->browser(), panel_);
260  context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get()));
261  context_menu_menu_->RunContextMenuAt(p);
262
263  SetButtonNotPushed();
264  showing_context_menu_ = false;
265}
266
267void BrowserActionButton::SetButtonPushed() {
268  SetState(views::CustomButton::BS_PUSHED);
269  menu_visible_ = true;
270}
271
272void BrowserActionButton::SetButtonNotPushed() {
273  SetState(views::CustomButton::BS_NORMAL);
274  menu_visible_ = false;
275}
276
277BrowserActionButton::~BrowserActionButton() {
278}
279
280
281////////////////////////////////////////////////////////////////////////////////
282// BrowserActionView
283
284BrowserActionView::BrowserActionView(const Extension* extension,
285                                     BrowserActionsContainer* panel)
286    : panel_(panel) {
287  button_ = new BrowserActionButton(extension, panel);
288  button_->SetDragController(panel_);
289  AddChildView(button_);
290  button_->UpdateState();
291  SetAccessibleName(UTF16ToWide(
292      l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_BROWSER_ACTION)));
293}
294
295BrowserActionView::~BrowserActionView() {
296  RemoveChildView(button_);
297  button_->Destroy();
298}
299
300gfx::Canvas* BrowserActionView::GetIconWithBadge() {
301  int tab_id = panel_->GetCurrentTabId();
302
303  SkBitmap icon = button_->extension()->browser_action()->GetIcon(tab_id);
304  if (icon.isNull())
305    icon = button_->default_icon();
306
307  gfx::Canvas* canvas = new gfx::CanvasSkia(icon.width(), icon.height(), false);
308  canvas->DrawBitmapInt(icon, 0, 0);
309
310  if (tab_id >= 0) {
311    gfx::Rect bounds(icon.width(), icon.height() + ToolbarView::kVertSpacing);
312    button_->extension()->browser_action()->PaintBadge(canvas, bounds, tab_id);
313  }
314
315  return canvas;
316}
317
318AccessibilityTypes::Role BrowserActionView::GetAccessibleRole() {
319  return AccessibilityTypes::ROLE_GROUPING;
320}
321
322void BrowserActionView::Layout() {
323  // We can't rely on button_->GetPreferredSize() here because that's not set
324  // correctly until the first call to
325  // BrowserActionsContainer::RefreshBrowserActionViews(), whereas this can be
326  // called before that when the initial bounds are set (and then not after,
327  // since the bounds don't change).  So instead of setting the height from the
328  // button's preferred size, we use IconHeight(), since that's how big the
329  // button should be regardless of what it's displaying.
330  button_->SetBounds(0, ToolbarView::kVertSpacing, width(),
331                     BrowserActionsContainer::IconHeight());
332}
333
334void BrowserActionView::PaintChildren(gfx::Canvas* canvas) {
335  View::PaintChildren(canvas);
336  ExtensionAction* action = button()->browser_action();
337  int tab_id = panel_->GetCurrentTabId();
338  if (tab_id >= 0)
339    action->PaintBadge(canvas, gfx::Rect(width(), height()), tab_id);
340}
341
342////////////////////////////////////////////////////////////////////////////////
343// BrowserActionsContainer
344
345BrowserActionsContainer::BrowserActionsContainer(Browser* browser,
346                                                 View* owner_view)
347    : profile_(browser->profile()),
348      browser_(browser),
349      owner_view_(owner_view),
350      popup_(NULL),
351      popup_button_(NULL),
352      model_(NULL),
353      container_width_(0),
354      chevron_(NULL),
355      overflow_menu_(NULL),
356      suppress_chevron_(false),
357      resize_amount_(0),
358      animation_target_size_(0),
359      drop_indicator_position_(-1),
360      ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
361      ALLOW_THIS_IN_INITIALIZER_LIST(show_menu_task_factory_(this)) {
362  SetID(VIEW_ID_BROWSER_ACTION_TOOLBAR);
363
364  if (profile_->GetExtensionService()) {
365    model_ = profile_->GetExtensionService()->toolbar_model();
366    model_->AddObserver(this);
367  }
368
369  resize_animation_.reset(new ui::SlideAnimation(this));
370  resize_area_ = new views::ResizeArea(this);
371  resize_area_->SetAccessibleName(
372      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_SEPARATOR)));
373  AddChildView(resize_area_);
374
375  chevron_ = new views::MenuButton(NULL, std::wstring(), this, false);
376  chevron_->set_border(NULL);
377  chevron_->EnableCanvasFlippingForRTLUI(true);
378  chevron_->SetAccessibleName(
379      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON)));
380  chevron_->SetVisible(false);
381  AddChildView(chevron_);
382
383  SetAccessibleName(
384      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS)));
385}
386
387BrowserActionsContainer::~BrowserActionsContainer() {
388  if (model_)
389    model_->RemoveObserver(this);
390  StopShowFolderDropMenuTimer();
391  HidePopup();
392  DeleteBrowserActionViews();
393}
394
395// Static.
396void BrowserActionsContainer::RegisterUserPrefs(PrefService* prefs) {
397  prefs->RegisterIntegerPref(prefs::kBrowserActionContainerWidth, 0);
398}
399
400void BrowserActionsContainer::Init() {
401  LoadImages();
402
403  // We wait to set the container width until now so that the chevron images
404  // will be loaded.  The width calculation needs to know the chevron size.
405  if (model_ &&
406      !profile_->GetPrefs()->HasPrefPath(prefs::kExtensionToolbarSize)) {
407    // Migration code to the new VisibleIconCount pref.
408    // TODO(mpcomplete): remove this after users are upgraded to 5.0.
409    int predefined_width =
410        profile_->GetPrefs()->GetInteger(prefs::kBrowserActionContainerWidth);
411    if (predefined_width != 0)
412      model_->SetVisibleIconCount(WidthToIconCount(predefined_width));
413  }
414  if (model_ && model_->extensions_initialized())
415    SetContainerWidth();
416}
417
418int BrowserActionsContainer::GetCurrentTabId() const {
419  TabContents* tab_contents = browser_->GetSelectedTabContents();
420  return tab_contents ? tab_contents->controller().session_id().id() : -1;
421}
422
423BrowserActionView* BrowserActionsContainer::GetBrowserActionView(
424    ExtensionAction* action) {
425  for (BrowserActionViews::iterator iter = browser_action_views_.begin();
426       iter != browser_action_views_.end(); ++iter) {
427    if ((*iter)->button()->browser_action() == action)
428      return *iter;
429  }
430  return NULL;
431}
432
433void BrowserActionsContainer::RefreshBrowserActionViews() {
434  for (size_t i = 0; i < browser_action_views_.size(); ++i)
435    browser_action_views_[i]->button()->UpdateState();
436}
437
438void BrowserActionsContainer::CreateBrowserActionViews() {
439  DCHECK(browser_action_views_.empty());
440  if (!model_)
441    return;
442
443  for (ExtensionList::iterator iter = model_->begin(); iter != model_->end();
444       ++iter) {
445    if (!ShouldDisplayBrowserAction(*iter))
446      continue;
447
448    BrowserActionView* view = new BrowserActionView(*iter, this);
449    browser_action_views_.push_back(view);
450    AddChildView(view);
451  }
452}
453
454void BrowserActionsContainer::DeleteBrowserActionViews() {
455  if (!browser_action_views_.empty()) {
456    for (size_t i = 0; i < browser_action_views_.size(); ++i)
457      RemoveChildView(browser_action_views_[i]);
458    STLDeleteContainerPointers(browser_action_views_.begin(),
459                               browser_action_views_.end());
460    browser_action_views_.clear();
461  }
462}
463
464void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
465  SetVisible(!browser_action_views_.empty());
466  owner_view_->Layout();
467  owner_view_->SchedulePaint();
468}
469
470size_t BrowserActionsContainer::VisibleBrowserActions() const {
471  size_t visible_actions = 0;
472  for (size_t i = 0; i < browser_action_views_.size(); ++i) {
473    if (browser_action_views_[i]->IsVisible())
474      ++visible_actions;
475  }
476  return visible_actions;
477}
478
479void BrowserActionsContainer::OnBrowserActionExecuted(
480    BrowserActionButton* button,
481    bool inspect_with_devtools) {
482  ExtensionAction* browser_action = button->browser_action();
483
484  // Popups just display.  No notification to the extension.
485  // TODO(erikkay): should there be?
486  if (!button->IsPopup()) {
487    ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted(
488        profile_, browser_action->extension_id(), browser_);
489    return;
490  }
491
492  // If we're showing the same popup, just hide it and return.
493  bool same_showing = popup_ && button == popup_button_;
494
495  // Always hide the current popup, even if it's not the same.
496  // Only one popup should be visible at a time.
497  HidePopup();
498
499  if (same_showing)
500    return;
501
502  // We can get the execute event for browser actions that are not visible,
503  // since buttons can be activated from the overflow menu (chevron). In that
504  // case we show the popup as originating from the chevron.
505  View* reference_view = button->GetParent()->IsVisible() ? button : chevron_;
506  gfx::Point origin;
507  View::ConvertPointToScreen(reference_view, &origin);
508  gfx::Rect rect = reference_view->bounds();
509  rect.set_origin(origin);
510
511  gfx::NativeWindow frame_window = browser_->window()->GetNativeHandle();
512  BubbleBorder::ArrowLocation arrow_location = base::i18n::IsRTL() ?
513      BubbleBorder::TOP_LEFT : BubbleBorder::TOP_RIGHT;
514
515  popup_ = ExtensionPopup::Show(button->GetPopupUrl(), browser_,
516      browser_->profile(), frame_window, rect, arrow_location, true,
517      inspect_with_devtools, ExtensionPopup::BUBBLE_CHROME, this);
518  popup_button_ = button;
519  popup_button_->SetButtonPushed();
520}
521
522gfx::Size BrowserActionsContainer::GetPreferredSize() {
523  if (browser_action_views_.empty())
524    return gfx::Size(ToolbarView::kStandardSpacing, 0);
525
526  // We calculate the size of the view by taking the current width and
527  // subtracting resize_amount_ (the latter represents how far the user is
528  // resizing the view or, if animating the snapping, how far to animate it).
529  // But we also clamp it to a minimum size and the maximum size, so that the
530  // container can never shrink too far or take up more space than it needs. In
531  // other words: ContainerMinSize() < width() - resize < ClampTo(MAX).
532  int clamped_width = std::min(
533      std::max(ContainerMinSize(), container_width_ - resize_amount_),
534      IconCountToWidth(-1, false));
535  return gfx::Size(clamped_width, 0);
536}
537
538void BrowserActionsContainer::Layout() {
539  if (browser_action_views_.empty()) {
540    SetVisible(false);
541    return;
542  }
543
544  SetVisible(true);
545  resize_area_->SetBounds(0, ToolbarView::kVertSpacing, kItemSpacing,
546                          IconHeight());
547
548  // If the icons don't all fit, show the chevron (unless suppressed).
549  int max_x = GetPreferredSize().width();
550  if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_) {
551    chevron_->SetVisible(true);
552    gfx::Size chevron_size(chevron_->GetPreferredSize());
553    max_x -=
554        ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
555    chevron_->SetBounds(
556        width() - ToolbarView::kStandardSpacing - chevron_size.width(),
557        ToolbarView::kVertSpacing, chevron_size.width(), chevron_size.height());
558  } else {
559    chevron_->SetVisible(false);
560  }
561
562  // Now draw the icons for the browser actions in the available space.
563  int icon_width = IconWidth(false);
564  for (size_t i = 0; i < browser_action_views_.size(); ++i) {
565    BrowserActionView* view = browser_action_views_[i];
566    int x = ToolbarView::kStandardSpacing + (i * IconWidth(true));
567    if (x + icon_width <= max_x) {
568      view->SetBounds(x, 0, icon_width, height());
569      view->SetVisible(true);
570    } else {
571      view->SetVisible(false);
572    }
573  }
574}
575
576void BrowserActionsContainer::Paint(gfx::Canvas* canvas) {
577  // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
578  // dragging (like we do for tab dragging).
579  if (drop_indicator_position_ > -1) {
580    // The two-pixel width drop indicator.
581    static const int kDropIndicatorWidth = 2;
582    gfx::Rect indicator_bounds(
583        drop_indicator_position_ - (kDropIndicatorWidth / 2),
584        ToolbarView::kVertSpacing, kDropIndicatorWidth, IconHeight());
585
586    // Color of the drop indicator.
587    static const SkColor kDropIndicatorColor = SK_ColorBLACK;
588    canvas->FillRectInt(kDropIndicatorColor, indicator_bounds.x(),
589                        indicator_bounds.y(), indicator_bounds.width(),
590                        indicator_bounds.height());
591  }
592}
593
594void BrowserActionsContainer::ViewHierarchyChanged(bool is_add,
595                                                   views::View* parent,
596                                                   views::View* child) {
597  // No extensions (e.g., incognito).
598  if (!model_)
599    return;
600
601  if (is_add && child == this) {
602    // Initial toolbar button creation and placement in the widget hierarchy.
603    // We do this here instead of in the constructor because AddBrowserAction
604    // calls Layout on the Toolbar, which needs this object to be constructed
605    // before its Layout function is called.
606    CreateBrowserActionViews();
607  }
608}
609
610bool BrowserActionsContainer::GetDropFormats(
611    int* formats,
612    std::set<OSExchangeData::CustomFormat>* custom_formats) {
613  custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
614
615  return true;
616}
617
618bool BrowserActionsContainer::AreDropTypesRequired() {
619  return true;
620}
621
622bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
623  BrowserActionDragData drop_data;
624  return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false;
625}
626
627void BrowserActionsContainer::OnDragEntered(
628    const views::DropTargetEvent& event) {
629}
630
631int BrowserActionsContainer::OnDragUpdated(
632    const views::DropTargetEvent& event) {
633  // First check if we are above the chevron (overflow) menu.
634  if (GetViewForPoint(event.location()) == chevron_) {
635    if (show_menu_task_factory_.empty() && !overflow_menu_)
636      StartShowFolderDropMenuTimer();
637    return DragDropTypes::DRAG_MOVE;
638  }
639  StopShowFolderDropMenuTimer();
640
641  // Figure out where to display the indicator.  This is a complex calculation:
642
643  // First, we figure out how much space is to the left of the icon area, so we
644  // can calculate the true offset into the icon area.
645  int width_before_icons = ToolbarView::kStandardSpacing +
646      (base::i18n::IsRTL() ?
647          (chevron_->GetPreferredSize().width() + kChevronSpacing) : 0);
648  int offset_into_icon_area = event.x() - width_before_icons;
649
650  // Next, we determine which icon to place the indicator in front of.  We want
651  // to place the indicator in front of icon n when the cursor is between the
652  // midpoints of icons (n - 1) and n.  To do this we take the offset into the
653  // icon area and transform it as follows:
654  //
655  // Real icon area:
656  //   0   a     *  b        c
657  //   |   |        |        |
658  //   |[IC|ON]  [IC|ON]  [IC|ON]
659  // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
660  // Here the "*" represents the offset into the icon area, and since it's
661  // between a and b, we want to return "1".
662  //
663  // Transformed "icon area":
664  //   0        a     *  b        c
665  //   |        |        |        |
666  //   |[ICON]  |[ICON]  |[ICON]  |
667  // If we shift both our offset and our divider points later by half an icon
668  // plus one spacing unit, then it becomes very easy to calculate how many
669  // divider points we've passed, because they're the multiples of "one icon
670  // plus padding".
671  int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) +
672      kItemSpacing) / IconWidth(true);
673
674  // Because the user can drag outside the container bounds, we need to clamp to
675  // the valid range.  Note that the maximum allowable value is (num icons), not
676  // (num icons - 1), because we represent the indicator being past the last
677  // icon as being "before the (last + 1) icon".
678  int before_icon = std::min(std::max(before_icon_unclamped, 0),
679                             static_cast<int>(VisibleBrowserActions()));
680
681  // Now we convert back to a pixel offset into the container.  We want to place
682  // the center of the drop indicator at the midpoint of the space before our
683  // chosen icon.
684  SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) -
685      (kItemSpacing / 2));
686
687  return DragDropTypes::DRAG_MOVE;
688}
689
690void BrowserActionsContainer::OnDragExited() {
691  StopShowFolderDropMenuTimer();
692  drop_indicator_position_ = -1;
693  SchedulePaint();
694}
695
696int BrowserActionsContainer::OnPerformDrop(
697    const views::DropTargetEvent& event) {
698  BrowserActionDragData data;
699  if (!data.Read(event.GetData()))
700    return DragDropTypes::DRAG_NONE;
701
702  // Make sure we have the same view as we started with.
703  DCHECK_EQ(browser_action_views_[data.index()]->button()->extension()->id(),
704            data.id());
705  DCHECK(model_);
706
707  size_t i = 0;
708  for (; i < browser_action_views_.size(); ++i) {
709    int view_x =
710        browser_action_views_[i]->GetBounds(APPLY_MIRRORING_TRANSFORMATION).x();
711    if (!browser_action_views_[i]->IsVisible() ||
712        (base::i18n::IsRTL() ? (view_x < drop_indicator_position_) :
713            (view_x >= drop_indicator_position_))) {
714      // We have reached the end of the visible icons or found one that has a
715      // higher x position than the drop point.
716      break;
717    }
718  }
719
720  // |i| now points to the item to the right of the drop indicator*, which is
721  // correct when dragging an icon to the left. When dragging to the right,
722  // however, we want the icon being dragged to get the index of the item to
723  // the left of the drop indicator, so we subtract one.
724  // * Well, it can also point to the end, but not when dragging to the left. :)
725  if (i > data.index())
726    --i;
727
728  if (profile_->IsOffTheRecord())
729    i = model_->IncognitoIndexToOriginal(i);
730
731  model_->MoveBrowserAction(
732      browser_action_views_[data.index()]->button()->extension(), i);
733
734  OnDragExited();  // Perform clean up after dragging.
735  return DragDropTypes::DRAG_MOVE;
736}
737
738void BrowserActionsContainer::OnThemeChanged() {
739  LoadImages();
740}
741
742AccessibilityTypes::Role BrowserActionsContainer::GetAccessibleRole() {
743  return AccessibilityTypes::ROLE_GROUPING;
744}
745
746void BrowserActionsContainer::RunMenu(View* source, const gfx::Point& pt) {
747  if (source == chevron_) {
748    overflow_menu_ = new BrowserActionOverflowMenuController(
749        this, chevron_, browser_action_views_, VisibleBrowserActions());
750    overflow_menu_->set_observer(this);
751    overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), false);
752  }
753}
754
755void BrowserActionsContainer::WriteDragData(View* sender,
756                                            const gfx::Point& press_pt,
757                                            OSExchangeData* data) {
758  DCHECK(data);
759
760  for (size_t i = 0; i < browser_action_views_.size(); ++i) {
761    BrowserActionButton* button = browser_action_views_[i]->button();
762    if (button == sender) {
763      // Set the dragging image for the icon.
764      scoped_ptr<gfx::Canvas> canvas(
765          browser_action_views_[i]->GetIconWithBadge());
766      drag_utils::SetDragImageOnDataObject(*canvas, button->size(), press_pt,
767                                           data);
768
769      // Fill in the remaining info.
770      BrowserActionDragData drag_data(
771          browser_action_views_[i]->button()->extension()->id(), i);
772      drag_data.Write(profile_, data);
773      break;
774    }
775  }
776}
777
778int BrowserActionsContainer::GetDragOperations(View* sender,
779                                               const gfx::Point& p) {
780  return DragDropTypes::DRAG_MOVE;
781}
782
783bool BrowserActionsContainer::CanStartDrag(View* sender,
784                                           const gfx::Point& press_pt,
785                                           const gfx::Point& p) {
786  return true;
787}
788
789void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
790  if (!done_resizing) {
791    resize_amount_ = resize_amount;
792    OnBrowserActionVisibilityChanged();
793    return;
794  }
795
796  // Up until now we've only been modifying the resize_amount, but now it is
797  // time to set the container size to the size we have resized to, and then
798  // animate to the nearest icon count size if necessary (which may be 0).
799  int max_width = IconCountToWidth(-1, false);
800  container_width_ =
801      std::min(std::max(0, container_width_ - resize_amount), max_width);
802  SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT,
803                            WidthToIconCount(container_width_));
804}
805
806void BrowserActionsContainer::AnimationProgressed(
807    const ui::Animation* animation) {
808  DCHECK_EQ(resize_animation_.get(), animation);
809  resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
810      (container_width_ - animation_target_size_));
811  OnBrowserActionVisibilityChanged();
812}
813
814void BrowserActionsContainer::AnimationEnded(const ui::Animation* animation) {
815  container_width_ = animation_target_size_;
816  animation_target_size_ = 0;
817  resize_amount_ = 0;
818  OnBrowserActionVisibilityChanged();
819  suppress_chevron_ = false;
820}
821
822void BrowserActionsContainer::NotifyMenuDeleted(
823    BrowserActionOverflowMenuController* controller) {
824  DCHECK(controller == overflow_menu_);
825  overflow_menu_ = NULL;
826}
827
828void BrowserActionsContainer::InspectPopup(ExtensionAction* action) {
829  OnBrowserActionExecuted(GetBrowserActionView(action)->button(), true);
830}
831
832void BrowserActionsContainer::ExtensionPopupIsClosing(ExtensionPopup* popup) {
833  // ExtensionPopup is ref-counted, so we don't need to delete it.
834  DCHECK_EQ(popup_, popup);
835  popup_ = NULL;
836  popup_button_->SetButtonNotPushed();
837  popup_button_ = NULL;
838}
839
840void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
841                                                size_t new_index) {
842  ExtensionService* service = profile_->GetExtensionService();
843  if (service) {
844    const Extension* extension = service->GetExtensionById(extension_id, false);
845    model_->MoveBrowserAction(extension, new_index);
846    SchedulePaint();
847  }
848}
849
850void BrowserActionsContainer::HidePopup() {
851  if (popup_)
852    popup_->Close();
853}
854
855void BrowserActionsContainer::TestExecuteBrowserAction(int index) {
856  BrowserActionButton* button = browser_action_views_[index]->button();
857  OnBrowserActionExecuted(button, false);
858}
859
860void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
861  model_->SetVisibleIconCount(icons);
862  chevron_->SetVisible(icons < browser_action_views_.size());
863  container_width_ = IconCountToWidth(icons, chevron_->IsVisible());
864  Layout();
865  SchedulePaint();
866}
867
868// static
869int BrowserActionsContainer::IconWidth(bool include_padding) {
870  static bool initialized = false;
871  static int icon_width = 0;
872  if (!initialized) {
873    initialized = true;
874    icon_width = ResourceBundle::GetSharedInstance().GetBitmapNamed(
875        IDR_BROWSER_ACTION)->width();
876  }
877  return icon_width + (include_padding ? kItemSpacing : 0);
878}
879
880// static
881int BrowserActionsContainer::IconHeight() {
882  static bool initialized = false;
883  static int icon_height = 0;
884  if (!initialized) {
885    initialized = true;
886    icon_height = ResourceBundle::GetSharedInstance().GetBitmapNamed(
887        IDR_BROWSER_ACTION)->height();
888  }
889  return icon_height;
890}
891
892void BrowserActionsContainer::BrowserActionAdded(const Extension* extension,
893                                                 int index) {
894#if defined(DEBUG)
895  for (size_t i = 0; i < browser_action_views_.size(); ++i) {
896    DCHECK(browser_action_views_[i]->button()->extension() != extension) <<
897           "Asked to add a browser action view for an extension that already "
898           "exists.";
899  }
900#endif
901  CloseOverflowMenu();
902
903  if (!ShouldDisplayBrowserAction(extension))
904    return;
905
906  size_t visible_actions = VisibleBrowserActions();
907
908  // Add the new browser action to the vector and the view hierarchy.
909  if (profile_->IsOffTheRecord())
910    index = model_->OriginalIndexToIncognito(index);
911  BrowserActionView* view = new BrowserActionView(extension, this);
912  browser_action_views_.insert(browser_action_views_.begin() + index, view);
913  AddChildView(index, view);
914
915  // If we are still initializing the container, don't bother animating.
916  if (!model_->extensions_initialized())
917    return;
918
919  // Enlarge the container if it was already at maximum size and we're not in
920  // the middle of upgrading.
921  if ((model_->GetVisibleIconCount() < 0) &&
922      !profile_->GetExtensionService()->IsBeingUpgraded(extension)) {
923    suppress_chevron_ = true;
924    SaveDesiredSizeAndAnimate(ui::Tween::LINEAR, visible_actions + 1);
925  } else {
926    // Just redraw the (possibly modified) visible icon set.
927    OnBrowserActionVisibilityChanged();
928  }
929}
930
931void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) {
932  CloseOverflowMenu();
933
934  if (popup_ && popup_->host()->extension() == extension)
935    HidePopup();
936
937  size_t visible_actions = VisibleBrowserActions();
938  for (BrowserActionViews::iterator iter = browser_action_views_.begin();
939       iter != browser_action_views_.end(); ++iter) {
940    if ((*iter)->button()->extension() == extension) {
941      RemoveChildView(*iter);
942      delete *iter;
943      browser_action_views_.erase(iter);
944
945      // If the extension is being upgraded we don't want the bar to shrink
946      // because the icon is just going to get re-added to the same location.
947      if (profile_->GetExtensionService()->IsBeingUpgraded(extension))
948        return;
949
950      if (browser_action_views_.size() > visible_actions) {
951        // If we have more icons than we can show, then we must not be changing
952        // the container size (since we either removed an icon from the main
953        // area and one from the overflow list will have shifted in, or we
954        // removed an entry directly from the overflow list).
955        OnBrowserActionVisibilityChanged();
956      } else {
957        // Either we went from overflow to no-overflow, or we shrunk the no-
958        // overflow container by 1.  Either way the size changed, so animate.
959        chevron_->SetVisible(false);
960        SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT,
961                                  browser_action_views_.size());
962      }
963      return;
964    }
965  }
966}
967
968void BrowserActionsContainer::BrowserActionMoved(const Extension* extension,
969                                                 int index) {
970  if (!ShouldDisplayBrowserAction(extension))
971    return;
972
973  if (profile_->IsOffTheRecord())
974    index = model_->OriginalIndexToIncognito(index);
975
976  DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
977
978  DeleteBrowserActionViews();
979  CreateBrowserActionViews();
980  Layout();
981  SchedulePaint();
982}
983
984void BrowserActionsContainer::ModelLoaded() {
985  SetContainerWidth();
986}
987
988void BrowserActionsContainer::LoadImages() {
989  ThemeProvider* tp = GetThemeProvider();
990  chevron_->SetIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
991  chevron_->SetHoverIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_H));
992  chevron_->SetPushedIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_P));
993}
994
995void BrowserActionsContainer::SetContainerWidth() {
996  int visible_actions = model_->GetVisibleIconCount();
997  if (visible_actions < 0)  // All icons should be visible.
998    visible_actions = model_->size();
999  chevron_->SetVisible(static_cast<size_t>(visible_actions) < model_->size());
1000  container_width_ = IconCountToWidth(visible_actions, chevron_->IsVisible());
1001}
1002
1003void BrowserActionsContainer::CloseOverflowMenu() {
1004  if (overflow_menu_)
1005    overflow_menu_->CancelMenu();
1006}
1007
1008void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
1009  show_menu_task_factory_.RevokeAll();
1010}
1011
1012void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
1013  int delay = View::GetMenuShowDelay();
1014  MessageLoop::current()->PostDelayedTask(FROM_HERE,
1015      show_menu_task_factory_.NewRunnableMethod(
1016          &BrowserActionsContainer::ShowDropFolder),
1017      delay);
1018}
1019
1020void BrowserActionsContainer::ShowDropFolder() {
1021  DCHECK(!overflow_menu_);
1022  SetDropIndicator(-1);
1023  overflow_menu_ = new BrowserActionOverflowMenuController(
1024      this, chevron_, browser_action_views_, VisibleBrowserActions());
1025  overflow_menu_->set_observer(this);
1026  overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), true);
1027}
1028
1029void BrowserActionsContainer::SetDropIndicator(int x_pos) {
1030  if (drop_indicator_position_ != x_pos) {
1031    drop_indicator_position_ = x_pos;
1032    SchedulePaint();
1033  }
1034}
1035
1036int BrowserActionsContainer::IconCountToWidth(int icons,
1037                                              bool display_chevron) const {
1038  if (icons < 0)
1039    icons = browser_action_views_.size();
1040  if ((icons == 0) && !display_chevron)
1041    return ToolbarView::kStandardSpacing;
1042  int icons_size =
1043      (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
1044  int chevron_size = display_chevron ?
1045      (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
1046  return ToolbarView::kStandardSpacing + icons_size + chevron_size +
1047      ToolbarView::kStandardSpacing;
1048}
1049
1050size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
1051  // Check for widths large enough to show the entire icon set.
1052  if (pixels >= IconCountToWidth(-1, false))
1053    return browser_action_views_.size();
1054
1055  // We need to reserve space for the resize area, chevron, and the spacing on
1056  // either side of the chevron.
1057  int available_space = pixels - ToolbarView::kStandardSpacing -
1058      chevron_->GetPreferredSize().width() - kChevronSpacing -
1059      ToolbarView::kStandardSpacing;
1060  // Now we add an extra between-item padding value so the space can be divided
1061  // evenly by (size of icon with padding).
1062  return static_cast<size_t>(
1063      std::max(0, available_space + kItemSpacing) / IconWidth(true));
1064}
1065
1066int BrowserActionsContainer::ContainerMinSize() const {
1067  return ToolbarView::kStandardSpacing + kChevronSpacing +
1068      chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing;
1069}
1070
1071void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
1072    ui::Tween::Type tween_type,
1073    size_t num_visible_icons) {
1074  // Save off the desired number of visible icons.  We do this now instead of at
1075  // the end of the animation so that even if the browser is shut down while
1076  // animating, the right value will be restored on next run.
1077  // NOTE: Don't save the icon count in incognito because there may be fewer
1078  // icons in that mode. The result is that the container in a normal window is
1079  // always at least as wide as in an incognito window.
1080  if (!profile_->IsOffTheRecord())
1081    model_->SetVisibleIconCount(num_visible_icons);
1082
1083  int target_size = IconCountToWidth(num_visible_icons,
1084      num_visible_icons < browser_action_views_.size());
1085  if (!disable_animations_during_testing_) {
1086    // Animate! We have to set the animation_target_size_ after calling Reset(),
1087    // because that could end up calling AnimationEnded which clears the value.
1088    resize_animation_->Reset();
1089    resize_animation_->SetTweenType(tween_type);
1090    animation_target_size_ = target_size;
1091    resize_animation_->Show();
1092  } else {
1093    animation_target_size_ = target_size;
1094    AnimationEnded(resize_animation_.get());
1095  }
1096}
1097
1098bool BrowserActionsContainer::ShouldDisplayBrowserAction(
1099    const Extension* extension) {
1100  // Only display incognito-enabled extensions while in incognito mode.
1101  return (!profile_->IsOffTheRecord() ||
1102          profile_->GetExtensionService()->IsIncognitoEnabled(extension));
1103}
1104