summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/views/browser_actions_container.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/views/browser_actions_container.cc')
-rw-r--r--chrome/browser/ui/views/browser_actions_container.cc1100
1 files changed, 1100 insertions, 0 deletions
diff --git a/chrome/browser/ui/views/browser_actions_container.cc b/chrome/browser/ui/views/browser_actions_container.cc
new file mode 100644
index 0000000..ce7bb4b
--- /dev/null
+++ b/chrome/browser/ui/views/browser_actions_container.cc
@@ -0,0 +1,1100 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/views/browser_actions_container.h"
+
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "app/slide_animation.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/extensions/extension_browser_event_router.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_tabs_module.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/themes/browser_theme_provider.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/view_ids.h"
+#include "chrome/browser/views/detachable_toolbar_view.h"
+#include "chrome/browser/views/extensions/browser_action_drag_data.h"
+#include "chrome/browser/views/extensions/extension_popup.h"
+#include "chrome/browser/views/toolbar_view.h"
+#include "chrome/common/extensions/extension_action.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/pref_names.h"
+#include "gfx/canvas.h"
+#include "gfx/canvas_skia.h"
+#include "grit/app_resources.h"
+#include "grit/generated_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "views/controls/button/menu_button.h"
+#include "views/controls/button/text_button.h"
+#include "views/controls/menu/menu_2.h"
+#include "views/drag_utils.h"
+#include "views/window/window.h"
+
+#include "grit/theme_resources.h"
+
+// Horizontal spacing between most items in the container, as well as after the
+// last item or chevron (if visible).
+static const int kItemSpacing = ToolbarView::kStandardSpacing;
+// Horizontal spacing before the chevron (if visible).
+static const int kChevronSpacing = kItemSpacing - 2;
+
+// static
+bool BrowserActionsContainer::disable_animations_during_testing_ = false;
+
+////////////////////////////////////////////////////////////////////////////////
+// BrowserActionButton
+
+BrowserActionButton::BrowserActionButton(const Extension* extension,
+ BrowserActionsContainer* panel)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ MenuButton(this, std::wstring(), NULL, false)),
+ browser_action_(extension->browser_action()),
+ extension_(extension),
+ ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)),
+ showing_context_menu_(false),
+ panel_(panel) {
+ set_border(NULL);
+ set_alignment(TextButton::ALIGN_CENTER);
+
+ // No UpdateState() here because View hierarchy not setup yet. Our parent
+ // should call UpdateState() after creation.
+
+ registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED,
+ Source<ExtensionAction>(browser_action_));
+}
+
+void BrowserActionButton::Destroy() {
+ if (showing_context_menu_) {
+ context_menu_menu_->CancelMenu();
+ MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+ } else {
+ delete this;
+ }
+}
+
+void BrowserActionButton::ViewHierarchyChanged(
+ bool is_add, View* parent, View* child) {
+ if (is_add && child == this) {
+ // The Browser Action API does not allow the default icon path to be
+ // changed at runtime, so we can load this now and cache it.
+ std::string relative_path = browser_action_->default_icon_path();
+ if (relative_path.empty())
+ return;
+
+ // LoadImage is not guaranteed to be synchronous, so we might see the
+ // callback OnImageLoaded execute immediately. It (through UpdateState)
+ // expects GetParent() to return the owner for this button, so this
+ // function is as early as we can start this request.
+ tracker_.LoadImage(extension_, extension_->GetResource(relative_path),
+ gfx::Size(Extension::kBrowserActionIconMaxSize,
+ Extension::kBrowserActionIconMaxSize),
+ ImageLoadingTracker::DONT_CACHE);
+ }
+
+ MenuButton::ViewHierarchyChanged(is_add, parent, child);
+}
+
+void BrowserActionButton::ButtonPressed(views::Button* sender,
+ const views::Event& event) {
+ panel_->OnBrowserActionExecuted(this, false);
+}
+
+void BrowserActionButton::OnImageLoaded(SkBitmap* image,
+ ExtensionResource resource,
+ int index) {
+ if (image)
+ default_icon_ = *image;
+
+ // Call back to UpdateState() because a more specific icon might have been set
+ // while the load was outstanding.
+ UpdateState();
+}
+
+void BrowserActionButton::UpdateState() {
+ int tab_id = panel_->GetCurrentTabId();
+ if (tab_id < 0)
+ return;
+
+ SkBitmap icon(browser_action()->GetIcon(tab_id));
+ if (icon.isNull())
+ icon = default_icon_;
+ if (!icon.isNull()) {
+ SkPaint paint;
+ paint.setXfermode(SkXfermode::Create(SkXfermode::kSrcOver_Mode));
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+
+ SkBitmap bg;
+ rb.GetBitmapNamed(IDR_BROWSER_ACTION)->copyTo(&bg,
+ SkBitmap::kARGB_8888_Config);
+ SkCanvas bg_canvas(bg);
+ bg_canvas.drawBitmap(icon, SkIntToScalar((bg.width() - icon.width()) / 2),
+ SkIntToScalar((bg.height() - icon.height()) / 2), &paint);
+ SetIcon(bg);
+
+ SkBitmap bg_h;
+ rb.GetBitmapNamed(IDR_BROWSER_ACTION_H)->copyTo(&bg_h,
+ SkBitmap::kARGB_8888_Config);
+ SkCanvas bg_h_canvas(bg_h);
+ bg_h_canvas.drawBitmap(icon,
+ SkIntToScalar((bg_h.width() - icon.width()) / 2),
+ SkIntToScalar((bg_h.height() - icon.height()) / 2), &paint);
+ SetHoverIcon(bg_h);
+
+ SkBitmap bg_p;
+ rb.GetBitmapNamed(IDR_BROWSER_ACTION_P)->copyTo(&bg_p,
+ SkBitmap::kARGB_8888_Config);
+ SkCanvas bg_p_canvas(bg_p);
+ bg_p_canvas.drawBitmap(icon,
+ SkIntToScalar((bg_p.width() - icon.width()) / 2),
+ SkIntToScalar((bg_p.height() - icon.height()) / 2), &paint);
+ SetPushedIcon(bg_p);
+ }
+
+ // If the browser action name is empty, show the extension name instead.
+ std::wstring name = UTF8ToWide(browser_action()->GetTitle(tab_id));
+ if (name.empty())
+ name = UTF8ToWide(extension()->name());
+ SetTooltipText(name);
+ SetAccessibleName(name);
+ GetParent()->SchedulePaint();
+}
+
+void BrowserActionButton::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED);
+ UpdateState();
+ // The browser action may have become visible/hidden so we need to make
+ // sure the state gets updated.
+ panel_->OnBrowserActionVisibilityChanged();
+}
+
+bool BrowserActionButton::IsPopup() {
+ int tab_id = panel_->GetCurrentTabId();
+ return (tab_id < 0) ? false : browser_action_->HasPopup(tab_id);
+}
+
+GURL BrowserActionButton::GetPopupUrl() {
+ int tab_id = panel_->GetCurrentTabId();
+ return (tab_id < 0) ? GURL() : browser_action_->GetPopupUrl(tab_id);
+}
+
+bool BrowserActionButton::Activate() {
+ if (!IsPopup())
+ return true;
+
+ panel_->OnBrowserActionExecuted(this, false);
+
+ // TODO(erikkay): Run a nested modal loop while the mouse is down to
+ // enable menu-like drag-select behavior.
+
+ // The return value of this method is returned via OnMousePressed.
+ // We need to return false here since we're handing off focus to another
+ // widget/view, and true will grab it right back and try to send events
+ // to us.
+ return false;
+}
+
+bool BrowserActionButton::OnMousePressed(const views::MouseEvent& e) {
+ if (!e.IsRightMouseButton()) {
+ return IsPopup() ?
+ MenuButton::OnMousePressed(e) : TextButton::OnMousePressed(e);
+ }
+
+ // Get the top left point of this button in screen coordinates.
+ gfx::Point point = gfx::Point(0, 0);
+ ConvertPointToScreen(this, &point);
+
+ // Make the menu appear below the button.
+ point.Offset(0, height());
+
+ ShowContextMenu(point, true);
+ return false;
+}
+
+void BrowserActionButton::OnMouseReleased(const views::MouseEvent& e,
+ bool canceled) {
+ if (IsPopup() || showing_context_menu_) {
+ // TODO(erikkay) this never actually gets called (probably because of the
+ // loss of focus).
+ MenuButton::OnMouseReleased(e, canceled);
+ } else {
+ TextButton::OnMouseReleased(e, canceled);
+ }
+}
+
+bool BrowserActionButton::OnKeyReleased(const views::KeyEvent& e) {
+ return IsPopup() ?
+ MenuButton::OnKeyReleased(e) : TextButton::OnKeyReleased(e);
+}
+
+void BrowserActionButton::OnMouseExited(const views::MouseEvent& e) {
+ if (IsPopup() || showing_context_menu_)
+ MenuButton::OnMouseExited(e);
+ else
+ TextButton::OnMouseExited(e);
+}
+
+void BrowserActionButton::ShowContextMenu(const gfx::Point& p,
+ bool is_mouse_gesture) {
+ showing_context_menu_ = true;
+ SetButtonPushed();
+
+ // Reconstructs the menu every time because the menu's contents are dynamic.
+ context_menu_contents_ =
+ new ExtensionContextMenuModel(extension(), panel_->browser(), panel_);
+ context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get()));
+ context_menu_menu_->RunContextMenuAt(p);
+
+ SetButtonNotPushed();
+ showing_context_menu_ = false;
+}
+
+void BrowserActionButton::SetButtonPushed() {
+ SetState(views::CustomButton::BS_PUSHED);
+ menu_visible_ = true;
+}
+
+void BrowserActionButton::SetButtonNotPushed() {
+ SetState(views::CustomButton::BS_NORMAL);
+ menu_visible_ = false;
+}
+
+BrowserActionButton::~BrowserActionButton() {
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BrowserActionView
+
+BrowserActionView::BrowserActionView(const Extension* extension,
+ BrowserActionsContainer* panel)
+ : panel_(panel) {
+ button_ = new BrowserActionButton(extension, panel);
+ button_->SetDragController(panel_);
+ AddChildView(button_);
+ button_->UpdateState();
+ SetAccessibleName(
+ l10n_util::GetString(IDS_ACCNAME_EXTENSIONS_BROWSER_ACTION));
+}
+
+BrowserActionView::~BrowserActionView() {
+ RemoveChildView(button_);
+ button_->Destroy();
+}
+
+gfx::Canvas* BrowserActionView::GetIconWithBadge() {
+ int tab_id = panel_->GetCurrentTabId();
+
+ SkBitmap icon = button_->extension()->browser_action()->GetIcon(tab_id);
+ if (icon.isNull())
+ icon = button_->default_icon();
+
+ gfx::Canvas* canvas = new gfx::CanvasSkia(icon.width(), icon.height(), false);
+ canvas->DrawBitmapInt(icon, 0, 0);
+
+ if (tab_id >= 0) {
+ gfx::Rect bounds(icon.width(), icon.height() + ToolbarView::kVertSpacing);
+ button_->extension()->browser_action()->PaintBadge(canvas, bounds, tab_id);
+ }
+
+ return canvas;
+}
+
+AccessibilityTypes::Role BrowserActionView::GetAccessibleRole() {
+ return AccessibilityTypes::ROLE_GROUPING;
+}
+
+void BrowserActionView::Layout() {
+ // We can't rely on button_->GetPreferredSize() here because that's not set
+ // correctly until the first call to
+ // BrowserActionsContainer::RefreshBrowserActionViews(), whereas this can be
+ // called before that when the initial bounds are set (and then not after,
+ // since the bounds don't change). So instead of setting the height from the
+ // button's preferred size, we use IconHeight(), since that's how big the
+ // button should be regardless of what it's displaying.
+ button_->SetBounds(0, ToolbarView::kVertSpacing, width(),
+ BrowserActionsContainer::IconHeight());
+}
+
+void BrowserActionView::PaintChildren(gfx::Canvas* canvas) {
+ View::PaintChildren(canvas);
+ ExtensionAction* action = button()->browser_action();
+ int tab_id = panel_->GetCurrentTabId();
+ if (tab_id >= 0)
+ action->PaintBadge(canvas, gfx::Rect(width(), height()), tab_id);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BrowserActionsContainer
+
+BrowserActionsContainer::BrowserActionsContainer(Browser* browser,
+ View* owner_view)
+ : profile_(browser->profile()),
+ browser_(browser),
+ owner_view_(owner_view),
+ popup_(NULL),
+ popup_button_(NULL),
+ model_(NULL),
+ container_width_(0),
+ chevron_(NULL),
+ overflow_menu_(NULL),
+ suppress_chevron_(false),
+ resize_amount_(0),
+ animation_target_size_(0),
+ drop_indicator_position_(-1),
+ ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(show_menu_task_factory_(this)) {
+ SetID(VIEW_ID_BROWSER_ACTION_TOOLBAR);
+
+ if (profile_->GetExtensionsService()) {
+ model_ = profile_->GetExtensionsService()->toolbar_model();
+ model_->AddObserver(this);
+ }
+
+ resize_animation_.reset(new SlideAnimation(this));
+ resize_area_ = new views::ResizeArea(this);
+ resize_area_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_SEPARATOR));
+ AddChildView(resize_area_);
+
+ chevron_ = new views::MenuButton(NULL, std::wstring(), this, false);
+ chevron_->set_border(NULL);
+ chevron_->EnableCanvasFlippingForRTLUI(true);
+ chevron_->SetAccessibleName(
+ l10n_util::GetString(IDS_ACCNAME_EXTENSIONS_CHEVRON));
+ chevron_->SetVisible(false);
+ AddChildView(chevron_);
+
+ SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_EXTENSIONS));
+}
+
+BrowserActionsContainer::~BrowserActionsContainer() {
+ if (model_)
+ model_->RemoveObserver(this);
+ StopShowFolderDropMenuTimer();
+ HidePopup();
+ DeleteBrowserActionViews();
+}
+
+// Static.
+void BrowserActionsContainer::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterIntegerPref(prefs::kBrowserActionContainerWidth, 0);
+}
+
+void BrowserActionsContainer::Init() {
+ LoadImages();
+
+ // We wait to set the container width until now so that the chevron images
+ // will be loaded. The width calculation needs to know the chevron size.
+ if (model_ &&
+ !profile_->GetPrefs()->HasPrefPath(prefs::kExtensionToolbarSize)) {
+ // Migration code to the new VisibleIconCount pref.
+ // TODO(mpcomplete): remove this after users are upgraded to 5.0.
+ int predefined_width =
+ profile_->GetPrefs()->GetInteger(prefs::kBrowserActionContainerWidth);
+ if (predefined_width != 0)
+ model_->SetVisibleIconCount(WidthToIconCount(predefined_width));
+ }
+ if (model_ && model_->extensions_initialized())
+ SetContainerWidth();
+}
+
+int BrowserActionsContainer::GetCurrentTabId() const {
+ TabContents* tab_contents = browser_->GetSelectedTabContents();
+ return tab_contents ? tab_contents->controller().session_id().id() : -1;
+}
+
+BrowserActionView* BrowserActionsContainer::GetBrowserActionView(
+ ExtensionAction* action) {
+ for (BrowserActionViews::iterator iter = browser_action_views_.begin();
+ iter != browser_action_views_.end(); ++iter) {
+ if ((*iter)->button()->browser_action() == action)
+ return *iter;
+ }
+ return NULL;
+}
+
+void BrowserActionsContainer::RefreshBrowserActionViews() {
+ for (size_t i = 0; i < browser_action_views_.size(); ++i)
+ browser_action_views_[i]->button()->UpdateState();
+}
+
+void BrowserActionsContainer::CreateBrowserActionViews() {
+ DCHECK(browser_action_views_.empty());
+ if (!model_)
+ return;
+
+ for (ExtensionList::iterator iter = model_->begin(); iter != model_->end();
+ ++iter) {
+ if (!ShouldDisplayBrowserAction(*iter))
+ continue;
+
+ BrowserActionView* view = new BrowserActionView(*iter, this);
+ browser_action_views_.push_back(view);
+ AddChildView(view);
+ }
+}
+
+void BrowserActionsContainer::DeleteBrowserActionViews() {
+ if (!browser_action_views_.empty()) {
+ for (size_t i = 0; i < browser_action_views_.size(); ++i)
+ RemoveChildView(browser_action_views_[i]);
+ STLDeleteContainerPointers(browser_action_views_.begin(),
+ browser_action_views_.end());
+ browser_action_views_.clear();
+ }
+}
+
+void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
+ SetVisible(!browser_action_views_.empty());
+ owner_view_->Layout();
+ owner_view_->SchedulePaint();
+}
+
+size_t BrowserActionsContainer::VisibleBrowserActions() const {
+ size_t visible_actions = 0;
+ for (size_t i = 0; i < browser_action_views_.size(); ++i) {
+ if (browser_action_views_[i]->IsVisible())
+ ++visible_actions;
+ }
+ return visible_actions;
+}
+
+void BrowserActionsContainer::OnBrowserActionExecuted(
+ BrowserActionButton* button,
+ bool inspect_with_devtools) {
+ ExtensionAction* browser_action = button->browser_action();
+
+ // Popups just display. No notification to the extension.
+ // TODO(erikkay): should there be?
+ if (!button->IsPopup()) {
+ ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted(
+ profile_, browser_action->extension_id(), browser_);
+ return;
+ }
+
+ // If we're showing the same popup, just hide it and return.
+ bool same_showing = popup_ && button == popup_button_;
+
+ // Always hide the current popup, even if it's not the same.
+ // Only one popup should be visible at a time.
+ HidePopup();
+
+ if (same_showing)
+ return;
+
+ // We can get the execute event for browser actions that are not visible,
+ // since buttons can be activated from the overflow menu (chevron). In that
+ // case we show the popup as originating from the chevron.
+ View* reference_view = button->GetParent()->IsVisible() ? button : chevron_;
+ gfx::Point origin;
+ View::ConvertPointToScreen(reference_view, &origin);
+ gfx::Rect rect = reference_view->bounds();
+ rect.set_origin(origin);
+
+ gfx::NativeWindow frame_window = browser_->window()->GetNativeHandle();
+ BubbleBorder::ArrowLocation arrow_location = base::i18n::IsRTL() ?
+ BubbleBorder::TOP_LEFT : BubbleBorder::TOP_RIGHT;
+
+ popup_ = ExtensionPopup::Show(button->GetPopupUrl(), browser_,
+ browser_->profile(), frame_window, rect, arrow_location, true,
+ inspect_with_devtools, ExtensionPopup::BUBBLE_CHROME, this);
+ popup_button_ = button;
+ popup_button_->SetButtonPushed();
+}
+
+gfx::Size BrowserActionsContainer::GetPreferredSize() {
+ if (browser_action_views_.empty())
+ return gfx::Size(ToolbarView::kStandardSpacing, 0);
+
+ // We calculate the size of the view by taking the current width and
+ // subtracting resize_amount_ (the latter represents how far the user is
+ // resizing the view or, if animating the snapping, how far to animate it).
+ // But we also clamp it to a minimum size and the maximum size, so that the
+ // container can never shrink too far or take up more space than it needs. In
+ // other words: ContainerMinSize() < width() - resize < ClampTo(MAX).
+ int clamped_width = std::min(
+ std::max(ContainerMinSize(), container_width_ - resize_amount_),
+ IconCountToWidth(-1, false));
+ return gfx::Size(clamped_width, 0);
+}
+
+void BrowserActionsContainer::Layout() {
+ if (browser_action_views_.empty()) {
+ SetVisible(false);
+ return;
+ }
+
+ SetVisible(true);
+ resize_area_->SetBounds(0, ToolbarView::kVertSpacing, kItemSpacing,
+ IconHeight());
+
+ // If the icons don't all fit, show the chevron (unless suppressed).
+ int max_x = GetPreferredSize().width();
+ if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_) {
+ chevron_->SetVisible(true);
+ gfx::Size chevron_size(chevron_->GetPreferredSize());
+ max_x -=
+ ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
+ chevron_->SetBounds(
+ width() - ToolbarView::kStandardSpacing - chevron_size.width(),
+ ToolbarView::kVertSpacing, chevron_size.width(), chevron_size.height());
+ } else {
+ chevron_->SetVisible(false);
+ }
+
+ // Now draw the icons for the browser actions in the available space.
+ int icon_width = IconWidth(false);
+ for (size_t i = 0; i < browser_action_views_.size(); ++i) {
+ BrowserActionView* view = browser_action_views_[i];
+ int x = ToolbarView::kStandardSpacing + (i * IconWidth(true));
+ if (x + icon_width <= max_x) {
+ view->SetBounds(x, 0, icon_width, height());
+ view->SetVisible(true);
+ } else {
+ view->SetVisible(false);
+ }
+ }
+}
+
+void BrowserActionsContainer::Paint(gfx::Canvas* canvas) {
+ // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
+ // dragging (like we do for tab dragging).
+ if (drop_indicator_position_ > -1) {
+ // The two-pixel width drop indicator.
+ static const int kDropIndicatorWidth = 2;
+ gfx::Rect indicator_bounds(
+ drop_indicator_position_ - (kDropIndicatorWidth / 2),
+ ToolbarView::kVertSpacing, kDropIndicatorWidth, IconHeight());
+
+ // Color of the drop indicator.
+ static const SkColor kDropIndicatorColor = SK_ColorBLACK;
+ canvas->FillRectInt(kDropIndicatorColor, indicator_bounds.x(),
+ indicator_bounds.y(), indicator_bounds.width(),
+ indicator_bounds.height());
+ }
+}
+
+void BrowserActionsContainer::ViewHierarchyChanged(bool is_add,
+ views::View* parent,
+ views::View* child) {
+ // No extensions (e.g., incognito).
+ if (!model_)
+ return;
+
+ if (is_add && child == this) {
+ // Initial toolbar button creation and placement in the widget hierarchy.
+ // We do this here instead of in the constructor because AddBrowserAction
+ // calls Layout on the Toolbar, which needs this object to be constructed
+ // before its Layout function is called.
+ CreateBrowserActionViews();
+ }
+}
+
+bool BrowserActionsContainer::GetDropFormats(
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) {
+ custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
+
+ return true;
+}
+
+bool BrowserActionsContainer::AreDropTypesRequired() {
+ return true;
+}
+
+bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
+ BrowserActionDragData drop_data;
+ return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false;
+}
+
+void BrowserActionsContainer::OnDragEntered(
+ const views::DropTargetEvent& event) {
+}
+
+int BrowserActionsContainer::OnDragUpdated(
+ const views::DropTargetEvent& event) {
+ // First check if we are above the chevron (overflow) menu.
+ if (GetViewForPoint(event.location()) == chevron_) {
+ if (show_menu_task_factory_.empty() && !overflow_menu_)
+ StartShowFolderDropMenuTimer();
+ return DragDropTypes::DRAG_MOVE;
+ }
+ StopShowFolderDropMenuTimer();
+
+ // Figure out where to display the indicator. This is a complex calculation:
+
+ // First, we figure out how much space is to the left of the icon area, so we
+ // can calculate the true offset into the icon area.
+ int width_before_icons = ToolbarView::kStandardSpacing +
+ (base::i18n::IsRTL() ?
+ (chevron_->GetPreferredSize().width() + kChevronSpacing) : 0);
+ int offset_into_icon_area = event.x() - width_before_icons;
+
+ // Next, we determine which icon to place the indicator in front of. We want
+ // to place the indicator in front of icon n when the cursor is between the
+ // midpoints of icons (n - 1) and n. To do this we take the offset into the
+ // icon area and transform it as follows:
+ //
+ // Real icon area:
+ // 0 a * b c
+ // | | | |
+ // |[IC|ON] [IC|ON] [IC|ON]
+ // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
+ // Here the "*" represents the offset into the icon area, and since it's
+ // between a and b, we want to return "1".
+ //
+ // Transformed "icon area":
+ // 0 a * b c
+ // | | | |
+ // |[ICON] |[ICON] |[ICON] |
+ // If we shift both our offset and our divider points later by half an icon
+ // plus one spacing unit, then it becomes very easy to calculate how many
+ // divider points we've passed, because they're the multiples of "one icon
+ // plus padding".
+ int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) +
+ kItemSpacing) / IconWidth(true);
+
+ // Because the user can drag outside the container bounds, we need to clamp to
+ // the valid range. Note that the maximum allowable value is (num icons), not
+ // (num icons - 1), because we represent the indicator being past the last
+ // icon as being "before the (last + 1) icon".
+ int before_icon = std::min(std::max(before_icon_unclamped, 0),
+ static_cast<int>(VisibleBrowserActions()));
+
+ // Now we convert back to a pixel offset into the container. We want to place
+ // the center of the drop indicator at the midpoint of the space before our
+ // chosen icon.
+ SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) -
+ (kItemSpacing / 2));
+
+ return DragDropTypes::DRAG_MOVE;
+}
+
+void BrowserActionsContainer::OnDragExited() {
+ StopShowFolderDropMenuTimer();
+ drop_indicator_position_ = -1;
+ SchedulePaint();
+}
+
+int BrowserActionsContainer::OnPerformDrop(
+ const views::DropTargetEvent& event) {
+ BrowserActionDragData data;
+ if (!data.Read(event.GetData()))
+ return DragDropTypes::DRAG_NONE;
+
+ // Make sure we have the same view as we started with.
+ DCHECK_EQ(browser_action_views_[data.index()]->button()->extension()->id(),
+ data.id());
+ DCHECK(model_);
+
+ size_t i = 0;
+ for (; i < browser_action_views_.size(); ++i) {
+ int view_x =
+ browser_action_views_[i]->GetBounds(APPLY_MIRRORING_TRANSFORMATION).x();
+ if (!browser_action_views_[i]->IsVisible() ||
+ (base::i18n::IsRTL() ? (view_x < drop_indicator_position_) :
+ (view_x >= drop_indicator_position_))) {
+ // We have reached the end of the visible icons or found one that has a
+ // higher x position than the drop point.
+ break;
+ }
+ }
+
+ // |i| now points to the item to the right of the drop indicator*, which is
+ // correct when dragging an icon to the left. When dragging to the right,
+ // however, we want the icon being dragged to get the index of the item to
+ // the left of the drop indicator, so we subtract one.
+ // * Well, it can also point to the end, but not when dragging to the left. :)
+ if (i > data.index())
+ --i;
+
+ if (profile_->IsOffTheRecord())
+ i = model_->IncognitoIndexToOriginal(i);
+
+ model_->MoveBrowserAction(
+ browser_action_views_[data.index()]->button()->extension(), i);
+
+ OnDragExited(); // Perform clean up after dragging.
+ return DragDropTypes::DRAG_MOVE;
+}
+
+void BrowserActionsContainer::OnThemeChanged() {
+ LoadImages();
+}
+
+AccessibilityTypes::Role BrowserActionsContainer::GetAccessibleRole() {
+ return AccessibilityTypes::ROLE_GROUPING;
+}
+
+void BrowserActionsContainer::RunMenu(View* source, const gfx::Point& pt) {
+ if (source == chevron_) {
+ overflow_menu_ = new BrowserActionOverflowMenuController(
+ this, chevron_, browser_action_views_, VisibleBrowserActions());
+ overflow_menu_->set_observer(this);
+ overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), false);
+ }
+}
+
+void BrowserActionsContainer::WriteDragData(View* sender,
+ const gfx::Point& press_pt,
+ OSExchangeData* data) {
+ DCHECK(data);
+
+ for (size_t i = 0; i < browser_action_views_.size(); ++i) {
+ BrowserActionButton* button = browser_action_views_[i]->button();
+ if (button == sender) {
+ // Set the dragging image for the icon.
+ scoped_ptr<gfx::Canvas> canvas(
+ browser_action_views_[i]->GetIconWithBadge());
+ drag_utils::SetDragImageOnDataObject(*canvas, button->size(), press_pt,
+ data);
+
+ // Fill in the remaining info.
+ BrowserActionDragData drag_data(
+ browser_action_views_[i]->button()->extension()->id(), i);
+ drag_data.Write(profile_, data);
+ break;
+ }
+ }
+}
+
+int BrowserActionsContainer::GetDragOperations(View* sender,
+ const gfx::Point& p) {
+ return DragDropTypes::DRAG_MOVE;
+}
+
+bool BrowserActionsContainer::CanStartDrag(View* sender,
+ const gfx::Point& press_pt,
+ const gfx::Point& p) {
+ return true;
+}
+
+void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
+ if (!done_resizing) {
+ resize_amount_ = resize_amount;
+ OnBrowserActionVisibilityChanged();
+ return;
+ }
+
+ // Up until now we've only been modifying the resize_amount, but now it is
+ // time to set the container size to the size we have resized to, and then
+ // animate to the nearest icon count size if necessary (which may be 0).
+ int max_width = IconCountToWidth(-1, false);
+ container_width_ =
+ std::min(std::max(0, container_width_ - resize_amount), max_width);
+ SaveDesiredSizeAndAnimate(Tween::EASE_OUT,
+ WidthToIconCount(container_width_));
+}
+
+void BrowserActionsContainer::AnimationProgressed(const Animation* animation) {
+ DCHECK_EQ(resize_animation_.get(), animation);
+ resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
+ (container_width_ - animation_target_size_));
+ OnBrowserActionVisibilityChanged();
+}
+
+void BrowserActionsContainer::AnimationEnded(const Animation* animation) {
+ container_width_ = animation_target_size_;
+ animation_target_size_ = 0;
+ resize_amount_ = 0;
+ OnBrowserActionVisibilityChanged();
+ suppress_chevron_ = false;
+}
+
+void BrowserActionsContainer::NotifyMenuDeleted(
+ BrowserActionOverflowMenuController* controller) {
+ DCHECK(controller == overflow_menu_);
+ overflow_menu_ = NULL;
+}
+
+void BrowserActionsContainer::InspectPopup(ExtensionAction* action) {
+ OnBrowserActionExecuted(GetBrowserActionView(action)->button(), true);
+}
+
+void BrowserActionsContainer::ExtensionPopupIsClosing(ExtensionPopup* popup) {
+ // ExtensionPopup is ref-counted, so we don't need to delete it.
+ DCHECK_EQ(popup_, popup);
+ popup_ = NULL;
+ popup_button_->SetButtonNotPushed();
+ popup_button_ = NULL;
+}
+
+void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
+ size_t new_index) {
+ ExtensionsService* service = profile_->GetExtensionsService();
+ if (service) {
+ const Extension* extension = service->GetExtensionById(extension_id, false);
+ model_->MoveBrowserAction(extension, new_index);
+ SchedulePaint();
+ }
+}
+
+void BrowserActionsContainer::HidePopup() {
+ if (popup_)
+ popup_->Close();
+}
+
+void BrowserActionsContainer::TestExecuteBrowserAction(int index) {
+ BrowserActionButton* button = browser_action_views_[index]->button();
+ OnBrowserActionExecuted(button, false);
+}
+
+void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
+ model_->SetVisibleIconCount(icons);
+ chevron_->SetVisible(icons < browser_action_views_.size());
+ container_width_ = IconCountToWidth(icons, chevron_->IsVisible());
+ Layout();
+ SchedulePaint();
+}
+
+// static
+int BrowserActionsContainer::IconWidth(bool include_padding) {
+ static bool initialized = false;
+ static int icon_width = 0;
+ if (!initialized) {
+ initialized = true;
+ icon_width = ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ IDR_BROWSER_ACTION)->width();
+ }
+ return icon_width + (include_padding ? kItemSpacing : 0);
+}
+
+// static
+int BrowserActionsContainer::IconHeight() {
+ static bool initialized = false;
+ static int icon_height = 0;
+ if (!initialized) {
+ initialized = true;
+ icon_height = ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ IDR_BROWSER_ACTION)->height();
+ }
+ return icon_height;
+}
+
+void BrowserActionsContainer::BrowserActionAdded(const Extension* extension,
+ int index) {
+#if defined(DEBUG)
+ for (size_t i = 0; i < browser_action_views_.size(); ++i) {
+ DCHECK(browser_action_views_[i]->button()->extension() != extension) <<
+ "Asked to add a browser action view for an extension that already "
+ "exists.";
+ }
+#endif
+ CloseOverflowMenu();
+
+ if (!ShouldDisplayBrowserAction(extension))
+ return;
+
+ size_t visible_actions = VisibleBrowserActions();
+
+ // Add the new browser action to the vector and the view hierarchy.
+ if (profile_->IsOffTheRecord())
+ index = model_->OriginalIndexToIncognito(index);
+ BrowserActionView* view = new BrowserActionView(extension, this);
+ browser_action_views_.insert(browser_action_views_.begin() + index, view);
+ AddChildView(index, view);
+
+ // If we are still initializing the container, don't bother animating.
+ if (!model_->extensions_initialized())
+ return;
+
+ // Enlarge the container if it was already at maximum size and we're not in
+ // the middle of upgrading.
+ if ((model_->GetVisibleIconCount() < 0) &&
+ !profile_->GetExtensionsService()->IsBeingUpgraded(extension)) {
+ suppress_chevron_ = true;
+ SaveDesiredSizeAndAnimate(Tween::LINEAR, visible_actions + 1);
+ } else {
+ // Just redraw the (possibly modified) visible icon set.
+ OnBrowserActionVisibilityChanged();
+ }
+}
+
+void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) {
+ CloseOverflowMenu();
+
+ if (popup_ && popup_->host()->extension() == extension)
+ HidePopup();
+
+ size_t visible_actions = VisibleBrowserActions();
+ for (BrowserActionViews::iterator iter = browser_action_views_.begin();
+ iter != browser_action_views_.end(); ++iter) {
+ if ((*iter)->button()->extension() == extension) {
+ RemoveChildView(*iter);
+ delete *iter;
+ browser_action_views_.erase(iter);
+
+ // If the extension is being upgraded we don't want the bar to shrink
+ // because the icon is just going to get re-added to the same location.
+ if (profile_->GetExtensionsService()->IsBeingUpgraded(extension))
+ return;
+
+ if (browser_action_views_.size() > visible_actions) {
+ // If we have more icons than we can show, then we must not be changing
+ // the container size (since we either removed an icon from the main
+ // area and one from the overflow list will have shifted in, or we
+ // removed an entry directly from the overflow list).
+ OnBrowserActionVisibilityChanged();
+ } else {
+ // Either we went from overflow to no-overflow, or we shrunk the no-
+ // overflow container by 1. Either way the size changed, so animate.
+ chevron_->SetVisible(false);
+ SaveDesiredSizeAndAnimate(Tween::EASE_OUT,
+ browser_action_views_.size());
+ }
+ return;
+ }
+ }
+}
+
+void BrowserActionsContainer::BrowserActionMoved(const Extension* extension,
+ int index) {
+ if (!ShouldDisplayBrowserAction(extension))
+ return;
+
+ if (profile_->IsOffTheRecord())
+ index = model_->OriginalIndexToIncognito(index);
+
+ DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
+
+ DeleteBrowserActionViews();
+ CreateBrowserActionViews();
+ Layout();
+ SchedulePaint();
+}
+
+void BrowserActionsContainer::ModelLoaded() {
+ SetContainerWidth();
+}
+
+void BrowserActionsContainer::LoadImages() {
+ ThemeProvider* tp = GetThemeProvider();
+ chevron_->SetIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
+ chevron_->SetHoverIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_H));
+ chevron_->SetPushedIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_P));
+}
+
+void BrowserActionsContainer::SetContainerWidth() {
+ int visible_actions = model_->GetVisibleIconCount();
+ if (visible_actions < 0) // All icons should be visible.
+ visible_actions = model_->size();
+ chevron_->SetVisible(static_cast<size_t>(visible_actions) < model_->size());
+ container_width_ = IconCountToWidth(visible_actions, chevron_->IsVisible());
+}
+
+void BrowserActionsContainer::CloseOverflowMenu() {
+ if (overflow_menu_)
+ overflow_menu_->CancelMenu();
+}
+
+void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
+ show_menu_task_factory_.RevokeAll();
+}
+
+void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
+ int delay = View::GetMenuShowDelay();
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ show_menu_task_factory_.NewRunnableMethod(
+ &BrowserActionsContainer::ShowDropFolder),
+ delay);
+}
+
+void BrowserActionsContainer::ShowDropFolder() {
+ DCHECK(!overflow_menu_);
+ SetDropIndicator(-1);
+ overflow_menu_ = new BrowserActionOverflowMenuController(
+ this, chevron_, browser_action_views_, VisibleBrowserActions());
+ overflow_menu_->set_observer(this);
+ overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), true);
+}
+
+void BrowserActionsContainer::SetDropIndicator(int x_pos) {
+ if (drop_indicator_position_ != x_pos) {
+ drop_indicator_position_ = x_pos;
+ SchedulePaint();
+ }
+}
+
+int BrowserActionsContainer::IconCountToWidth(int icons,
+ bool display_chevron) const {
+ if (icons < 0)
+ icons = browser_action_views_.size();
+ if ((icons == 0) && !display_chevron)
+ return ToolbarView::kStandardSpacing;
+ int icons_size =
+ (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
+ int chevron_size = display_chevron ?
+ (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
+ return ToolbarView::kStandardSpacing + icons_size + chevron_size +
+ ToolbarView::kStandardSpacing;
+}
+
+size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
+ // Check for widths large enough to show the entire icon set.
+ if (pixels >= IconCountToWidth(-1, false))
+ return browser_action_views_.size();
+
+ // We need to reserve space for the resize area, chevron, and the spacing on
+ // either side of the chevron.
+ int available_space = pixels - ToolbarView::kStandardSpacing -
+ chevron_->GetPreferredSize().width() - kChevronSpacing -
+ ToolbarView::kStandardSpacing;
+ // Now we add an extra between-item padding value so the space can be divided
+ // evenly by (size of icon with padding).
+ return static_cast<size_t>(
+ std::max(0, available_space + kItemSpacing) / IconWidth(true));
+}
+
+int BrowserActionsContainer::ContainerMinSize() const {
+ return ToolbarView::kStandardSpacing + kChevronSpacing +
+ chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing;
+}
+
+void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
+ Tween::Type tween_type,
+ size_t num_visible_icons) {
+ // Save off the desired number of visible icons. We do this now instead of at
+ // the end of the animation so that even if the browser is shut down while
+ // animating, the right value will be restored on next run.
+ // NOTE: Don't save the icon count in incognito because there may be fewer
+ // icons in that mode. The result is that the container in a normal window is
+ // always at least as wide as in an incognito window.
+ if (!profile_->IsOffTheRecord())
+ model_->SetVisibleIconCount(num_visible_icons);
+
+ int target_size = IconCountToWidth(num_visible_icons,
+ num_visible_icons < browser_action_views_.size());
+ if (!disable_animations_during_testing_) {
+ // Animate! We have to set the animation_target_size_ after calling Reset(),
+ // because that could end up calling AnimationEnded which clears the value.
+ resize_animation_->Reset();
+ resize_animation_->SetTweenType(tween_type);
+ animation_target_size_ = target_size;
+ resize_animation_->Show();
+ } else {
+ animation_target_size_ = target_size;
+ AnimationEnded(resize_animation_.get());
+ }
+}
+
+bool BrowserActionsContainer::ShouldDisplayBrowserAction(
+ const Extension* extension) {
+ // Only display incognito-enabled extensions while in incognito mode.
+ return (!profile_->IsOffTheRecord() ||
+ profile_->GetExtensionsService()->IsIncognitoEnabled(extension));
+}