summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/infobar_container_controller.h30
-rw-r--r--chrome/browser/cocoa/infobar_container_controller.mm55
-rw-r--r--chrome/browser/cocoa/infobar_container_controller_unittest.mm18
-rw-r--r--chrome/browser/cocoa/infobar_controller.h20
-rw-r--r--chrome/browser/cocoa/infobar_controller.mm99
-rw-r--r--chrome/browser/cocoa/infobar_controller_unittest.mm44
-rw-r--r--chrome/browser/cocoa/view_resizer.h2
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