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/bundle_locations.h"
      7 #include "base/mac/mac_util.h"
      8 #include "chrome/browser/chrome_notification_types.h"
      9 #include "chrome/browser/infobars/confirm_infobar_delegate.h"
     10 #include "chrome/browser/infobars/infobar.h"
     11 #include "chrome/browser/infobars/infobar_service.h"
     12 #import "chrome/browser/ui/cocoa/animatable_view.h"
     13 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
     14 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
     15 #import "chrome/browser/ui/cocoa/infobars/infobar_controller.h"
     16 #import "chrome/browser/ui/cocoa/view_id_util.h"
     17 #include "content/public/browser/notification_details.h"
     18 #include "content/public/browser/notification_source.h"
     19 #include "skia/ext/skia_utils_mac.h"
     20 
     21 // C++ class that receives INFOBAR_ADDED and INFOBAR_REMOVED
     22 // notifications and proxies them back to |controller|.
     23 class InfoBarNotificationObserver : public content::NotificationObserver {
     24  public:
     25   InfoBarNotificationObserver(InfoBarContainerController* controller)
     26       : controller_(controller) {
     27   }
     28 
     29  private:
     30   // NotificationObserver implementation
     31   virtual void Observe(
     32       int type,
     33       const content::NotificationSource& source,
     34       const content::NotificationDetails& details) OVERRIDE {
     35     InfoBarService* infobar_service =
     36         content::Source<InfoBarService>(source).ptr();
     37     switch (type) {
     38       case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED:
     39         [controller_ addInfoBar:content::Details<InfoBarAddedDetails>(details)->
     40                                     CreateInfoBar(infobar_service)
     41                         animate:YES];
     42         break;
     43 
     44       case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED: {
     45         InfoBarRemovedDetails* removed_details =
     46             content::Details<InfoBarRemovedDetails>(details).ptr();
     47         [controller_
     48             closeInfoBarsForDelegate:removed_details->first
     49                              animate:(removed_details->second ? YES : NO)];
     50         break;
     51       }
     52 
     53       case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED: {
     54         InfoBarReplacedDetails* replaced_details =
     55             content::Details<InfoBarReplacedDetails>(details).ptr();
     56         [controller_ closeInfoBarsForDelegate:replaced_details->first
     57                                       animate:NO];
     58         [controller_ addInfoBar:replaced_details->second->
     59                                     CreateInfoBar(infobar_service)
     60                         animate:NO];
     61         break;
     62       }
     63 
     64       default:
     65         NOTREACHED();  // we don't ask for anything else!
     66         break;
     67     }
     68 
     69     [controller_ positionInfoBarsAndRedraw];
     70   }
     71 
     72   InfoBarContainerController* controller_;  // weak, owns us.
     73 };
     74 
     75 
     76 @interface InfoBarContainerController (PrivateMethods)
     77 // Returns the desired height of the container view, computed by
     78 // adding together the heights of all its subviews.
     79 - (CGFloat)desiredHeight;
     80 
     81 @end
     82 
     83 
     84 @implementation InfoBarContainerController
     85 
     86 @synthesize shouldSuppressTopInfoBarTip = shouldSuppressTopInfoBarTip_;
     87 
     88 - (id)initWithResizeDelegate:(id<ViewResizer>)resizeDelegate {
     89   DCHECK(resizeDelegate);
     90   if ((self = [super initWithNibName:@"InfoBarContainer"
     91                               bundle:base::mac::FrameworkBundle()])) {
     92     resizeDelegate_ = resizeDelegate;
     93     infoBarObserver_.reset(new InfoBarNotificationObserver(self));
     94 
     95     // NSMutableArray needs an initial capacity, and we rarely ever see
     96     // more than two infobars at a time, so that seems like a good choice.
     97     infobarControllers_.reset([[NSMutableArray alloc] initWithCapacity:2]);
     98     closingInfoBars_.reset([[NSMutableSet alloc] initWithCapacity:2]);
     99   }
    100   return self;
    101 }
    102 
    103 - (void)dealloc {
    104   DCHECK_EQ([infobarControllers_ count], 0U);
    105   DCHECK_EQ([closingInfoBars_ count], 0U);
    106   view_id_util::UnsetID([self view]);
    107   [super dealloc];
    108 }
    109 
    110 - (void)awakeFromNib {
    111   // The info bar container view is an ordinary NSView object, so we set its
    112   // ViewID here.
    113   view_id_util::SetID([self view], VIEW_ID_INFO_BAR_CONTAINER);
    114 }
    115 
    116 - (void)willRemoveController:(InfoBarController*)controller {
    117   [closingInfoBars_ addObject:controller];
    118 }
    119 
    120 - (void)removeController:(InfoBarController*)controller {
    121   if (![infobarControllers_ containsObject:controller])
    122     return;
    123 
    124   // This code can be executed while InfoBarController is still on the stack, so
    125   // we retain and autorelease the controller to prevent it from being
    126   // dealloc'ed too early.
    127   [[controller retain] autorelease];
    128   [[controller view] removeFromSuperview];
    129   [infobarControllers_ removeObject:controller];
    130   [closingInfoBars_ removeObject:controller];
    131   [self positionInfoBarsAndRedraw];
    132 }
    133 
    134 - (BrowserWindowController*)browserWindowController {
    135   id controller = [[[self view] window] windowController];
    136   if (![controller isKindOfClass:[BrowserWindowController class]])
    137     return nil;
    138   return controller;
    139 }
    140 
    141 - (void)changeWebContents:(content::WebContents*)contents {
    142   registrar_.RemoveAll();
    143   [self removeAllInfoBars];
    144 
    145   currentWebContents_ = contents;
    146   if (currentWebContents_) {
    147     InfoBarService* infobarService =
    148         InfoBarService::FromWebContents(currentWebContents_);
    149     for (size_t i = 0; i < infobarService->infobar_count(); ++i) {
    150       InfoBar* infobar =
    151           infobarService->infobar_at(i)->CreateInfoBar(infobarService);
    152       [self addInfoBar:infobar animate:NO];
    153     }
    154 
    155     content::Source<InfoBarService> source(infobarService);
    156     registrar_.Add(infoBarObserver_.get(),
    157                    chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED, source);
    158     registrar_.Add(infoBarObserver_.get(),
    159                    chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, source);
    160     registrar_.Add(infoBarObserver_.get(),
    161                    chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED, source);
    162   }
    163 
    164   [self positionInfoBarsAndRedraw];
    165 }
    166 
    167 - (void)tabDetachedWithContents:(content::WebContents*)contents {
    168   if (currentWebContents_ == contents)
    169     [self changeWebContents:NULL];
    170 }
    171 
    172 - (NSUInteger)infobarCount {
    173   return [infobarControllers_ count] - [closingInfoBars_ count];
    174 }
    175 
    176 - (CGFloat)overlappingTipHeight {
    177   return [self infobarCount] ? infobars::kTipHeight : 0;
    178 }
    179 
    180 - (void)resizeView:(NSView*)view newHeight:(CGFloat)height {
    181   NSRect frame = [view frame];
    182   frame.size.height = height;
    183   [view setFrame:frame];
    184   [self positionInfoBarsAndRedraw];
    185 }
    186 
    187 - (void)setAnimationInProgress:(BOOL)inProgress {
    188   if ([resizeDelegate_ respondsToSelector:@selector(setAnimationInProgress:)])
    189     [resizeDelegate_ setAnimationInProgress:inProgress];
    190 }
    191 
    192 - (void)setShouldSuppressTopInfoBarTip:(BOOL)flag {
    193   if (shouldSuppressTopInfoBarTip_ == flag)
    194     return;
    195   shouldSuppressTopInfoBarTip_ = flag;
    196   [self positionInfoBarsAndRedraw];
    197 }
    198 
    199 @end
    200 
    201 @implementation InfoBarContainerController (PrivateMethods)
    202 
    203 - (CGFloat)desiredHeight {
    204   CGFloat height = 0;
    205 
    206   // Take out the height of the tip from the total size of the infobar so that
    207   // the tip overlaps the preceding infobar when there is more than one infobar.
    208   for (InfoBarController* controller in infobarControllers_.get())
    209     height += NSHeight([[controller view] frame]) - infobars::kTipHeight;
    210 
    211   // If there are any infobars, add a little extra room for the tip of the first
    212   // infobar.
    213   if (height)
    214     height += infobars::kTipHeight;
    215 
    216   return height;
    217 }
    218 
    219 - (void)addInfoBar:(InfoBar*)infobar animate:(BOOL)animate {
    220   InfoBarController* controller = infobar->controller();
    221   [controller setContainerController:self];
    222   [[controller animatableView] setResizeDelegate:self];
    223   [[self view] addSubview:[controller view]];
    224   [infobarControllers_ addObject:[controller autorelease]];
    225 
    226   if (animate)
    227     [controller animateOpen];
    228   else
    229     [controller open];
    230 
    231   delete infobar;
    232 }
    233 
    234 - (void)closeInfoBarsForDelegate:(InfoBarDelegate*)delegate
    235                          animate:(BOOL)animate {
    236   for (InfoBarController* controller in
    237        [NSArray arrayWithArray:infobarControllers_.get()]) {
    238     if ([controller delegate] == delegate) {
    239       [controller infobarWillClose];
    240       if (animate)
    241         [controller animateClosed];
    242       else
    243         [controller close];
    244     }
    245   }
    246 }
    247 
    248 - (void)removeAllInfoBars {
    249   // stopAnimation can remove the infobar from infobarControllers_ if it was in
    250   // the midst of closing, so copy the array so mutations won't cause problems.
    251   for (InfoBarController* controller in
    252        [NSArray arrayWithArray:infobarControllers_.get()]) {
    253     [[controller animatableView] stopAnimation];
    254     // This code can be executed while InfoBarController is still on the stack,
    255     // so we retain and autorelease the controller to prevent it from being
    256     // dealloc'ed too early.
    257     [[controller retain] autorelease];
    258     [[controller view] removeFromSuperview];
    259   }
    260   [infobarControllers_ removeAllObjects];
    261   [closingInfoBars_ removeAllObjects];
    262 }
    263 
    264 - (void)positionInfoBarsAndRedraw {
    265   NSRect containerBounds = [[self view] bounds];
    266   int minY = 0;
    267 
    268   // Stack the infobars at the bottom of the view, starting with the
    269   // last infobar and working our way to the front of the array.  This
    270   // way we ensure that the first infobar added shows up on top, with
    271   // the others below.
    272   for (InfoBarController* controller in
    273            [infobarControllers_ reverseObjectEnumerator]) {
    274     NSView* view = [controller view];
    275     NSRect frame = [view frame];
    276     frame.origin.x = NSMinX(containerBounds);
    277     frame.origin.y = minY;
    278     frame.size.width = NSWidth(containerBounds);
    279     [view setFrame:frame];
    280 
    281     minY += NSHeight(frame) - infobars::kTipHeight;
    282 
    283     BOOL isTop = [controller isEqual:[infobarControllers_ objectAtIndex:0]];
    284     [controller setHasTip:!shouldSuppressTopInfoBarTip_ || !isTop];
    285   }
    286 
    287   [resizeDelegate_ resizeView:[self view] newHeight:[self desiredHeight]];
    288 }
    289 
    290 @end
    291