1// Copyright (c) 2012 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/ui/views/infobars/extension_infobar.h"
6
7#include "chrome/browser/extensions/extension_context_menu_model.h"
8#include "chrome/browser/extensions/extension_infobar_delegate.h"
9#include "chrome/browser/extensions/extension_view_host.h"
10#include "chrome/browser/platform_util.h"
11#include "chrome/browser/ui/views/extensions/extension_view_views.h"
12#include "chrome/browser/ui/views/frame/browser_view.h"
13#include "extensions/browser/image_loader.h"
14#include "extensions/common/constants.h"
15#include "extensions/common/extension.h"
16#include "extensions/common/extension_icon_set.h"
17#include "extensions/common/extension_resource.h"
18#include "extensions/common/manifest_handlers/icons_handler.h"
19#include "grit/theme_resources.h"
20#include "ui/base/resource/resource_bundle.h"
21#include "ui/gfx/animation/slide_animation.h"
22#include "ui/gfx/canvas.h"
23#include "ui/gfx/image/canvas_image_source.h"
24#include "ui/gfx/image/image.h"
25#include "ui/views/controls/button/menu_button.h"
26#include "ui/views/controls/image_view.h"
27#include "ui/views/widget/widget.h"
28
29
30// ExtensionInfoBarDelegate ----------------------------------------------------
31
32// static
33scoped_ptr<infobars::InfoBar> ExtensionInfoBarDelegate::CreateInfoBar(
34    scoped_ptr<ExtensionInfoBarDelegate> delegate) {
35  Browser* browser = delegate->browser_;
36  return scoped_ptr<infobars::InfoBar>(
37      new ExtensionInfoBar(delegate.Pass(), browser));
38}
39
40
41// ExtensionInfoBar ------------------------------------------------------------
42
43namespace {
44// The horizontal margin between the infobar icon and the Extension (HTML) view.
45const int kIconHorizontalMargin = 1;
46
47class MenuImageSource: public gfx::CanvasImageSource {
48 public:
49  MenuImageSource(const gfx::ImageSkia& icon, const gfx::ImageSkia& drop_image)
50      : gfx::CanvasImageSource(ComputeSize(drop_image), false),
51        icon_(icon),
52        drop_image_(drop_image) {
53  }
54
55  virtual ~MenuImageSource() {
56  }
57
58  // Overridden from gfx::CanvasImageSource
59  virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
60    int image_size = extension_misc::EXTENSION_ICON_BITTY;
61    canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0,
62                         image_size, image_size, false);
63    canvas->DrawImageInt(drop_image_, image_size + kDropArrowLeftMargin,
64                         image_size / 2);
65  }
66
67 private:
68  gfx::Size ComputeSize(const gfx::ImageSkia& drop_image) const {
69    int image_size = extension_misc::EXTENSION_ICON_BITTY;
70    return gfx::Size(image_size + kDropArrowLeftMargin + drop_image.width(),
71                     image_size);
72  }
73
74  // The margin between the extension icon and the drop-down arrow image.
75  static const int kDropArrowLeftMargin = 3;
76
77  const gfx::ImageSkia icon_;
78  const gfx::ImageSkia drop_image_;
79
80  DISALLOW_COPY_AND_ASSIGN(MenuImageSource);
81};
82
83}  // namespace
84
85ExtensionInfoBar::ExtensionInfoBar(
86    scoped_ptr<ExtensionInfoBarDelegate> delegate,
87    Browser* browser)
88    : InfoBarView(delegate.PassAs<infobars::InfoBarDelegate>()),
89      browser_(browser),
90      infobar_icon_(NULL),
91      icon_as_menu_(NULL),
92      icon_as_image_(NULL),
93      weak_ptr_factory_(this) {
94}
95
96ExtensionInfoBar::~ExtensionInfoBar() {
97}
98
99void ExtensionInfoBar::Layout() {
100  InfoBarView::Layout();
101
102  infobar_icon_->SetPosition(gfx::Point(StartX(), OffsetY(infobar_icon_)));
103  ExtensionViewViews* extension_view = GetExtensionView();
104  // TODO(pkasting): We'd like to simply set the extension view's desired height
105  // at creation time and position using OffsetY() like for other infobar items,
106  // but the NativeViewHost inside does not seem to be clipped by the ClipRect()
107  // call in InfoBarView::PaintChildren(), so we have to manually clamp the size
108  // here.
109  extension_view->SetSize(
110      gfx::Size(std::max(0, EndX() - StartX() - NonExtensionViewWidth()),
111                std::min(height() - kSeparatorLineHeight - arrow_height(),
112                         GetDelegate()->height())));
113  // We do SetPosition() separately after SetSize() so OffsetY() will work.
114  extension_view->SetPosition(
115      gfx::Point(infobar_icon_->bounds().right() + kIconHorizontalMargin,
116                 std::max(arrow_height(), OffsetY(extension_view))));
117}
118
119void ExtensionInfoBar::ViewHierarchyChanged(
120    const ViewHierarchyChangedDetails& details) {
121  if (!details.is_add || (details.child != this) || (infobar_icon_ != NULL)) {
122    InfoBarView::ViewHierarchyChanged(details);
123    return;
124  }
125
126  extensions::ExtensionViewHost* extension_view_host =
127      GetDelegate()->extension_view_host();
128
129  if (extension_view_host->extension()->ShowConfigureContextMenus()) {
130    icon_as_menu_ = new views::MenuButton(NULL, base::string16(), this, false);
131    icon_as_menu_->SetFocusable(true);
132    infobar_icon_ = icon_as_menu_;
133  } else {
134    icon_as_image_ = new views::ImageView();
135    infobar_icon_ = icon_as_image_;
136  }
137
138  // Wait until the icon image is loaded before showing it.
139  infobar_icon_->SetVisible(false);
140  AddChildView(infobar_icon_);
141
142  // Set the desired height of the ExtensionViewViews, so that when the
143  // AddChildView() call triggers InfoBarView::ViewHierarchyChanged(), it can
144  // read the correct height off this object in order to calculate the overall
145  // desired infobar height.
146  GetExtensionView()->SetSize(gfx::Size(0, GetDelegate()->height()));
147  AddChildView(GetExtensionView());
148
149  // This must happen after adding all other children so InfoBarView can ensure
150  // the close button is the last child.
151  InfoBarView::ViewHierarchyChanged(details);
152
153  // This must happen after adding all children because it can trigger layout,
154  // which assumes that particular children (e.g. the close button) have already
155  // been added.
156  const extensions::Extension* extension = extension_view_host->extension();
157  extension_misc::ExtensionIcons image_size =
158      extension_misc::EXTENSION_ICON_BITTY;
159  extensions::ExtensionResource icon_resource =
160      extensions::IconsInfo::GetIconResource(
161          extension, image_size, ExtensionIconSet::MATCH_EXACTLY);
162  extensions::ImageLoader* loader =
163      extensions::ImageLoader::Get(extension_view_host->browser_context());
164  loader->LoadImageAsync(
165      extension,
166      icon_resource,
167      gfx::Size(image_size, image_size),
168      base::Bind(&ExtensionInfoBar::OnImageLoaded,
169                 weak_ptr_factory_.GetWeakPtr()));
170}
171
172int ExtensionInfoBar::ContentMinimumWidth() const {
173  return NonExtensionViewWidth() + static_cast<const ExtensionViewViews*>(
174      GetDelegate()->extension_view_host()->view())->GetMinimumSize().width();
175}
176
177void ExtensionInfoBar::OnMenuButtonClicked(views::View* source,
178                                           const gfx::Point& point) {
179  if (!owner())
180    return;  // We're closing; don't call anything, it might access the owner.
181  const extensions::Extension* extension =
182      GetDelegate()->extension_view_host()->extension();
183  DCHECK(icon_as_menu_);
184
185  scoped_refptr<ExtensionContextMenuModel> options_menu_contents =
186      new ExtensionContextMenuModel(extension, browser_);
187  DCHECK_EQ(icon_as_menu_, source);
188  RunMenuAt(
189      options_menu_contents.get(), icon_as_menu_, views::MENU_ANCHOR_TOPLEFT);
190}
191
192void ExtensionInfoBar::OnImageLoaded(const gfx::Image& image) {
193  if (!GetDelegate())
194    return;  // The delegate can go away while we asynchronously load images.
195
196  const gfx::ImageSkia* icon = NULL;
197  // Fall back on the default extension icon on failure.
198  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
199  if (image.IsEmpty())
200    icon = rb.GetImageNamed(IDR_EXTENSIONS_SECTION).ToImageSkia();
201  else
202    icon = image.ToImageSkia();
203
204  if (icon_as_menu_) {
205    const gfx::ImageSkia* drop_image =
206        rb.GetImageNamed(IDR_APP_DROPARROW).ToImageSkia();
207
208    gfx::CanvasImageSource* source = new MenuImageSource(*icon, *drop_image);
209    gfx::ImageSkia menu_image = gfx::ImageSkia(source, source->size());
210    icon_as_menu_->SetImage(views::Button::STATE_NORMAL, menu_image);
211  } else {
212    icon_as_image_->SetImage(*icon);
213  }
214
215  infobar_icon_->SizeToPreferredSize();
216  infobar_icon_->SetVisible(true);
217
218  Layout();
219}
220
221ExtensionInfoBarDelegate* ExtensionInfoBar::GetDelegate() {
222  return delegate()->AsExtensionInfoBarDelegate();
223}
224
225const ExtensionInfoBarDelegate* ExtensionInfoBar::GetDelegate() const {
226  return delegate()->AsExtensionInfoBarDelegate();
227}
228
229ExtensionViewViews* ExtensionInfoBar::GetExtensionView() {
230  return static_cast<ExtensionViewViews*>(
231      GetDelegate()->extension_view_host()->view());
232}
233
234int ExtensionInfoBar::NonExtensionViewWidth() const {
235  return infobar_icon_->width() + kIconHorizontalMargin;
236}
237