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|. 20class 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