Skip to content
Snippets Groups Projects
Commit fc701bfe authored by rohitrao@chromium.org's avatar rohitrao@chromium.org
Browse files

[Mac] Enables animations for the infobar. Changes the control flow for...

[Mac] Enables animations for the infobar.  Changes the control flow for infobar opening/closing to match Windows more closely.

Nib file changes:
- Embedded the InfoBarGradientView inside an AnimatableView.
- Rebound [controller view] to the AnimatableView and added an infoBarView_ IBOutlet.
- Bound the AnimatableView's delegate_ to the InfoBarController.

BUG=http://crbug.com/25599
TEST=Infobars should animate in and out, except for during tab switches.
Review URL: http://codereview.chromium.org/354008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30893 0039d316-1c4b-4281-b951-d872f2087c98
parent 5e36967c
No related merge requests found
This diff is collapsed.
......@@ -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;
......
......@@ -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];
}
......
......@@ -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];
......
......@@ -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
......
......@@ -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
......
......@@ -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_;
};
......
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment