diff options
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/cocoa/infobar_container_controller.h | 30 | ||||
-rw-r--r-- | chrome/browser/cocoa/infobar_container_controller.mm | 55 | ||||
-rw-r--r-- | chrome/browser/cocoa/infobar_container_controller_unittest.mm | 18 | ||||
-rw-r--r-- | chrome/browser/cocoa/infobar_controller.h | 20 | ||||
-rw-r--r-- | chrome/browser/cocoa/infobar_controller.mm | 99 | ||||
-rw-r--r-- | chrome/browser/cocoa/infobar_controller_unittest.mm | 44 | ||||
-rw-r--r-- | chrome/browser/cocoa/view_resizer.h | 2 |
7 files changed, 216 insertions, 52 deletions
diff --git a/chrome/browser/cocoa/infobar_container_controller.h b/chrome/browser/cocoa/infobar_container_controller.h index 22077ac..81e82f4 100644 --- a/chrome/browser/cocoa/infobar_container_controller.h +++ b/chrome/browser/cocoa/infobar_container_controller.h @@ -9,18 +9,27 @@ #import "chrome/browser/cocoa/view_resizer.h" #include "chrome/common/notification_registrar.h" +@class InfoBarController; class InfoBarDelegate; class InfoBarNotificationObserver; class TabContents; class TabStripModel; class TabStripModelObserverBridge; +// Protocol for basic container methods, as needed by an InfoBarController. +// This protocol exists to make mocking easier in unittests. +@protocol InfoBarContainer +- (void)removeDelegate:(InfoBarDelegate*)delegate; +- (void)removeController:(InfoBarController*)controller; +@end + // Controller for the infobar container view, which is the superview // of all the infobar views. This class owns zero or more // InfoBarControllers, which manage the infobar views. This class // also receives tab strip model notifications and handles // adding/removing infobars when needed. -@interface InfoBarContainerController : NSViewController { +@interface InfoBarContainerController : NSViewController <ViewResizer, + InfoBarContainer> { @private // Needed to send resize messages when infobars are added or removed. id<ViewResizer> resizeDelegate_; // weak @@ -51,21 +60,26 @@ class TabStripModelObserverBridge; // infobar was closed. - (void)removeDelegate:(InfoBarDelegate*)delegate; +// Removes |controller| from the list of controllers in this container and +// removes its view from the view hierarchy. This method is safe to call while +// |controller| is still on the call stack. +- (void)removeController:(InfoBarController*)controller; + @end @interface InfoBarContainerController (ForTheObserverAndTesting) -// Adds an infobar view for the given delegate. Callers must call -// positionInfoBarsAndRedraw after calling this method. -- (void)addInfoBar:(InfoBarDelegate*)delegate; +// Adds an infobar view for the given delegate. +- (void)addInfoBar:(InfoBarDelegate*)delegate animate:(BOOL)animate; -// Removes all the infobar views for a given delegate. Callers must -// call positionInfoBarsAndRedraw after calling this method. -- (void)removeInfoBarsForDelegate:(InfoBarDelegate*)delegate; +// Closes all the infobar views for a given delegate, either immediately or by +// starting a close animation. +- (void)closeInfoBarsForDelegate:(InfoBarDelegate*)delegate + animate:(BOOL)animate; // Replaces all info bars for the delegate with a new info bar. -// This simply calls removeInfoBarsForDelegate: and then addInfoBar:. +// This simply calls closeInfoBarsForDelegate: and then addInfoBar:. - (void)replaceInfoBarsForDelegate:(InfoBarDelegate*)old_delegate with:(InfoBarDelegate*)new_delegate; diff --git a/chrome/browser/cocoa/infobar_container_controller.mm b/chrome/browser/cocoa/infobar_container_controller.mm index 832a014..b09c580 100644 --- a/chrome/browser/cocoa/infobar_container_controller.mm +++ b/chrome/browser/cocoa/infobar_container_controller.mm @@ -4,6 +4,7 @@ #include "base/logging.h" #include "base/mac_util.h" +#import "chrome/browser/cocoa/animatable_view.h" #include "chrome/browser/cocoa/infobar.h" #import "chrome/browser/cocoa/infobar_container_controller.h" #import "chrome/browser/cocoa/infobar_controller.h" @@ -27,11 +28,13 @@ class InfoBarNotificationObserver : public NotificationObserver { const NotificationDetails& details) { switch (type.value) { case NotificationType::TAB_CONTENTS_INFOBAR_ADDED: - [controller_ addInfoBar:Details<InfoBarDelegate>(details).ptr()]; + [controller_ addInfoBar:Details<InfoBarDelegate>(details).ptr() + animate:YES]; break; case NotificationType::TAB_CONTENTS_INFOBAR_REMOVED: [controller_ - removeInfoBarsForDelegate:Details<InfoBarDelegate>(details).ptr()]; + closeInfoBarsForDelegate:Details<InfoBarDelegate>(details).ptr() + animate:YES]; break; case NotificationType::TAB_CONTENTS_INFOBAR_REPLACED: { typedef std::pair<InfoBarDelegate*, InfoBarDelegate*> @@ -97,6 +100,16 @@ class InfoBarNotificationObserver : public NotificationObserver { currentTabContents_->RemoveInfoBar(delegate); } +- (void)removeController:(InfoBarController*)controller { + // 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]; +} + // TabStripModelObserverBridge notifications - (void)selectTabWithContents:(TabContents*)newContents previousContents:(TabContents*)oldContents @@ -110,6 +123,13 @@ class InfoBarNotificationObserver : public NotificationObserver { [self changeTabContents:NULL]; } +- (void)resizeView:(NSView*)view newHeight:(float)height { + NSRect frame = [view frame]; + frame.size.height = height; + [view setFrame:frame]; + [self positionInfoBarsAndRedraw]; +} + @end @implementation InfoBarContainerController (PrivateMethods) @@ -131,7 +151,8 @@ class InfoBarNotificationObserver : public NotificationObserver { currentTabContents_ = contents; if (currentTabContents_) { for (int i = 0; i < currentTabContents_->infobar_delegate_count(); ++i) { - [self addInfoBar:currentTabContents_->GetInfoBarDelegateAt(i)]; + [self addInfoBar:currentTabContents_->GetInfoBarDelegateAt(i) + animate:NO]; } Source<TabContents> source(currentTabContents_); @@ -146,37 +167,42 @@ class InfoBarNotificationObserver : public NotificationObserver { [self positionInfoBarsAndRedraw]; } -- (void)addInfoBar:(InfoBarDelegate*)delegate { +- (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)removeInfoBarsForDelegate:(InfoBarDelegate*)delegate { +- (void)closeInfoBarsForDelegate:(InfoBarDelegate*)delegate + animate:(BOOL)animate { for (InfoBarController* controller in [NSArray arrayWithArray:infobarControllers_.get()]) { if ([controller delegate] == delegate) { - // This code can be executed while -[InfoBarController closeInfoBar] 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]; + if (animate) + [controller animateClosed]; + else + [controller close]; } } } - (void)replaceInfoBarsForDelegate:(InfoBarDelegate*)old_delegate with:(InfoBarDelegate*)new_delegate { - // TODO(rohitrao): This should avoid animation when we add it. - [self removeInfoBarsForDelegate:old_delegate]; - [self addInfoBar: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]; @@ -198,7 +224,6 @@ class InfoBarNotificationObserver : public NotificationObserver { frame.size.width = NSWidth(containerBounds); frame.origin.y = minY; minY += frame.size.height; - // TODO(rohitrao, jrg): Replace with an animator. [view setFrame:frame]; } diff --git a/chrome/browser/cocoa/infobar_container_controller_unittest.mm b/chrome/browser/cocoa/infobar_container_controller_unittest.mm index 274ebec..f5a45b2 100644 --- a/chrome/browser/cocoa/infobar_container_controller_unittest.mm +++ b/chrome/browser/cocoa/infobar_container_controller_unittest.mm @@ -63,23 +63,23 @@ TEST_F(InfoBarContainerControllerTest, AddAndRemoveInfoBars) { MockLinkInfoBarDelegate linkDelegate; MockConfirmInfoBarDelegate confirmDelegate; - [controller_ addInfoBar:&alertDelegate]; + [controller_ addInfoBar:&alertDelegate animate:NO]; EXPECT_EQ(1U, [[view subviews] count]); - [controller_ addInfoBar:&linkDelegate]; + [controller_ addInfoBar:&linkDelegate animate:NO]; EXPECT_EQ(2U, [[view subviews] count]); - [controller_ addInfoBar:&confirmDelegate]; + [controller_ addInfoBar:&confirmDelegate animate:NO]; EXPECT_EQ(3U, [[view subviews] count]); // Just to mix things up, remove them in a different order. - [controller_ removeInfoBarsForDelegate:&linkDelegate]; + [controller_ closeInfoBarsForDelegate:&linkDelegate animate:NO]; EXPECT_EQ(2U, [[view subviews] count]); - [controller_ removeInfoBarsForDelegate:&confirmDelegate]; + [controller_ closeInfoBarsForDelegate:&confirmDelegate animate:NO]; EXPECT_EQ(1U, [[view subviews] count]); - [controller_ removeInfoBarsForDelegate:&alertDelegate]; + [controller_ closeInfoBarsForDelegate:&alertDelegate animate:NO]; EXPECT_EQ(0U, [[view subviews] count]); } @@ -92,9 +92,9 @@ TEST_F(InfoBarContainerControllerTest, RemoveAllInfoBars) { MockLinkInfoBarDelegate linkDelegate; MockConfirmInfoBarDelegate confirmDelegate; - [controller_ addInfoBar:&alertDelegate]; - [controller_ addInfoBar:&linkDelegate]; - [controller_ addInfoBar:&confirmDelegate]; + [controller_ addInfoBar:&alertDelegate animate:NO]; + [controller_ addInfoBar:&linkDelegate animate:NO]; + [controller_ addInfoBar:&confirmDelegate animate:NO]; EXPECT_EQ(3U, [[view subviews] count]); [controller_ removeAllInfoBars]; diff --git a/chrome/browser/cocoa/infobar_controller.h b/chrome/browser/cocoa/infobar_controller.h index 8015067..8bd19ed 100644 --- a/chrome/browser/cocoa/infobar_controller.h +++ b/chrome/browser/cocoa/infobar_controller.h @@ -4,7 +4,8 @@ #import <Cocoa/Cocoa.h> -@class InfoBarContainerController; +@class AnimatableView; +@protocol InfoBarContainer; class InfoBarDelegate; // A controller for an infobar in the browser window. There is one @@ -13,10 +14,12 @@ class InfoBarDelegate; // override addAdditionalControls to customize the UI. @interface InfoBarController : NSViewController { @private - InfoBarContainerController* containerController_; // weak, owns us + id<InfoBarContainer> containerController_; // weak, owns us + BOOL infoBarClosing_; @protected InfoBarDelegate* delegate_; // weak + IBOutlet NSView* infoBarView_; IBOutlet NSImageView* image_; IBOutlet NSTextField* label_; IBOutlet NSButton* okButton_; @@ -35,12 +38,23 @@ class InfoBarDelegate; // infobar without taking any action. - (IBAction)dismiss:(id)sender; +// Returns a pointer to this controller's view, cast as an AnimatableView. +- (AnimatableView*)animatableView; + +// Open or animate open the infobar. +- (void)open; +- (void)animateOpen; + +// Close or animate close the infobar. +- (void)close; +- (void)animateClosed; + // Subclasses can override this method to add additional controls to // the infobar view. This method is called by awakeFromNib. The // default implementation does nothing. - (void)addAdditionalControls; -@property(assign, nonatomic) InfoBarContainerController* containerController; +@property(assign, nonatomic) id<InfoBarContainer> containerController; @property(readonly) InfoBarDelegate* delegate; @end diff --git a/chrome/browser/cocoa/infobar_controller.mm b/chrome/browser/cocoa/infobar_controller.mm index e8b1999..2f053b8b4 100644 --- a/chrome/browser/cocoa/infobar_controller.mm +++ b/chrome/browser/cocoa/infobar_controller.mm @@ -7,6 +7,7 @@ #include "base/logging.h" // for NOTREACHED() #include "base/mac_util.h" #include "base/sys_string_conversions.h" +#import "chrome/browser/cocoa/animatable_view.h" #include "chrome/browser/cocoa/event_utils.h" #include "chrome/browser/cocoa/infobar.h" #import "chrome/browser/cocoa/infobar_container_controller.h" @@ -16,14 +17,22 @@ #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" #include "webkit/glue/window_open_disposition.h" +namespace { +// Durations set to match the default SlideAnimation duration. +const float kAnimateOpenDuration = 0.12; +const float kAnimateCloseDuration = 0.12; +} @interface InfoBarController (PrivateMethods) -// Closes the infobar by calling RemoveDelegate on the container. -// This will remove the infobar from its associated TabContents as -// well as trigger the deletion of this InfoBarController. Once the -// delegate is removed from the container, it is no longer needed, so -// we ask it to delete itself. -- (void)closeInfoBar; +// Asks the container controller to remove the infobar for this delegate. This +// call will trigger a notification that starts the infobar animating closed. +- (void)removeInfoBar; + +// Performs final cleanup after an animation is finished or stopped, including +// notifying the InfoBarDelegate that the infobar was closed and removing the +// infobar from its container, if necessary. +- (void)cleanUpAfterAnimation:(BOOL)finished; + // Removes the ok and cancel buttons, and resizes the textfield to use the // space. - (void)removeButtons; @@ -76,7 +85,44 @@ // Called when someone clicks on the close button. - (void)dismiss:(id)sender { - [self closeInfoBar]; + [self removeInfoBar]; +} + +- (AnimatableView*)animatableView { + return static_cast<AnimatableView*>([self view]); +} + +- (void)open { + // Simply reset the frame size to its opened size, forcing a relayout. + CGFloat finalHeight = [[self view] frame].size.height; + [[self animatableView] setHeight:finalHeight]; +} + +- (void)animateOpen { + // Force the frame size to be 0 and then start an animation. + NSRect frame = [[self view] frame]; + CGFloat finalHeight = frame.size.height; + frame.size.height = 0; + [[self view] setFrame:frame]; + [[self animatableView] animateToNewHeight:finalHeight + duration:kAnimateOpenDuration]; +} + +- (void)close { + infoBarClosing_ = YES; + [self cleanUpAfterAnimation:YES]; +} + +- (void)animateClosed { + // Start animating closed. We will receive a notification when the animation + // is done, at which point we can remove our view from the hierarchy and + // notify the delegate that the infobar was closed. + [[self animatableView] animateToNewHeight:0 duration:kAnimateCloseDuration]; + + // The above call may trigger an animationDidStop: notification for any + // currently-running animations, so do not set |infoBarClosing_| until after + // starting the animation. + infoBarClosing_ = YES; } - (void)addAdditionalControls { @@ -87,14 +133,9 @@ @implementation InfoBarController (PrivateMethods) -- (void)closeInfoBar { - // Calling RemoveDelegate() triggers notifications which will remove - // the infobar view from the infobar container. At that point it is - // safe to ask the delegate to delete itself. +- (void)removeInfoBar { DCHECK(delegate_); [containerController_ removeDelegate:delegate_]; - delegate_->InfoBarClosed(); - delegate_ = NULL; } - (void)removeButtons { @@ -107,6 +148,32 @@ [label_ setFrame:labelFrame]; } +- (void)cleanUpAfterAnimation:(BOOL)finished { + // Don't need to do any cleanup if the bar was animating open. + if (!infoBarClosing_) + return; + + // Notify the delegate that the infobar was closed. The delegate may delete + // itself as a result of InfoBarClosed(), so we null out its pointer. + delegate_->InfoBarClosed(); + delegate_ = NULL; + + // If the animation ran to completion, then we need to remove ourselves from + // the container. If the animation was interrupted, then the container will + // take care of removing us. + // TODO(rohitrao): UGH! This works for now, but should be cleaner. + if (finished) + [containerController_ removeController:self]; +} + +- (void)animationDidStop:(NSAnimation*)animation { + [self cleanUpAfterAnimation:NO]; +} + +- (void)animationDidEnd:(NSAnimation*)animation { + [self cleanUpAfterAnimation:YES]; +} + @end @@ -203,7 +270,7 @@ WindowOpenDisposition disposition = event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); if (delegate_->AsLinkInfoBarDelegate()->LinkClicked(disposition)) - [self closeInfoBar]; + [self removeInfoBar]; } @end @@ -217,13 +284,13 @@ // Called when someone clicks on the "OK" button. - (IBAction)ok:(id)sender { if (delegate_->AsConfirmInfoBarDelegate()->Accept()) - [self closeInfoBar]; + [self removeInfoBar]; } // Called when someone clicks on the "Cancel" button. - (IBAction)cancel:(id)sender { if (delegate_->AsConfirmInfoBarDelegate()->Cancel()) - [self closeInfoBar]; + [self removeInfoBar]; } // Confirm infobars can have OK and/or cancel buttons, depending on diff --git a/chrome/browser/cocoa/infobar_controller_unittest.mm b/chrome/browser/cocoa/infobar_controller_unittest.mm index c6c45c9..9a55d21 100644 --- a/chrome/browser/cocoa/infobar_controller_unittest.mm +++ b/chrome/browser/cocoa/infobar_controller_unittest.mm @@ -8,6 +8,7 @@ #include "base/string_util.h" #include "base/sys_string_conversions.h" #import "chrome/browser/cocoa/cocoa_test_helper.h" +#import "chrome/browser/cocoa/infobar_container_controller.h" #import "chrome/browser/cocoa/infobar_controller.h" #include "chrome/browser/cocoa/infobar_test_helper.h" #include "chrome/browser/tab_contents/infobar_delegate.h" @@ -24,6 +25,37 @@ } @end + +// Calls to removeDelegate: normally start an animation, which removes the +// infobar completely when finished. For unittesting purposes, we create a mock +// container which calls close: immediately, rather than kicking off an +// animation. +@interface InfoBarContainerTest : NSObject <InfoBarContainer> { + InfoBarController* controller_; +} +- (id)initWithController:(InfoBarController*)controller; +- (void)removeDelegate:(InfoBarDelegate*)delegate; +- (void)removeController:(InfoBarController*)controller; +@end + +@implementation InfoBarContainerTest +- (id)initWithController:(InfoBarController*)controller { + if ((self = [super init])) { + controller_ = controller; + } + return self; +} + +- (void)removeDelegate:(InfoBarDelegate*)delegate { + [controller_ close]; +} + +- (void)removeController:(InfoBarController*)controller { + DCHECK(controller_ == controller); + controller_ = nil; +} +@end + namespace { /////////////////////////////////////////////////////////////////////////// @@ -36,12 +68,16 @@ class AlertInfoBarControllerTest : public PlatformTest { controller_.reset( [[AlertInfoBarController alloc] initWithDelegate:&delegate_]); + container_.reset( + [[InfoBarContainerTest alloc] initWithController:controller_]); + [controller_ setContainerController:container_]; [helper_.contentView() addSubview:[controller_ view]]; } protected: CocoaTestHelper helper_; MockAlertInfoBarDelegate delegate_; + scoped_nsobject<id> container_; scoped_nsobject<AlertInfoBarController> controller_; }; @@ -52,12 +88,16 @@ class LinkInfoBarControllerTest : public PlatformTest { controller_.reset( [[LinkInfoBarController alloc] initWithDelegate:&delegate_]); + container_.reset( + [[InfoBarContainerTest alloc] initWithController:controller_]); + [controller_ setContainerController:container_]; [helper_.contentView() addSubview:[controller_ view]]; } protected: CocoaTestHelper helper_; MockLinkInfoBarDelegate delegate_; + scoped_nsobject<id> container_; scoped_nsobject<LinkInfoBarController> controller_; }; @@ -68,12 +108,16 @@ class ConfirmInfoBarControllerTest : public PlatformTest { controller_.reset( [[ConfirmInfoBarController alloc] initWithDelegate:&delegate_]); + container_.reset( + [[InfoBarContainerTest alloc] initWithController:controller_]); + [controller_ setContainerController:container_]; [helper_.contentView() addSubview:[controller_ view]]; } protected: CocoaTestHelper helper_; MockConfirmInfoBarDelegate delegate_; + scoped_nsobject<id> container_; scoped_nsobject<ConfirmInfoBarController> controller_; }; diff --git a/chrome/browser/cocoa/view_resizer.h b/chrome/browser/cocoa/view_resizer.h index 199b60f..091222d 100644 --- a/chrome/browser/cocoa/view_resizer.h +++ b/chrome/browser/cocoa/view_resizer.h @@ -14,7 +14,7 @@ // than resizing it directly, it sends a message to its parent asking the parent // to perform the resize. This allows the parent to do any re-layout that may // become necessary due to the resize. -@protocol ViewResizer +@protocol ViewResizer <NSObject> - (void)resizeView:(NSView*)view newHeight:(float)height; @end |