Home | History | Annotate | Download | only in infobars
      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|.
     20 class 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