1 // Copyright (c) 2011 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_host.h"
9 #include "chrome/browser/extensions/extension_infobar_delegate.h"
10 #include "chrome/browser/platform_util.h"
11 #include "chrome/browser/ui/views/infobars/infobar_background.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "chrome/common/extensions/extension.h"
14 #include "chrome/common/extensions/extension_icon_set.h"
15 #include "chrome/common/extensions/extension_resource.h"
16 #include "grit/theme_resources.h"
17 #include "ui/base/animation/slide_animation.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/canvas_skia.h"
20 #include "views/controls/button/menu_button.h"
21 #include "views/controls/menu/menu_2.h"
22 #include "views/widget/widget.h"
23
24 // ExtensionInfoBarDelegate ---------------------------------------------------
25
CreateInfoBar()26 InfoBar* ExtensionInfoBarDelegate::CreateInfoBar() {
27 return new ExtensionInfoBar(this);
28 }
29
30 // ExtensionInfoBar -----------------------------------------------------------
31
32 namespace {
33 // The horizontal margin between the menu and the Extension (HTML) view.
34 static const int kMenuHorizontalMargin = 1;
35 };
36
ExtensionInfoBar(ExtensionInfoBarDelegate * delegate)37 ExtensionInfoBar::ExtensionInfoBar(ExtensionInfoBarDelegate* delegate)
38 : InfoBarView(delegate),
39 delegate_(delegate),
40 menu_(NULL),
41 ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) {
42 delegate->set_observer(this);
43
44 ExtensionView* extension_view = delegate->extension_host()->view();
45 int height = extension_view->GetPreferredSize().height();
46 SetBarTargetHeight((height > 0) ? (height + kSeparatorLineHeight) : 0);
47
48 // Get notified of resize events for the ExtensionView.
49 extension_view->SetContainer(this);
50 }
51
~ExtensionInfoBar()52 ExtensionInfoBar::~ExtensionInfoBar() {
53 if (GetDelegate()) {
54 GetDelegate()->extension_host()->view()->SetContainer(NULL);
55 GetDelegate()->set_observer(NULL);
56 }
57 }
58
Layout()59 void ExtensionInfoBar::Layout() {
60 InfoBarView::Layout();
61
62 gfx::Size menu_size = menu_->GetPreferredSize();
63 menu_->SetBounds(StartX(), OffsetY(menu_size), menu_size.width(),
64 menu_size.height());
65
66 GetDelegate()->extension_host()->view()->SetBounds(
67 menu_->bounds().right() + kMenuHorizontalMargin, 0,
68 std::max(0, EndX() - StartX() - ContentMinimumWidth()), height());
69 }
70
ViewHierarchyChanged(bool is_add,View * parent,View * child)71 void ExtensionInfoBar::ViewHierarchyChanged(bool is_add,
72 View* parent,
73 View* child) {
74 if (!is_add || (child != this) || (menu_ != NULL)) {
75 InfoBarView::ViewHierarchyChanged(is_add, parent, child);
76 return;
77 }
78
79 menu_ = new views::MenuButton(NULL, std::wstring(), this, false);
80 menu_->SetVisible(false);
81 AddChildView(menu_);
82
83 ExtensionHost* extension_host = GetDelegate()->extension_host();
84 AddChildView(extension_host->view());
85
86 // This must happen after adding all other children so InfoBarView can ensure
87 // the close button is the last child.
88 InfoBarView::ViewHierarchyChanged(is_add, parent, child);
89
90 // This must happen after adding all children because it can trigger layout,
91 // which assumes that particular children (e.g. the close button) have already
92 // been added.
93 const Extension* extension = extension_host->extension();
94 int image_size = Extension::EXTENSION_ICON_BITTY;
95 ExtensionResource icon_resource = extension->GetIconResource(
96 image_size, ExtensionIconSet::MATCH_EXACTLY);
97 if (!icon_resource.relative_path().empty()) {
98 tracker_.LoadImage(extension, icon_resource,
99 gfx::Size(image_size, image_size), ImageLoadingTracker::DONT_CACHE);
100 } else {
101 OnImageLoaded(NULL, icon_resource, 0);
102 }
103 }
104
ContentMinimumWidth() const105 int ExtensionInfoBar::ContentMinimumWidth() const {
106 return menu_->GetPreferredSize().width() + kMenuHorizontalMargin;
107 }
108
OnExtensionMouseMove(ExtensionView * view)109 void ExtensionInfoBar::OnExtensionMouseMove(ExtensionView* view) {
110 }
111
OnExtensionMouseLeave(ExtensionView * view)112 void ExtensionInfoBar::OnExtensionMouseLeave(ExtensionView* view) {
113 }
114
OnExtensionPreferredSizeChanged(ExtensionView * view)115 void ExtensionInfoBar::OnExtensionPreferredSizeChanged(ExtensionView* view) {
116 ExtensionInfoBarDelegate* delegate = GetDelegate();
117 DCHECK_EQ(delegate->extension_host()->view(), view);
118
119 // When the infobar is closed, it animates to 0 vertical height. We'll
120 // continue to get size changed notifications from the ExtensionView, but we
121 // need to ignore them otherwise we'll try to re-animate open (and leak the
122 // infobar view).
123 if (delegate->closing())
124 return;
125
126 view->SetVisible(true);
127
128 if (height() == 0)
129 animation()->Reset(0.0);
130
131 // Clamp height to a min and a max size of between 1 and 2 InfoBars.
132 SetBarTargetHeight(std::min(2 * kDefaultBarTargetHeight,
133 std::max(kDefaultBarTargetHeight, view->GetPreferredSize().height())));
134
135 animation()->Show();
136 }
137
OnImageLoaded(SkBitmap * image,const ExtensionResource & resource,int index)138 void ExtensionInfoBar::OnImageLoaded(SkBitmap* image,
139 const ExtensionResource& resource,
140 int index) {
141 if (!GetDelegate())
142 return; // The delegate can go away while we asynchronously load images.
143
144 SkBitmap* icon = image;
145 // Fall back on the default extension icon on failure.
146 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
147 if (!image || image->empty())
148 icon = rb.GetBitmapNamed(IDR_EXTENSIONS_SECTION);
149
150 SkBitmap* drop_image = rb.GetBitmapNamed(IDR_APP_DROPARROW);
151
152 int image_size = Extension::EXTENSION_ICON_BITTY;
153 // The margin between the extension icon and the drop-down arrow bitmap.
154 static const int kDropArrowLeftMargin = 3;
155 scoped_ptr<gfx::CanvasSkia> canvas(new gfx::CanvasSkia(
156 image_size + kDropArrowLeftMargin + drop_image->width(), image_size,
157 false));
158 canvas->DrawBitmapInt(*icon, 0, 0, icon->width(), icon->height(), 0, 0,
159 image_size, image_size, false);
160 canvas->DrawBitmapInt(*drop_image, image_size + kDropArrowLeftMargin,
161 image_size / 2);
162 menu_->SetIcon(canvas->ExtractBitmap());
163 menu_->SetVisible(true);
164
165 Layout();
166 }
167
OnDelegateDeleted()168 void ExtensionInfoBar::OnDelegateDeleted() {
169 GetDelegate()->extension_host()->view()->SetContainer(NULL);
170 delegate_ = NULL;
171 }
172
RunMenu(View * source,const gfx::Point & pt)173 void ExtensionInfoBar::RunMenu(View* source, const gfx::Point& pt) {
174 const Extension* extension = GetDelegate()->extension_host()->extension();
175 if (!extension->ShowConfigureContextMenus())
176 return;
177
178 if (!options_menu_contents_.get()) {
179 Browser* browser = BrowserView::GetBrowserViewForNativeWindow(
180 platform_util::GetTopLevel(source->GetWidget()->GetNativeView()))->
181 browser();
182 options_menu_contents_ = new ExtensionContextMenuModel(extension, browser,
183 NULL);
184 }
185
186 options_menu_menu_.reset(new views::Menu2(options_menu_contents_.get()));
187 options_menu_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPLEFT);
188 }
189
GetDelegate()190 ExtensionInfoBarDelegate* ExtensionInfoBar::GetDelegate() {
191 return delegate_ ? delegate_->AsExtensionInfoBarDelegate() : NULL;
192 }
193