| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/cocoa/extensions/extension_infobar_controller.h" | 5 #import "chrome/browser/cocoa/extensions/extension_infobar_controller.h" |
| 6 | 6 |
| 7 #include <cmath> |
| 8 |
| 9 #include "app/resource_bundle.h" |
| 7 #import "chrome/browser/cocoa/animatable_view.h" | 10 #import "chrome/browser/cocoa/animatable_view.h" |
| 11 #import "chrome/browser/cocoa/extensions/extension_action_context_menu.h" |
| 12 #import "chrome/browser/cocoa/menu_button.h" |
| 8 #include "chrome/browser/cocoa/infobar.h" | 13 #include "chrome/browser/cocoa/infobar.h" |
| 9 #include "chrome/browser/extensions/extension_host.h" | 14 #include "chrome/browser/extensions/extension_host.h" |
| 10 #include "chrome/browser/extensions/extension_infobar_delegate.h" | 15 #include "chrome/browser/extensions/extension_infobar_delegate.h" |
| 11 #include "chrome/browser/tab_contents/tab_contents.h" | 16 #include "chrome/browser/tab_contents/tab_contents.h" |
| 17 #include "chrome/common/extensions/extension.h" |
| 18 #include "chrome/common/extensions/extension_resource.h" |
| 19 #include "gfx/canvas_skia.h" |
| 20 #include "grit/theme_resources.h" |
| 21 #include "skia/ext/skia_utils_mac.h" |
| 12 | 22 |
| 13 namespace { | 23 namespace { |
| 14 const CGFloat kAnimationDuration = 0.12; | 24 const CGFloat kAnimationDuration = 0.12; |
| 15 const CGFloat kBottomBorderHeightPx = 1.0; | 25 const CGFloat kBottomBorderHeightPx = 1.0; |
| 16 } // namepsace | 26 const CGFloat kButtonHeightPx = 26.0; |
| 27 const CGFloat kButtonLeftMarginPx = 2.0; |
| 28 const CGFloat kButtonWidthPx = 34.0; |
| 29 const CGFloat kDropArrowLeftMarginPx = 3.0; |
| 30 const CGFloat kToolbarMinHeightPx = 36.0; |
| 31 const CGFloat kToolbarMaxHeightPx = 72.0; |
| 32 } // namespace |
| 17 | 33 |
| 18 @interface ExtensionInfoBarController(Private) | 34 @interface ExtensionInfoBarController(Private) |
| 19 // Called when the extension's hosted NSView has been resized. | 35 // Called when the extension's hosted NSView has been resized. |
| 20 - (void)extensionViewFrameChanged; | 36 - (void)extensionViewFrameChanged; |
| 21 // Adjusts the width of the extension's hosted view to match the window's width. | 37 // Returns the clamped height of the extension view to be within the min and max |
| 22 - (void)adjustWidthToFitWindow; | 38 // values defined above. |
| 39 - (CGFloat)clampedExtensionViewHeight; |
| 40 // Adjusts the width of the extension's hosted view to match the window's width |
| 41 // and sets the proper height for it as well. |
| 42 - (void)adjustExtensionViewSize; |
| 43 // Sets the image to be used in the button on the left side of the infobar. |
| 44 - (void)setButtonImage:(NSImage*)image; |
| 23 @end | 45 @end |
| 24 | 46 |
| 47 // A helper class to bridge the asynchronous Skia bitmap loading mechanism to |
| 48 // the extension's button. |
| 49 class InfobarBridge : public ExtensionInfoBarDelegate::DelegateObserver, |
| 50 public ImageLoadingTracker::Observer { |
| 51 public: |
| 52 explicit InfobarBridge(ExtensionInfoBarController* owner) |
| 53 : owner_(owner), |
| 54 delegate_([owner delegate]->AsExtensionInfoBarDelegate()), |
| 55 tracker_(this) { |
| 56 delegate_->set_observer(this); |
| 57 LoadIcon(); |
| 58 } |
| 59 |
| 60 virtual ~InfobarBridge() { |
| 61 if (delegate_) |
| 62 delegate_->set_observer(NULL); |
| 63 } |
| 64 |
| 65 // Load the Extension's icon image. |
| 66 void LoadIcon() { |
| 67 ExtensionResource icon_resource; |
| 68 Extension* extension = delegate_->extension_host()->extension(); |
| 69 Extension::Icons size = |
| 70 extension->GetIconPathAllowLargerSize(&icon_resource, |
| 71 Extension::EXTENSION_ICON_BITTY); |
| 72 if (!icon_resource.relative_path().empty()) { |
| 73 tracker_.LoadImage(extension, icon_resource, gfx::Size(size, size), |
| 74 ImageLoadingTracker::DONT_CACHE); |
| 75 } else { |
| 76 OnImageLoaded(NULL, icon_resource, 0); |
| 77 } |
| 78 } |
| 79 |
| 80 // ImageLoadingTracker::Observer implementation. |
| 81 // TODO(andybons): The infobar view implementations share a lot of the same |
| 82 // code. Come up with a strategy to share amongst them. |
| 83 virtual void OnImageLoaded( |
| 84 SkBitmap* image, ExtensionResource resource, int index) { |
| 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 SkBitmap* icon; |
| 92 if (!image || image->empty()) |
| 93 icon = rb.GetBitmapNamed(IDR_EXTENSIONS_SECTION); |
| 94 else |
| 95 icon = image; |
| 96 |
| 97 SkBitmap* drop_image = rb.GetBitmapNamed(IDR_APP_DROPARROW); |
| 98 |
| 99 const int image_size = Extension::EXTENSION_ICON_BITTY; |
| 100 scoped_ptr<gfx::CanvasSkia> canvas( |
| 101 new gfx::CanvasSkia( |
| 102 image_size + kDropArrowLeftMarginPx + drop_image->width(), |
| 103 image_size, false)); |
| 104 canvas->DrawBitmapInt(*icon, |
| 105 0, 0, icon->width(), icon->height(), |
| 106 0, 0, image_size, image_size, |
| 107 false); |
| 108 canvas->DrawBitmapInt(*drop_image, |
| 109 image_size + kDropArrowLeftMarginPx, |
| 110 image_size / 2); |
| 111 [owner_ setButtonImage:gfx::SkBitmapToNSImage(canvas->ExtractBitmap())]; |
| 112 } |
| 113 |
| 114 // Overridden from ExtensionInfoBarDelegate::DelegateObserver: |
| 115 virtual void OnDelegateDeleted() { |
| 116 delegate_ = NULL; |
| 117 } |
| 118 |
| 119 private: |
| 120 // Weak. Owns us. |
| 121 ExtensionInfoBarController* owner_; |
| 122 |
| 123 // Weak. |
| 124 ExtensionInfoBarDelegate* delegate_; |
| 125 |
| 126 // Loads the extensions's icon on the file thread. |
| 127 ImageLoadingTracker tracker_; |
| 128 |
| 129 DISALLOW_COPY_AND_ASSIGN(InfobarBridge); |
| 130 }; |
| 131 |
| 132 |
| 25 @implementation ExtensionInfoBarController | 133 @implementation ExtensionInfoBarController |
| 26 | 134 |
| 27 - (id)initWithDelegate:(InfoBarDelegate*)delegate | 135 - (id)initWithDelegate:(InfoBarDelegate*)delegate |
| 28 window:(NSWindow*)window { | 136 window:(NSWindow*)window { |
| 29 if ((self = [super initWithDelegate:delegate])) { | 137 if ((self = [super initWithDelegate:delegate])) { |
| 30 window_ = window; | 138 window_ = window; |
| 139 dropdownButton_.reset([[MenuButton alloc] init]); |
| 140 |
| 141 ExtensionHost* extensionHost = delegate_->AsExtensionInfoBarDelegate()-> |
| 142 extension_host(); |
| 143 contextMenu_.reset([[ExtensionActionContextMenu alloc] |
| 144 initWithExtension:extensionHost->extension() |
| 145 profile:extensionHost->profile() |
| 146 extensionAction:NULL]); |
| 147 // See menu_button.h for documentation on why this is needed. |
| 148 NSMenuItem* dummyItem = |
| 149 [[[NSMenuItem alloc] initWithTitle:@"" |
| 150 action:nil |
| 151 keyEquivalent:@""] autorelease]; |
| 152 [contextMenu_ insertItem:dummyItem atIndex:0]; |
| 153 [dropdownButton_ setAttachedMenu:contextMenu_.get()]; |
| 154 |
| 155 bridge_.reset(new InfobarBridge(self)); |
| 31 } | 156 } |
| 32 return self; | 157 return self; |
| 33 } | 158 } |
| 34 | 159 |
| 35 - (void)dealloc { | 160 - (void)dealloc { |
| 36 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 161 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 37 [super dealloc]; | 162 [super dealloc]; |
| 38 } | 163 } |
| 39 | 164 |
| 40 - (void)addAdditionalControls { | 165 - (void)addAdditionalControls { |
| 41 [self removeButtons]; | 166 [self removeButtons]; |
| 42 | 167 |
| 43 extensionView_ = delegate_->AsExtensionInfoBarDelegate()-> | 168 extensionView_ = delegate_->AsExtensionInfoBarDelegate()->extension_host()-> |
| 44 extension_host()->view()->native_view(); | 169 view()->native_view(); |
| 45 | 170 |
| 46 // Add the extension's RenderWidgetHostViewMac to the view hierarchy of the | 171 // Add the extension's RenderWidgetHostViewMac to the view hierarchy of the |
| 47 // InfoBar and make sure to place it below the Close button. | 172 // InfoBar and make sure to place it below the Close button. |
| 48 [infoBarView_ addSubview:extensionView_ | 173 [infoBarView_ addSubview:extensionView_ |
| 49 positioned:NSWindowBelow | 174 positioned:NSWindowBelow |
| 50 relativeTo:(NSView*)closeButton_]; | 175 relativeTo:(NSView*)closeButton_]; |
| 51 | 176 |
| 177 // Add the context menu button to the hierarchy. |
| 178 [dropdownButton_ setShowsBorderOnlyWhileMouseInside:YES]; |
| 179 CGFloat buttonY = |
| 180 std::floor(NSMidY([infoBarView_ frame]) - (kButtonHeightPx / 2.0)) + |
| 181 kBottomBorderHeightPx; |
| 182 NSRect buttonFrame = NSMakeRect( |
| 183 kButtonLeftMarginPx, buttonY, kButtonWidthPx, kButtonHeightPx); |
| 184 [dropdownButton_ setFrame:buttonFrame]; |
| 185 [dropdownButton_ setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin]; |
| 186 [infoBarView_ addSubview:dropdownButton_]; |
| 187 |
| 52 // Because the parent view has a bottom border, account for it during | 188 // Because the parent view has a bottom border, account for it during |
| 53 // positioning. | 189 // positioning. |
| 54 NSRect extensionFrame = [extensionView_ frame]; | 190 NSRect extensionFrame = [extensionView_ frame]; |
| 55 extensionFrame.origin.y = kBottomBorderHeightPx; | 191 extensionFrame.origin.y = kBottomBorderHeightPx; |
| 56 | 192 |
| 57 [extensionView_ setFrame:extensionFrame]; | 193 [extensionView_ setFrame:extensionFrame]; |
| 58 // The extension's native view will only have a height that is non-zero if it | 194 // The extension's native view will only have a height that is non-zero if it |
| 59 // already has been loaded and rendered, which is the case when you switch | 195 // already has been loaded and rendered, which is the case when you switch |
| 60 // back to a tab with an extension infobar within it. The reason this is | 196 // back to a tab with an extension infobar within it. The reason this is |
| 61 // needed is because the extension view's frame will not have changed in the | 197 // needed is because the extension view's frame will not have changed in the |
| 62 // above case, so the NSViewFrameDidChangeNotification registered below will | 198 // above case, so the NSViewFrameDidChangeNotification registered below will |
| 63 // never fire. | 199 // never fire. |
| 64 if (extensionFrame.size.height > 0.0) { | 200 if (NSHeight(extensionFrame) > 0.0) { |
| 65 NSSize infoBarSize = [[self view] frame].size; | 201 NSSize infoBarSize = [[self view] frame].size; |
| 66 infoBarSize.height = extensionFrame.size.height + kBottomBorderHeightPx; | 202 infoBarSize.height = [self clampedExtensionViewHeight] + |
| 203 kBottomBorderHeightPx; |
| 67 [[self view] setFrameSize:infoBarSize]; | 204 [[self view] setFrameSize:infoBarSize]; |
| 68 [infoBarView_ setFrameSize:infoBarSize]; | 205 [infoBarView_ setFrameSize:infoBarSize]; |
| 69 } | 206 } |
| 70 | 207 |
| 71 [self adjustWidthToFitWindow]; | 208 [self adjustExtensionViewSize]; |
| 72 | 209 |
| 73 // These two notification handlers are here to ensure the width of the | 210 // These two notification handlers are here to ensure the width of the |
| 74 // native extension view is the same as the browser window's width and that | 211 // native extension view is the same as the browser window's width and that |
| 75 // the parent infobar view matches the height of the extension's native view. | 212 // the parent infobar view matches the height of the extension's native view. |
| 76 [[NSNotificationCenter defaultCenter] | 213 [[NSNotificationCenter defaultCenter] |
| 77 addObserver:self | 214 addObserver:self |
| 78 selector:@selector(extensionViewFrameChanged) | 215 selector:@selector(extensionViewFrameChanged) |
| 79 name:NSViewFrameDidChangeNotification | 216 name:NSViewFrameDidChangeNotification |
| 80 object:extensionView_]; | 217 object:extensionView_]; |
| 81 | 218 |
| 82 [[NSNotificationCenter defaultCenter] | 219 [[NSNotificationCenter defaultCenter] |
| 83 addObserver:self | 220 addObserver:self |
| 84 selector:@selector(adjustWidthToFitWindow) | 221 selector:@selector(adjustWidthToFitWindow) |
| 85 name:NSWindowDidResizeNotification | 222 name:NSWindowDidResizeNotification |
| 86 object:window_]; | 223 object:window_]; |
| 87 } | 224 } |
| 88 | 225 |
| 89 - (void)extensionViewFrameChanged { | 226 - (void)extensionViewFrameChanged { |
| 90 [self adjustWidthToFitWindow]; | 227 [self adjustExtensionViewSize]; |
| 91 | 228 |
| 92 AnimatableView* view = [self animatableView]; | 229 AnimatableView* view = [self animatableView]; |
| 93 NSRect infoBarFrame = [view frame]; | 230 NSRect infoBarFrame = [view frame]; |
| 94 CGFloat newHeight = NSHeight([extensionView_ frame]) + kBottomBorderHeightPx; | 231 CGFloat newHeight = [self clampedExtensionViewHeight] + kBottomBorderHeightPx; |
| 95 [infoBarView_ setPostsFrameChangedNotifications:NO]; | 232 [infoBarView_ setPostsFrameChangedNotifications:NO]; |
| 96 infoBarFrame.size.height = newHeight; | 233 infoBarFrame.size.height = newHeight; |
| 97 [infoBarView_ setFrame:infoBarFrame]; | 234 [infoBarView_ setFrame:infoBarFrame]; |
| 98 [infoBarView_ setPostsFrameChangedNotifications:YES]; | 235 [infoBarView_ setPostsFrameChangedNotifications:YES]; |
| 99 [view animateToNewHeight:newHeight duration:kAnimationDuration]; | 236 [view animateToNewHeight:newHeight duration:kAnimationDuration]; |
| 100 } | 237 } |
| 101 | 238 |
| 102 - (void)adjustWidthToFitWindow { | 239 - (CGFloat)clampedExtensionViewHeight { |
| 240 return std::max(kToolbarMinHeightPx, |
| 241 std::min(NSHeight([extensionView_ frame]), kToolbarMaxHeightPx)); |
| 242 } |
| 243 |
| 244 - (void)adjustExtensionViewSize { |
| 103 [extensionView_ setPostsFrameChangedNotifications:NO]; | 245 [extensionView_ setPostsFrameChangedNotifications:NO]; |
| 104 NSSize extensionViewSize = [extensionView_ frame].size; | 246 NSSize extensionViewSize = [extensionView_ frame].size; |
| 105 extensionViewSize.width = NSWidth([window_ frame]); | 247 extensionViewSize.width = NSWidth([window_ frame]); |
| 248 extensionViewSize.height = [self clampedExtensionViewHeight]; |
| 106 [extensionView_ setFrameSize:extensionViewSize]; | 249 [extensionView_ setFrameSize:extensionViewSize]; |
| 107 [extensionView_ setPostsFrameChangedNotifications:YES]; | 250 [extensionView_ setPostsFrameChangedNotifications:YES]; |
| 108 } | 251 } |
| 109 | 252 |
| 253 - (void)setButtonImage:(NSImage*)image { |
| 254 [dropdownButton_ setImage:image]; |
| 255 } |
| 256 |
| 110 @end | 257 @end |
| 111 | 258 |
| 112 InfoBar* ExtensionInfoBarDelegate::CreateInfoBar() { | 259 InfoBar* ExtensionInfoBarDelegate::CreateInfoBar() { |
| 113 NSWindow* window = [(NSView*)tab_contents_->GetContentNativeView() window]; | 260 NSWindow* window = [(NSView*)tab_contents_->GetContentNativeView() window]; |
| 114 ExtensionInfoBarController* controller = | 261 ExtensionInfoBarController* controller = |
| 115 [[ExtensionInfoBarController alloc] initWithDelegate:this | 262 [[ExtensionInfoBarController alloc] initWithDelegate:this |
| 116 window:window]; | 263 window:window]; |
| 117 return new InfoBar(controller); | 264 return new InfoBar(controller); |
| 118 } | 265 } |
| OLD | NEW |