// Copyright (c) 2011 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/ui/views/infobars/extension_infobar.h" #include "chrome/browser/extensions/extension_context_menu_model.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_infobar_delegate.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/ui/views/infobars/infobar_background.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_icon_set.h" #include "chrome/common/extensions/extension_resource.h" #include "grit/theme_resources.h" #include "ui/base/animation/slide_animation.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas_skia.h" #include "views/controls/button/menu_button.h" #include "views/controls/menu/menu_2.h" #include "views/widget/widget.h" // ExtensionInfoBarDelegate --------------------------------------------------- InfoBar* ExtensionInfoBarDelegate::CreateInfoBar() { return new ExtensionInfoBar(this); } // ExtensionInfoBar ----------------------------------------------------------- namespace { // The horizontal margin between the menu and the Extension (HTML) view. static const int kMenuHorizontalMargin = 1; }; ExtensionInfoBar::ExtensionInfoBar(ExtensionInfoBarDelegate* delegate) : InfoBarView(delegate), delegate_(delegate), menu_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) { delegate->set_observer(this); ExtensionView* extension_view = delegate->extension_host()->view(); int height = extension_view->GetPreferredSize().height(); SetBarTargetHeight((height > 0) ? (height + kSeparatorLineHeight) : 0); // Get notified of resize events for the ExtensionView. extension_view->SetContainer(this); } ExtensionInfoBar::~ExtensionInfoBar() { if (GetDelegate()) { GetDelegate()->extension_host()->view()->SetContainer(NULL); GetDelegate()->set_observer(NULL); } } void ExtensionInfoBar::Layout() { InfoBarView::Layout(); gfx::Size menu_size = menu_->GetPreferredSize(); menu_->SetBounds(StartX(), OffsetY(menu_size), menu_size.width(), menu_size.height()); GetDelegate()->extension_host()->view()->SetBounds( menu_->bounds().right() + kMenuHorizontalMargin, 0, std::max(0, EndX() - StartX() - ContentMinimumWidth()), height()); } void ExtensionInfoBar::ViewHierarchyChanged(bool is_add, View* parent, View* child) { if (!is_add || (child != this) || (menu_ != NULL)) { InfoBarView::ViewHierarchyChanged(is_add, parent, child); return; } menu_ = new views::MenuButton(NULL, std::wstring(), this, false); menu_->SetVisible(false); AddChildView(menu_); ExtensionHost* extension_host = GetDelegate()->extension_host(); AddChildView(extension_host->view()); // This must happen after adding all other children so InfoBarView can ensure // the close button is the last child. InfoBarView::ViewHierarchyChanged(is_add, parent, child); // This must happen after adding all children because it can trigger layout, // which assumes that particular children (e.g. the close button) have already // been added. const Extension* extension = extension_host->extension(); int image_size = Extension::EXTENSION_ICON_BITTY; ExtensionResource icon_resource = extension->GetIconResource( image_size, ExtensionIconSet::MATCH_EXACTLY); if (!icon_resource.relative_path().empty()) { tracker_.LoadImage(extension, icon_resource, gfx::Size(image_size, image_size), ImageLoadingTracker::DONT_CACHE); } else { OnImageLoaded(NULL, icon_resource, 0); } } int ExtensionInfoBar::ContentMinimumWidth() const { return menu_->GetPreferredSize().width() + kMenuHorizontalMargin; } void ExtensionInfoBar::OnExtensionMouseMove(ExtensionView* view) { } void ExtensionInfoBar::OnExtensionMouseLeave(ExtensionView* view) { } void ExtensionInfoBar::OnExtensionPreferredSizeChanged(ExtensionView* view) { ExtensionInfoBarDelegate* delegate = GetDelegate(); DCHECK_EQ(delegate->extension_host()->view(), view); // When the infobar is closed, it animates to 0 vertical height. We'll // continue to get size changed notifications from the ExtensionView, but we // need to ignore them otherwise we'll try to re-animate open (and leak the // infobar view). if (delegate->closing()) return; view->SetVisible(true); if (height() == 0) animation()->Reset(0.0); // Clamp height to a min and a max size of between 1 and 2 InfoBars. SetBarTargetHeight(std::min(2 * kDefaultBarTargetHeight, std::max(kDefaultBarTargetHeight, view->GetPreferredSize().height()))); animation()->Show(); } void ExtensionInfoBar::OnImageLoaded(SkBitmap* image, const ExtensionResource& resource, int index) { if (!GetDelegate()) return; // The delegate can go away while we asynchronously load images. SkBitmap* icon = image; // Fall back on the default extension icon on failure. ResourceBundle& rb = ResourceBundle::GetSharedInstance(); if (!image || image->empty()) icon = rb.GetBitmapNamed(IDR_EXTENSIONS_SECTION); SkBitmap* drop_image = rb.GetBitmapNamed(IDR_APP_DROPARROW); int image_size = Extension::EXTENSION_ICON_BITTY; // The margin between the extension icon and the drop-down arrow bitmap. static const int kDropArrowLeftMargin = 3; scoped_ptr<gfx::CanvasSkia> canvas(new gfx::CanvasSkia( image_size + kDropArrowLeftMargin + drop_image->width(), image_size, false)); canvas->DrawBitmapInt(*icon, 0, 0, icon->width(), icon->height(), 0, 0, image_size, image_size, false); canvas->DrawBitmapInt(*drop_image, image_size + kDropArrowLeftMargin, image_size / 2); menu_->SetIcon(canvas->ExtractBitmap()); menu_->SetVisible(true); Layout(); } void ExtensionInfoBar::OnDelegateDeleted() { GetDelegate()->extension_host()->view()->SetContainer(NULL); delegate_ = NULL; } void ExtensionInfoBar::RunMenu(View* source, const gfx::Point& pt) { const Extension* extension = GetDelegate()->extension_host()->extension(); if (!extension->ShowConfigureContextMenus()) return; if (!options_menu_contents_.get()) { Browser* browser = BrowserView::GetBrowserViewForNativeWindow( platform_util::GetTopLevel(source->GetWidget()->GetNativeView()))-> browser(); options_menu_contents_ = new ExtensionContextMenuModel(extension, browser, NULL); } options_menu_menu_.reset(new views::Menu2(options_menu_contents_.get())); options_menu_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPLEFT); } ExtensionInfoBarDelegate* ExtensionInfoBar::GetDelegate() { return delegate_ ? delegate_->AsExtensionInfoBarDelegate() : NULL; }