| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/logging.h" |
| #include "base/mac_util.h" |
| #include "chrome/browser/tab_contents/infobar_delegate.h" |
| #include "chrome/browser/tab_contents/tab_contents.h" |
| #import "chrome/browser/ui/cocoa/animatable_view.h" |
| #include "chrome/browser/ui/cocoa/infobar.h" |
| #import "chrome/browser/ui/cocoa/infobar_container_controller.h" |
| #import "chrome/browser/ui/cocoa/infobar_controller.h" |
| #import "chrome/browser/ui/cocoa/view_id_util.h" |
| #include "chrome/common/notification_details.h" |
| #include "chrome/common/notification_source.h" |
| #include "skia/ext/skia_utils_mac.h" |
| |
| // C++ class that receives INFOBAR_ADDED and INFOBAR_REMOVED |
| // notifications and proxies them back to |controller|. |
| class InfoBarNotificationObserver : public NotificationObserver { |
| public: |
| InfoBarNotificationObserver(InfoBarContainerController* controller) |
| : controller_(controller) { |
| } |
| |
| private: |
| // NotificationObserver implementation |
| void Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| switch (type.value) { |
| case NotificationType::TAB_CONTENTS_INFOBAR_ADDED: |
| [controller_ addInfoBar:Details<InfoBarDelegate>(details).ptr() |
| animate:YES]; |
| break; |
| case NotificationType::TAB_CONTENTS_INFOBAR_REMOVED: |
| [controller_ |
| closeInfoBarsForDelegate:Details<InfoBarDelegate>(details).ptr() |
| animate:YES]; |
| break; |
| case NotificationType::TAB_CONTENTS_INFOBAR_REPLACED: { |
| typedef std::pair<InfoBarDelegate*, InfoBarDelegate*> |
| InfoBarDelegatePair; |
| InfoBarDelegatePair* delegates = |
| Details<InfoBarDelegatePair>(details).ptr(); |
| [controller_ |
| replaceInfoBarsForDelegate:delegates->first with:delegates->second]; |
| break; |
| } |
| default: |
| NOTREACHED(); // we don't ask for anything else! |
| break; |
| } |
| |
| [controller_ positionInfoBarsAndRedraw]; |
| } |
| |
| InfoBarContainerController* controller_; // weak, owns us. |
| }; |
| |
| |
| @interface InfoBarContainerController (PrivateMethods) |
| // Returns the desired height of the container view, computed by |
| // adding together the heights of all its subviews. |
| - (CGFloat)desiredHeight; |
| |
| @end |
| |
| |
| @implementation InfoBarContainerController |
| - (id)initWithResizeDelegate:(id<ViewResizer>)resizeDelegate { |
| DCHECK(resizeDelegate); |
| if ((self = [super initWithNibName:@"InfoBarContainer" |
| bundle:mac_util::MainAppBundle()])) { |
| resizeDelegate_ = resizeDelegate; |
| infoBarObserver_.reset(new InfoBarNotificationObserver(self)); |
| |
| // NSMutableArray needs an initial capacity, and we rarely ever see |
| // more than two infobars at a time, so that seems like a good choice. |
| infobarControllers_.reset([[NSMutableArray alloc] initWithCapacity:2]); |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| DCHECK([infobarControllers_ count] == 0); |
| view_id_util::UnsetID([self view]); |
| [super dealloc]; |
| } |
| |
| - (void)awakeFromNib { |
| // The info bar container view is an ordinary NSView object, so we set its |
| // ViewID here. |
| view_id_util::SetID([self view], VIEW_ID_INFO_BAR_CONTAINER); |
| } |
| |
| - (void)removeDelegate:(InfoBarDelegate*)delegate { |
| DCHECK(currentTabContents_); |
| currentTabContents_->RemoveInfoBar(delegate); |
| } |
| |
| - (void)removeController:(InfoBarController*)controller { |
| if (![infobarControllers_ containsObject:controller]) |
| return; |
| |
| // This code can be executed while InfoBarController is still on the stack, so |
| // we retain and autorelease the controller to prevent it from being |
| // dealloc'ed too early. |
| [[controller retain] autorelease]; |
| [[controller view] removeFromSuperview]; |
| [infobarControllers_ removeObject:controller]; |
| [self positionInfoBarsAndRedraw]; |
| } |
| |
| - (void)changeTabContents:(TabContents*)contents { |
| registrar_.RemoveAll(); |
| [self removeAllInfoBars]; |
| |
| currentTabContents_ = contents; |
| if (currentTabContents_) { |
| for (int i = 0; i < currentTabContents_->infobar_delegate_count(); ++i) { |
| [self addInfoBar:currentTabContents_->GetInfoBarDelegateAt(i) |
| animate:NO]; |
| } |
| |
| Source<TabContents> source(currentTabContents_); |
| registrar_.Add(infoBarObserver_.get(), |
| NotificationType::TAB_CONTENTS_INFOBAR_ADDED, source); |
| registrar_.Add(infoBarObserver_.get(), |
| NotificationType::TAB_CONTENTS_INFOBAR_REMOVED, source); |
| registrar_.Add(infoBarObserver_.get(), |
| NotificationType::TAB_CONTENTS_INFOBAR_REPLACED, source); |
| } |
| |
| [self positionInfoBarsAndRedraw]; |
| } |
| |
| - (void)tabDetachedWithContents:(TabContents*)contents { |
| if (currentTabContents_ == contents) |
| [self changeTabContents:NULL]; |
| } |
| |
| - (void)resizeView:(NSView*)view newHeight:(CGFloat)height { |
| NSRect frame = [view frame]; |
| frame.size.height = height; |
| [view setFrame:frame]; |
| [self positionInfoBarsAndRedraw]; |
| } |
| |
| - (void)setAnimationInProgress:(BOOL)inProgress { |
| if ([resizeDelegate_ respondsToSelector:@selector(setAnimationInProgress:)]) |
| [resizeDelegate_ setAnimationInProgress:inProgress]; |
| } |
| |
| @end |
| |
| @implementation InfoBarContainerController (PrivateMethods) |
| |
| - (CGFloat)desiredHeight { |
| CGFloat height = 0; |
| for (InfoBarController* controller in infobarControllers_.get()) |
| height += NSHeight([[controller view] frame]); |
| return height; |
| } |
| |
| - (void)addInfoBar:(InfoBarDelegate*)delegate animate:(BOOL)animate { |
| scoped_ptr<InfoBar> infobar(delegate->CreateInfoBar()); |
| InfoBarController* controller = infobar->controller(); |
| [controller setContainerController:self]; |
| [[controller animatableView] setResizeDelegate:self]; |
| [[self view] addSubview:[controller view]]; |
| [infobarControllers_ addObject:[controller autorelease]]; |
| |
| if (animate) |
| [controller animateOpen]; |
| else |
| [controller open]; |
| } |
| |
| - (void)closeInfoBarsForDelegate:(InfoBarDelegate*)delegate |
| animate:(BOOL)animate { |
| for (InfoBarController* controller in |
| [NSArray arrayWithArray:infobarControllers_.get()]) { |
| if ([controller delegate] == delegate) { |
| if (animate) |
| [controller animateClosed]; |
| else |
| [controller close]; |
| } |
| } |
| } |
| |
| - (void)replaceInfoBarsForDelegate:(InfoBarDelegate*)old_delegate |
| with:(InfoBarDelegate*)new_delegate { |
| [self closeInfoBarsForDelegate:old_delegate animate:NO]; |
| [self addInfoBar:new_delegate animate:NO]; |
| } |
| |
| - (void)removeAllInfoBars { |
| for (InfoBarController* controller in infobarControllers_.get()) { |
| [[controller animatableView] stopAnimation]; |
| [[controller view] removeFromSuperview]; |
| } |
| [infobarControllers_ removeAllObjects]; |
| } |
| |
| - (void)positionInfoBarsAndRedraw { |
| NSRect containerBounds = [[self view] bounds]; |
| int minY = 0; |
| |
| // Stack the infobars at the bottom of the view, starting with the |
| // last infobar and working our way to the front of the array. This |
| // way we ensure that the first infobar added shows up on top, with |
| // the others below. |
| for (InfoBarController* controller in |
| [infobarControllers_ reverseObjectEnumerator]) { |
| NSView* view = [controller view]; |
| NSRect frame = [view frame]; |
| frame.origin.x = NSMinX(containerBounds); |
| frame.size.width = NSWidth(containerBounds); |
| frame.origin.y = minY; |
| minY += frame.size.height; |
| [view setFrame:frame]; |
| } |
| |
| [resizeDelegate_ resizeView:[self view] newHeight:[self desiredHeight]]; |
| } |
| |
| @end |