OLD | NEW |
| (Empty) |
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 #import "chrome/browser/ui/cocoa/infobars/extension_infobar_controller.h" | |
6 | |
7 #include "chrome/browser/extensions/extension_infobar_delegate.h" | |
8 #include "chrome/browser/extensions/extension_context_menu_model.h" | |
9 #include "chrome/browser/extensions/extension_view.h" | |
10 #include "chrome/browser/extensions/extension_view_host.h" | |
11 #include "chrome/browser/infobars/infobar_service.h" | |
12 #include "chrome/browser/profiles/profile.h" | |
13 #include "chrome/browser/ui/browser_finder.h" | |
14 #import "chrome/browser/ui/cocoa/animatable_view.h" | |
15 #include "chrome/browser/ui/cocoa/infobars/infobar_cocoa.h" | |
16 #import "chrome/browser/ui/cocoa/menu_button.h" | |
17 #include "content/public/browser/web_contents.h" | |
18 #include "extensions/browser/image_loader.h" | |
19 #include "extensions/common/constants.h" | |
20 #include "extensions/common/extension.h" | |
21 #include "extensions/common/extension_icon_set.h" | |
22 #include "extensions/common/extension_resource.h" | |
23 #include "extensions/common/manifest_handlers/icons_handler.h" | |
24 #include "grit/theme_resources.h" | |
25 #include "skia/ext/skia_utils_mac.h" | |
26 #include "ui/base/resource/resource_bundle.h" | |
27 #include "ui/gfx/canvas.h" | |
28 #import "ui/base/cocoa/menu_controller.h" | |
29 #include "ui/gfx/image/image.h" | |
30 | |
31 const CGFloat kBottomBorderHeightPx = 1.0; | |
32 const CGFloat kButtonHeightPx = 26.0; | |
33 const CGFloat kButtonLeftMarginPx = 2.0; | |
34 const CGFloat kButtonWidthPx = 34.0; | |
35 const CGFloat kDropArrowLeftMarginPx = 3.0; | |
36 const CGFloat kToolbarMinHeightPx = 36.0; | |
37 const CGFloat kToolbarMaxHeightPx = 72.0; | |
38 | |
39 @interface ExtensionInfoBarController(Private) | |
40 // Called when the extension's hosted NSView has been resized. | |
41 - (void)extensionViewFrameChanged; | |
42 // Returns the clamped height of the extension view to be within the min and max | |
43 // values defined above. | |
44 - (CGFloat)clampedExtensionViewHeight; | |
45 // Adjusts the width of the extension's hosted view to match the window's width | |
46 // and sets the proper height for it as well. | |
47 - (void)adjustExtensionViewSize; | |
48 // Sets the image to be used in the button on the left side of the infobar. | |
49 - (void)setButtonImage:(NSImage*)image; | |
50 @end | |
51 | |
52 // A helper class to bridge the asynchronous Skia bitmap loading mechanism to | |
53 // the extension's button. | |
54 class InfobarBridge { | |
55 public: | |
56 explicit InfobarBridge(ExtensionInfoBarController* owner) | |
57 : owner_(owner), | |
58 delegate_([owner delegate]->AsExtensionInfoBarDelegate()), | |
59 weak_ptr_factory_(this) { | |
60 LoadIcon(); | |
61 } | |
62 | |
63 // Load the Extension's icon image. | |
64 void LoadIcon() { | |
65 const extensions::Extension* extension = delegate_->extension_view_host()-> | |
66 extension(); | |
67 extensions::ExtensionResource icon_resource = | |
68 extensions::IconsInfo::GetIconResource( | |
69 extension, | |
70 extension_misc::EXTENSION_ICON_BITTY, | |
71 ExtensionIconSet::MATCH_EXACTLY); | |
72 extensions::ImageLoader* loader = extensions::ImageLoader::Get( | |
73 delegate_->extension_view_host()->browser_context()); | |
74 loader->LoadImageAsync(extension, icon_resource, | |
75 gfx::Size(extension_misc::EXTENSION_ICON_BITTY, | |
76 extension_misc::EXTENSION_ICON_BITTY), | |
77 base::Bind(&InfobarBridge::OnImageLoaded, | |
78 weak_ptr_factory_.GetWeakPtr())); | |
79 } | |
80 | |
81 // ImageLoader callback. | |
82 // TODO(andybons): The infobar view implementations share a lot of the same | |
83 // code. Come up with a strategy to share amongst them. | |
84 void OnImageLoaded(const gfx::Image& image) { | |
85 if (!delegate_) | |
86 return; // The delegate can go away while the image asynchronously loads. | |
87 | |
88 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
89 | |
90 // Fall back on the default extension icon on failure. | |
91 const gfx::ImageSkia* icon; | |
92 if (image.IsEmpty()) | |
93 icon = rb.GetImageSkiaNamed(IDR_EXTENSIONS_SECTION); | |
94 else | |
95 icon = image.ToImageSkia(); | |
96 | |
97 gfx::ImageSkia* drop_image = rb.GetImageSkiaNamed(IDR_APP_DROPARROW); | |
98 | |
99 const int image_size = extension_misc::EXTENSION_ICON_BITTY; | |
100 scoped_ptr<gfx::Canvas> canvas( | |
101 new gfx::Canvas( | |
102 gfx::Size(image_size + kDropArrowLeftMarginPx + drop_image->width(), | |
103 image_size), 1.0f, false)); | |
104 canvas->DrawImageInt(*icon, | |
105 0, 0, icon->width(), icon->height(), | |
106 0, 0, image_size, image_size, | |
107 false); | |
108 canvas->DrawImageInt(*drop_image, | |
109 image_size + kDropArrowLeftMarginPx, | |
110 image_size / 2); | |
111 [owner_ setButtonImage:gfx::SkBitmapToNSImage( | |
112 canvas->ExtractImageRep().sk_bitmap())]; | |
113 } | |
114 | |
115 private: | |
116 // Weak. Owns us. | |
117 ExtensionInfoBarController* owner_; | |
118 | |
119 // Weak. | |
120 ExtensionInfoBarDelegate* delegate_; | |
121 | |
122 base::WeakPtrFactory<InfobarBridge> weak_ptr_factory_; | |
123 | |
124 DISALLOW_COPY_AND_ASSIGN(InfobarBridge); | |
125 }; | |
126 | |
127 | |
128 @implementation ExtensionInfoBarController | |
129 | |
130 - (id)initWithInfoBar:(InfoBarCocoa*)infobar { | |
131 if ((self = [super initWithInfoBar:infobar])) { | |
132 dropdownButton_.reset([[MenuButton alloc] init]); | |
133 [dropdownButton_ setOpenMenuOnClick:YES]; | |
134 | |
135 base::scoped_nsobject<NSMenu> contextMenu( | |
136 [[NSMenu alloc] initWithTitle:@""]); | |
137 [contextMenu setDelegate:self]; | |
138 // See menu_button.h for documentation on why this is needed. | |
139 [contextMenu addItemWithTitle:@"" action:NULL keyEquivalent:@""]; | |
140 [dropdownButton_ setAttachedMenu:contextMenu]; | |
141 | |
142 bridge_.reset(new InfobarBridge(self)); | |
143 } | |
144 return self; | |
145 } | |
146 | |
147 - (void)dealloc { | |
148 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
149 [super dealloc]; | |
150 } | |
151 | |
152 - (void)addAdditionalControls { | |
153 [self removeButtons]; | |
154 | |
155 extensionView_ = [self delegate]->AsExtensionInfoBarDelegate() | |
156 ->extension_view_host()->view()->GetNativeView(); | |
157 | |
158 // Add the extension's RenderWidgetHostView to the view hierarchy of the | |
159 // InfoBar and make sure to place it below the Close button. | |
160 [infoBarView_ addSubview:extensionView_ | |
161 positioned:NSWindowBelow | |
162 relativeTo:(NSView*)closeButton_]; | |
163 | |
164 // Add the context menu button to the hierarchy. | |
165 [dropdownButton_ setShowsBorderOnlyWhileMouseInside:YES]; | |
166 CGFloat buttonY = | |
167 std::floor(NSMidY([infoBarView_ frame]) - (kButtonHeightPx / 2.0)) + | |
168 kBottomBorderHeightPx; | |
169 NSRect buttonFrame = NSMakeRect( | |
170 kButtonLeftMarginPx, buttonY, kButtonWidthPx, kButtonHeightPx); | |
171 [dropdownButton_ setFrame:buttonFrame]; | |
172 [dropdownButton_ setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin]; | |
173 [infoBarView_ addSubview:dropdownButton_]; | |
174 | |
175 // Because the parent view has a bottom border, account for it during | |
176 // positioning. | |
177 NSRect extensionFrame = [extensionView_ frame]; | |
178 extensionFrame.origin.y = kBottomBorderHeightPx; | |
179 | |
180 [extensionView_ setFrame:extensionFrame]; | |
181 // The extension's native view will only have a height that is non-zero if it | |
182 // already has been loaded and rendered, which is the case when you switch | |
183 // back to a tab with an extension infobar within it. The reason this is | |
184 // needed is because the extension view's frame will not have changed in the | |
185 // above case, so the NSViewFrameDidChangeNotification registered below will | |
186 // never fire. | |
187 if (NSHeight(extensionFrame) > 0.0) | |
188 [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]); | |
189 | |
190 [self adjustExtensionViewSize]; | |
191 | |
192 // These two notification handlers are here to ensure the width of the | |
193 // native extension view is the same as the browser window's width and that | |
194 // the parent infobar view matches the height of the extension's native view. | |
195 [[NSNotificationCenter defaultCenter] | |
196 addObserver:self | |
197 selector:@selector(extensionViewFrameChanged) | |
198 name:NSViewFrameDidChangeNotification | |
199 object:extensionView_]; | |
200 | |
201 [[NSNotificationCenter defaultCenter] | |
202 addObserver:self | |
203 selector:@selector(adjustExtensionViewSize) | |
204 name:NSViewFrameDidChangeNotification | |
205 object:[self view]]; | |
206 } | |
207 | |
208 - (void)infobarWillHide { | |
209 [[dropdownButton_ menu] cancelTracking]; | |
210 [super infobarWillHide]; | |
211 } | |
212 | |
213 - (void)infobarWillClose { | |
214 [self disablePopUpMenu:[dropdownButton_ menu]]; | |
215 [super infobarWillClose]; | |
216 } | |
217 | |
218 - (void)extensionViewFrameChanged { | |
219 [self adjustExtensionViewSize]; | |
220 [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]); | |
221 } | |
222 | |
223 - (CGFloat)clampedExtensionViewHeight { | |
224 CGFloat height = [self delegate]->AsExtensionInfoBarDelegate()->height(); | |
225 return std::max(kToolbarMinHeightPx, std::min(height, kToolbarMaxHeightPx)); | |
226 } | |
227 | |
228 - (void)adjustExtensionViewSize { | |
229 [extensionView_ setPostsFrameChangedNotifications:NO]; | |
230 NSSize extensionViewSize = [extensionView_ frame].size; | |
231 extensionViewSize.width = NSWidth([[self view] frame]); | |
232 extensionViewSize.height = [self clampedExtensionViewHeight]; | |
233 [extensionView_ setFrameSize:extensionViewSize]; | |
234 [extensionView_ setPostsFrameChangedNotifications:YES]; | |
235 } | |
236 | |
237 - (void)setButtonImage:(NSImage*)image { | |
238 [dropdownButton_ setImage:image]; | |
239 } | |
240 | |
241 - (void)menuNeedsUpdate:(NSMenu*)menu { | |
242 DCHECK([self isOwned]); | |
243 | |
244 if (!contextMenuController_) { | |
245 extensions::ExtensionViewHost* extensionViewHost = | |
246 [self delegate]->AsExtensionInfoBarDelegate()->extension_view_host(); | |
247 Browser* browser = chrome::FindBrowserWithWebContents( | |
248 [self delegate]->AsExtensionInfoBarDelegate()->GetWebContents()); | |
249 contextMenuModel_ = make_scoped_refptr(new ExtensionContextMenuModel( | |
250 extensionViewHost->extension(), browser)); | |
251 contextMenuController_.reset( | |
252 [[MenuController alloc] initWithModel:contextMenuModel_.get() | |
253 useWithPopUpButtonCell:NO]); | |
254 } | |
255 | |
256 [menu removeAllItems]; | |
257 [contextMenuController_ menuNeedsUpdate:menu]; | |
258 } | |
259 | |
260 @end | |
261 | |
262 // static | |
263 scoped_ptr<infobars::InfoBar> ExtensionInfoBarDelegate::CreateInfoBar( | |
264 scoped_ptr<ExtensionInfoBarDelegate> delegate) { | |
265 scoped_ptr<InfoBarCocoa> infobar(new InfoBarCocoa(delegate.Pass())); | |
266 base::scoped_nsobject<ExtensionInfoBarController> controller( | |
267 [[ExtensionInfoBarController alloc] initWithInfoBar:infobar.get()]); | |
268 infobar->set_controller(controller); | |
269 return infobar.Pass(); | |
270 } | |
OLD | NEW |