• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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