diff options
Diffstat (limited to 'chrome/browser/ui/views/browser_actions_container.cc')
-rw-r--r-- | chrome/browser/ui/views/browser_actions_container.cc | 1100 |
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)); +} |