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/mac_util.h" 7 #include "chrome/browser/tab_contents/confirm_infobar_delegate.h" 8 #import "chrome/browser/ui/cocoa/animatable_view.h" 9 #include "chrome/browser/ui/cocoa/infobars/infobar.h" 10 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 11 #import "chrome/browser/ui/cocoa/infobars/infobar_controller.h" 12 #import "chrome/browser/ui/cocoa/view_id_util.h" 13 #include "content/browser/tab_contents/tab_contents.h" 14 #include "content/common/notification_details.h" 15 #include "content/common/notification_source.h" 16 #include "skia/ext/skia_utils_mac.h" 17 18 // C++ class that receives INFOBAR_ADDED and INFOBAR_REMOVED 19 // notifications and proxies them back to |controller|. 20 class InfoBarNotificationObserver : public NotificationObserver { 21 public: 22 InfoBarNotificationObserver(InfoBarContainerController* controller) 23 : controller_(controller) { 24 } 25 26 private: 27 // NotificationObserver implementation 28 void Observe(NotificationType type, 29 const NotificationSource& source, 30 const NotificationDetails& details) { 31 switch (type.value) { 32 case NotificationType::TAB_CONTENTS_INFOBAR_ADDED: 33 [controller_ addInfoBar:Details<InfoBarDelegate>(details).ptr() 34 animate:YES]; 35 break; 36 case NotificationType::TAB_CONTENTS_INFOBAR_REMOVED: 37 [controller_ 38 closeInfoBarsForDelegate:Details<InfoBarDelegate>(details).ptr() 39 animate:YES]; 40 break; 41 case NotificationType::TAB_CONTENTS_INFOBAR_REPLACED: { 42 typedef std::pair<InfoBarDelegate*, InfoBarDelegate*> 43 InfoBarDelegatePair; 44 InfoBarDelegatePair* delegates = 45 Details<InfoBarDelegatePair>(details).ptr(); 46 [controller_ 47 replaceInfoBarsForDelegate:delegates->first with:delegates->second]; 48 break; 49 } 50 default: 51 NOTREACHED(); // we don't ask for anything else! 52 break; 53 } 54 55 [controller_ positionInfoBarsAndRedraw]; 56 } 57 58 InfoBarContainerController* controller_; // weak, owns us. 59 }; 60 61 62 @interface InfoBarContainerController (PrivateMethods) 63 // Returns the desired height of the container view, computed by 64 // adding together the heights of all its subviews. 65 - (CGFloat)desiredHeight; 66 67 @end 68 69 70 @implementation InfoBarContainerController 71 72 - (id)initWithResizeDelegate:(id<ViewResizer>)resizeDelegate { 73 DCHECK(resizeDelegate); 74 if ((self = [super initWithNibName:@"InfoBarContainer" 75 bundle:base::mac::MainAppBundle()])) { 76 resizeDelegate_ = resizeDelegate; 77 infoBarObserver_.reset(new InfoBarNotificationObserver(self)); 78 79 // NSMutableArray needs an initial capacity, and we rarely ever see 80 // more than two infobars at a time, so that seems like a good choice. 81 infobarControllers_.reset([[NSMutableArray alloc] initWithCapacity:2]); 82 closingInfoBars_.reset([[NSMutableSet alloc] initWithCapacity:2]); 83 } 84 return self; 85 } 86 87 - (void)dealloc { 88 DCHECK_EQ([infobarControllers_ count], 0U); 89 DCHECK_EQ([closingInfoBars_ count], 0U); 90 view_id_util::UnsetID([self view]); 91 [super dealloc]; 92 } 93 94 - (void)awakeFromNib { 95 // The info bar container view is an ordinary NSView object, so we set its 96 // ViewID here. 97 view_id_util::SetID([self view], VIEW_ID_INFO_BAR_CONTAINER); 98 } 99 100 - (void)removeDelegate:(InfoBarDelegate*)delegate { 101 DCHECK(currentTabContents_); 102 currentTabContents_->RemoveInfoBar(delegate); 103 } 104 105 - (void)willRemoveController:(InfoBarController*)controller { 106 [closingInfoBars_ addObject:controller]; 107 } 108 109 - (void)removeController:(InfoBarController*)controller { 110 if (![infobarControllers_ containsObject:controller]) 111 return; 112 113 // This code can be executed while InfoBarController is still on the stack, so 114 // we retain and autorelease the controller to prevent it from being 115 // dealloc'ed too early. 116 [[controller retain] autorelease]; 117 [[controller view] removeFromSuperview]; 118 [infobarControllers_ removeObject:controller]; 119 [closingInfoBars_ removeObject:controller]; 120 [self positionInfoBarsAndRedraw]; 121 } 122 123 - (void)changeTabContents:(TabContents*)contents { 124 registrar_.RemoveAll(); 125 [self removeAllInfoBars]; 126 127 currentTabContents_ = contents; 128 if (currentTabContents_) { 129 for (size_t i = 0; i < currentTabContents_->infobar_count(); ++i) { 130 [self addInfoBar:currentTabContents_->GetInfoBarDelegateAt(i) 131 animate:NO]; 132 } 133 134 Source<TabContents> source(currentTabContents_); 135 registrar_.Add(infoBarObserver_.get(), 136 NotificationType::TAB_CONTENTS_INFOBAR_ADDED, source); 137 registrar_.Add(infoBarObserver_.get(), 138 NotificationType::TAB_CONTENTS_INFOBAR_REMOVED, source); 139 registrar_.Add(infoBarObserver_.get(), 140 NotificationType::TAB_CONTENTS_INFOBAR_REPLACED, source); 141 } 142 143 [self positionInfoBarsAndRedraw]; 144 } 145 146 - (void)tabDetachedWithContents:(TabContents*)contents { 147 if (currentTabContents_ == contents) 148 [self changeTabContents:NULL]; 149 } 150 151 - (NSUInteger)infobarCount { 152 return [infobarControllers_ count] - [closingInfoBars_ count]; 153 } 154 155 - (CGFloat)antiSpoofHeight { 156 return 0; 157 } 158 159 - (void)resizeView:(NSView*)view newHeight:(CGFloat)height { 160 NSRect frame = [view frame]; 161 frame.size.height = height; 162 [view setFrame:frame]; 163 [self positionInfoBarsAndRedraw]; 164 } 165 166 - (void)setAnimationInProgress:(BOOL)inProgress { 167 if ([resizeDelegate_ respondsToSelector:@selector(setAnimationInProgress:)]) 168 [resizeDelegate_ setAnimationInProgress:inProgress]; 169 } 170 171 @end 172 173 @implementation InfoBarContainerController (PrivateMethods) 174 175 - (CGFloat)desiredHeight { 176 CGFloat height = 0; 177 for (InfoBarController* controller in infobarControllers_.get()) 178 height += NSHeight([[controller view] frame]); 179 return height; 180 } 181 182 - (void)addInfoBar:(InfoBarDelegate*)delegate animate:(BOOL)animate { 183 scoped_ptr<InfoBar> infobar(delegate->CreateInfoBar()); 184 InfoBarController* controller = infobar->controller(); 185 [controller setContainerController:self]; 186 [[controller animatableView] setResizeDelegate:self]; 187 [[self view] addSubview:[controller view]]; 188 [infobarControllers_ addObject:[controller autorelease]]; 189 190 if (animate) 191 [controller animateOpen]; 192 else 193 [controller open]; 194 } 195 196 - (void)closeInfoBarsForDelegate:(InfoBarDelegate*)delegate 197 animate:(BOOL)animate { 198 for (InfoBarController* controller in 199 [NSArray arrayWithArray:infobarControllers_.get()]) { 200 if ([controller delegate] == delegate) { 201 [controller infobarWillClose]; 202 if (animate) 203 [controller animateClosed]; 204 else 205 [controller close]; 206 } 207 } 208 } 209 210 - (void)replaceInfoBarsForDelegate:(InfoBarDelegate*)old_delegate 211 with:(InfoBarDelegate*)new_delegate { 212 [self closeInfoBarsForDelegate:old_delegate animate:NO]; 213 [self addInfoBar:new_delegate animate:NO]; 214 } 215 216 - (void)removeAllInfoBars { 217 for (InfoBarController* controller in infobarControllers_.get()) { 218 [[controller animatableView] stopAnimation]; 219 [[controller view] removeFromSuperview]; 220 } 221 [infobarControllers_ removeAllObjects]; 222 [closingInfoBars_ removeAllObjects]; 223 } 224 225 - (void)positionInfoBarsAndRedraw { 226 NSRect containerBounds = [[self view] bounds]; 227 int minY = 0; 228 229 // Stack the infobars at the bottom of the view, starting with the 230 // last infobar and working our way to the front of the array. This 231 // way we ensure that the first infobar added shows up on top, with 232 // the others below. 233 for (InfoBarController* controller in 234 [infobarControllers_ reverseObjectEnumerator]) { 235 NSView* view = [controller view]; 236 NSRect frame = [view frame]; 237 frame.origin.x = NSMinX(containerBounds); 238 frame.origin.y = minY; 239 frame.size.width = NSWidth(containerBounds); 240 [view setFrame:frame]; 241 242 minY += NSHeight(frame); 243 } 244 245 [resizeDelegate_ resizeView:[self view] newHeight:[self desiredHeight]]; 246 } 247 248 @end 249