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 "base/logging.h" 6 #include "base/mac/bundle_locations.h" 7 #include "base/mac/mac_util.h" 8 #include "chrome/browser/chrome_notification_types.h" 9 #include "chrome/browser/infobars/confirm_infobar_delegate.h" 10 #include "chrome/browser/infobars/infobar.h" 11 #include "chrome/browser/infobars/infobar_service.h" 12 #import "chrome/browser/ui/cocoa/animatable_view.h" 13 #import "chrome/browser/ui/cocoa/browser_window_controller.h" 14 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 15 #import "chrome/browser/ui/cocoa/infobars/infobar_controller.h" 16 #import "chrome/browser/ui/cocoa/view_id_util.h" 17 #include "content/public/browser/notification_details.h" 18 #include "content/public/browser/notification_source.h" 19 #include "skia/ext/skia_utils_mac.h" 20 21 // C++ class that receives INFOBAR_ADDED and INFOBAR_REMOVED 22 // notifications and proxies them back to |controller|. 23 class InfoBarNotificationObserver : public content::NotificationObserver { 24 public: 25 InfoBarNotificationObserver(InfoBarContainerController* controller) 26 : controller_(controller) { 27 } 28 29 private: 30 // NotificationObserver implementation 31 virtual void Observe( 32 int type, 33 const content::NotificationSource& source, 34 const content::NotificationDetails& details) OVERRIDE { 35 InfoBarService* infobar_service = 36 content::Source<InfoBarService>(source).ptr(); 37 switch (type) { 38 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED: 39 [controller_ addInfoBar:content::Details<InfoBarAddedDetails>(details)-> 40 CreateInfoBar(infobar_service) 41 animate:YES]; 42 break; 43 44 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED: { 45 InfoBarRemovedDetails* removed_details = 46 content::Details<InfoBarRemovedDetails>(details).ptr(); 47 [controller_ 48 closeInfoBarsForDelegate:removed_details->first 49 animate:(removed_details->second ? YES : NO)]; 50 break; 51 } 52 53 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED: { 54 InfoBarReplacedDetails* replaced_details = 55 content::Details<InfoBarReplacedDetails>(details).ptr(); 56 [controller_ closeInfoBarsForDelegate:replaced_details->first 57 animate:NO]; 58 [controller_ addInfoBar:replaced_details->second-> 59 CreateInfoBar(infobar_service) 60 animate:NO]; 61 break; 62 } 63 64 default: 65 NOTREACHED(); // we don't ask for anything else! 66 break; 67 } 68 69 [controller_ positionInfoBarsAndRedraw]; 70 } 71 72 InfoBarContainerController* controller_; // weak, owns us. 73 }; 74 75 76 @interface InfoBarContainerController (PrivateMethods) 77 // Returns the desired height of the container view, computed by 78 // adding together the heights of all its subviews. 79 - (CGFloat)desiredHeight; 80 81 @end 82 83 84 @implementation InfoBarContainerController 85 86 @synthesize shouldSuppressTopInfoBarTip = shouldSuppressTopInfoBarTip_; 87 88 - (id)initWithResizeDelegate:(id<ViewResizer>)resizeDelegate { 89 DCHECK(resizeDelegate); 90 if ((self = [super initWithNibName:@"InfoBarContainer" 91 bundle:base::mac::FrameworkBundle()])) { 92 resizeDelegate_ = resizeDelegate; 93 infoBarObserver_.reset(new InfoBarNotificationObserver(self)); 94 95 // NSMutableArray needs an initial capacity, and we rarely ever see 96 // more than two infobars at a time, so that seems like a good choice. 97 infobarControllers_.reset([[NSMutableArray alloc] initWithCapacity:2]); 98 closingInfoBars_.reset([[NSMutableSet alloc] initWithCapacity:2]); 99 } 100 return self; 101 } 102 103 - (void)dealloc { 104 DCHECK_EQ([infobarControllers_ count], 0U); 105 DCHECK_EQ([closingInfoBars_ count], 0U); 106 view_id_util::UnsetID([self view]); 107 [super dealloc]; 108 } 109 110 - (void)awakeFromNib { 111 // The info bar container view is an ordinary NSView object, so we set its 112 // ViewID here. 113 view_id_util::SetID([self view], VIEW_ID_INFO_BAR_CONTAINER); 114 } 115 116 - (void)willRemoveController:(InfoBarController*)controller { 117 [closingInfoBars_ addObject:controller]; 118 } 119 120 - (void)removeController:(InfoBarController*)controller { 121 if (![infobarControllers_ containsObject:controller]) 122 return; 123 124 // This code can be executed while InfoBarController is still on the stack, so 125 // we retain and autorelease the controller to prevent it from being 126 // dealloc'ed too early. 127 [[controller retain] autorelease]; 128 [[controller view] removeFromSuperview]; 129 [infobarControllers_ removeObject:controller]; 130 [closingInfoBars_ removeObject:controller]; 131 [self positionInfoBarsAndRedraw]; 132 } 133 134 - (BrowserWindowController*)browserWindowController { 135 id controller = [[[self view] window] windowController]; 136 if (![controller isKindOfClass:[BrowserWindowController class]]) 137 return nil; 138 return controller; 139 } 140 141 - (void)changeWebContents:(content::WebContents*)contents { 142 registrar_.RemoveAll(); 143 [self removeAllInfoBars]; 144 145 currentWebContents_ = contents; 146 if (currentWebContents_) { 147 InfoBarService* infobarService = 148 InfoBarService::FromWebContents(currentWebContents_); 149 for (size_t i = 0; i < infobarService->infobar_count(); ++i) { 150 InfoBar* infobar = 151 infobarService->infobar_at(i)->CreateInfoBar(infobarService); 152 [self addInfoBar:infobar animate:NO]; 153 } 154 155 content::Source<InfoBarService> source(infobarService); 156 registrar_.Add(infoBarObserver_.get(), 157 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED, source); 158 registrar_.Add(infoBarObserver_.get(), 159 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, source); 160 registrar_.Add(infoBarObserver_.get(), 161 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED, source); 162 } 163 164 [self positionInfoBarsAndRedraw]; 165 } 166 167 - (void)tabDetachedWithContents:(content::WebContents*)contents { 168 if (currentWebContents_ == contents) 169 [self changeWebContents:NULL]; 170 } 171 172 - (NSUInteger)infobarCount { 173 return [infobarControllers_ count] - [closingInfoBars_ count]; 174 } 175 176 - (CGFloat)overlappingTipHeight { 177 return [self infobarCount] ? infobars::kTipHeight : 0; 178 } 179 180 - (void)resizeView:(NSView*)view newHeight:(CGFloat)height { 181 NSRect frame = [view frame]; 182 frame.size.height = height; 183 [view setFrame:frame]; 184 [self positionInfoBarsAndRedraw]; 185 } 186 187 - (void)setAnimationInProgress:(BOOL)inProgress { 188 if ([resizeDelegate_ respondsToSelector:@selector(setAnimationInProgress:)]) 189 [resizeDelegate_ setAnimationInProgress:inProgress]; 190 } 191 192 - (void)setShouldSuppressTopInfoBarTip:(BOOL)flag { 193 if (shouldSuppressTopInfoBarTip_ == flag) 194 return; 195 shouldSuppressTopInfoBarTip_ = flag; 196 [self positionInfoBarsAndRedraw]; 197 } 198 199 @end 200 201 @implementation InfoBarContainerController (PrivateMethods) 202 203 - (CGFloat)desiredHeight { 204 CGFloat height = 0; 205 206 // Take out the height of the tip from the total size of the infobar so that 207 // the tip overlaps the preceding infobar when there is more than one infobar. 208 for (InfoBarController* controller in infobarControllers_.get()) 209 height += NSHeight([[controller view] frame]) - infobars::kTipHeight; 210 211 // If there are any infobars, add a little extra room for the tip of the first 212 // infobar. 213 if (height) 214 height += infobars::kTipHeight; 215 216 return height; 217 } 218 219 - (void)addInfoBar:(InfoBar*)infobar animate:(BOOL)animate { 220 InfoBarController* controller = infobar->controller(); 221 [controller setContainerController:self]; 222 [[controller animatableView] setResizeDelegate:self]; 223 [[self view] addSubview:[controller view]]; 224 [infobarControllers_ addObject:[controller autorelease]]; 225 226 if (animate) 227 [controller animateOpen]; 228 else 229 [controller open]; 230 231 delete infobar; 232 } 233 234 - (void)closeInfoBarsForDelegate:(InfoBarDelegate*)delegate 235 animate:(BOOL)animate { 236 for (InfoBarController* controller in 237 [NSArray arrayWithArray:infobarControllers_.get()]) { 238 if ([controller delegate] == delegate) { 239 [controller infobarWillClose]; 240 if (animate) 241 [controller animateClosed]; 242 else 243 [controller close]; 244 } 245 } 246 } 247 248 - (void)removeAllInfoBars { 249 // stopAnimation can remove the infobar from infobarControllers_ if it was in 250 // the midst of closing, so copy the array so mutations won't cause problems. 251 for (InfoBarController* controller in 252 [NSArray arrayWithArray:infobarControllers_.get()]) { 253 [[controller animatableView] stopAnimation]; 254 // This code can be executed while InfoBarController is still on the stack, 255 // so we retain and autorelease the controller to prevent it from being 256 // dealloc'ed too early. 257 [[controller retain] autorelease]; 258 [[controller view] removeFromSuperview]; 259 } 260 [infobarControllers_ removeAllObjects]; 261 [closingInfoBars_ removeAllObjects]; 262 } 263 264 - (void)positionInfoBarsAndRedraw { 265 NSRect containerBounds = [[self view] bounds]; 266 int minY = 0; 267 268 // Stack the infobars at the bottom of the view, starting with the 269 // last infobar and working our way to the front of the array. This 270 // way we ensure that the first infobar added shows up on top, with 271 // the others below. 272 for (InfoBarController* controller in 273 [infobarControllers_ reverseObjectEnumerator]) { 274 NSView* view = [controller view]; 275 NSRect frame = [view frame]; 276 frame.origin.x = NSMinX(containerBounds); 277 frame.origin.y = minY; 278 frame.size.width = NSWidth(containerBounds); 279 [view setFrame:frame]; 280 281 minY += NSHeight(frame) - infobars::kTipHeight; 282 283 BOOL isTop = [controller isEqual:[infobarControllers_ objectAtIndex:0]]; 284 [controller setHasTip:!shouldSuppressTopInfoBarTip_ || !isTop]; 285 } 286 287 [resizeDelegate_ resizeView:[self view] newHeight:[self desiredHeight]]; 288 } 289 290 @end 291