Home | History | Annotate | Download | only in cocoa
      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 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/mac/mac_util.h"
      9 #include "base/memory/scoped_nsobject.h"
     10 #include "base/string_util.h"
     11 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
     12 #include "content/common/notification_observer.h"
     13 #include "content/common/notification_registrar.h"
     14 #include "content/common/notification_service.h"
     15 #include "content/common/notification_type.h"
     16 #include "grit/generated_resources.h"
     17 #include "ui/base/l10n/l10n_util.h"
     18 
     19 @interface BaseBubbleController (Private)
     20 - (void)updateOriginFromAnchor;
     21 @end
     22 
     23 namespace BaseBubbleControllerInternal {
     24 
     25 // This bridge listens for notifications so that the bubble closes when a user
     26 // switches tabs (including by opening a new one).
     27 class Bridge : public NotificationObserver {
     28  public:
     29   explicit Bridge(BaseBubbleController* controller) : controller_(controller) {
     30     registrar_.Add(this, NotificationType::TAB_CONTENTS_HIDDEN,
     31         NotificationService::AllSources());
     32   }
     33 
     34   // NotificationObserver:
     35   virtual void Observe(NotificationType type,
     36                        const NotificationSource& source,
     37                        const NotificationDetails& details) {
     38     [controller_ close];
     39   }
     40 
     41  private:
     42   BaseBubbleController* controller_;  // Weak, owns this.
     43   NotificationRegistrar registrar_;
     44 };
     45 
     46 }  // namespace BaseBubbleControllerInternal
     47 
     48 @implementation BaseBubbleController
     49 
     50 @synthesize parentWindow = parentWindow_;
     51 @synthesize anchorPoint = anchor_;
     52 @synthesize bubble = bubble_;
     53 
     54 - (id)initWithWindowNibPath:(NSString*)nibPath
     55                parentWindow:(NSWindow*)parentWindow
     56                  anchoredAt:(NSPoint)anchoredAt {
     57   nibPath = [base::mac::MainAppBundle() pathForResource:nibPath
     58                                                 ofType:@"nib"];
     59   if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
     60     parentWindow_ = parentWindow;
     61     anchor_ = anchoredAt;
     62 
     63     // Watch to see if the parent window closes, and if so, close this one.
     64     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
     65     [center addObserver:self
     66                selector:@selector(parentWindowWillClose:)
     67                    name:NSWindowWillCloseNotification
     68                  object:parentWindow_];
     69   }
     70   return self;
     71 }
     72 
     73 - (id)initWithWindowNibPath:(NSString*)nibPath
     74              relativeToView:(NSView*)view
     75                      offset:(NSPoint)offset {
     76   DCHECK([view window]);
     77   NSWindow* window = [view window];
     78   NSRect bounds = [view convertRect:[view bounds] toView:nil];
     79   NSPoint anchor = NSMakePoint(NSMinX(bounds) + offset.x,
     80                                NSMinY(bounds) + offset.y);
     81   anchor = [window convertBaseToScreen:anchor];
     82   return [self initWithWindowNibPath:nibPath
     83                         parentWindow:window
     84                           anchoredAt:anchor];
     85 }
     86 
     87 - (id)initWithWindow:(NSWindow*)theWindow
     88         parentWindow:(NSWindow*)parentWindow
     89           anchoredAt:(NSPoint)anchoredAt {
     90   DCHECK(theWindow);
     91   if ((self = [super initWithWindow:theWindow])) {
     92     parentWindow_ = parentWindow;
     93     anchor_ = anchoredAt;
     94 
     95     DCHECK(![[self window] delegate]);
     96     [theWindow setDelegate:self];
     97 
     98     scoped_nsobject<InfoBubbleView> contentView(
     99         [[InfoBubbleView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)]);
    100     [theWindow setContentView:contentView.get()];
    101     bubble_ = contentView.get();
    102 
    103     // Watch to see if the parent window closes, and if so, close this one.
    104     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
    105     [center addObserver:self
    106                selector:@selector(parentWindowWillClose:)
    107                    name:NSWindowWillCloseNotification
    108                  object:parentWindow_];
    109 
    110     [self awakeFromNib];
    111   }
    112   return self;
    113 }
    114 
    115 - (void)awakeFromNib {
    116   // Check all connections have been made in Interface Builder.
    117   DCHECK([self window]);
    118   DCHECK(bubble_);
    119   DCHECK_EQ(self, [[self window] delegate]);
    120 
    121   base_bridge_.reset(new BaseBubbleControllerInternal::Bridge(self));
    122 
    123   [bubble_ setArrowLocation:info_bubble::kTopRight];
    124 }
    125 
    126 - (void)dealloc {
    127   [[NSNotificationCenter defaultCenter] removeObserver:self];
    128   [super dealloc];
    129 }
    130 
    131 - (void)setAnchorPoint:(NSPoint)anchor {
    132   anchor_ = anchor;
    133   [self updateOriginFromAnchor];
    134 }
    135 
    136 - (void)parentWindowWillClose:(NSNotification*)notification {
    137   [self close];
    138 }
    139 
    140 - (void)windowWillClose:(NSNotification*)notification {
    141   // We caught a close so we don't need to watch for the parent closing.
    142   [[NSNotificationCenter defaultCenter] removeObserver:self];
    143   [self autorelease];
    144 }
    145 
    146 // We want this to be a child of a browser window.  addChildWindow:
    147 // (called from this function) will bring the window on-screen;
    148 // unfortunately, [NSWindowController showWindow:] will also bring it
    149 // on-screen (but will cause unexpected changes to the window's
    150 // position).  We cannot have an addChildWindow: and a subsequent
    151 // showWindow:. Thus, we have our own version.
    152 - (void)showWindow:(id)sender {
    153   NSWindow* window = [self window];  // completes nib load
    154   [self updateOriginFromAnchor];
    155   [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
    156   [window makeKeyAndOrderFront:self];
    157 }
    158 
    159 - (void)close {
    160   [parentWindow_ removeChildWindow:[self window]];
    161   [super close];
    162 }
    163 
    164 // The controller is the delegate of the window so it receives did resign key
    165 // notifications. When key is resigned mirror Windows behavior and close the
    166 // window.
    167 - (void)windowDidResignKey:(NSNotification*)notification {
    168   NSWindow* window = [self window];
    169   DCHECK_EQ([notification object], window);
    170   if ([window isVisible]) {
    171     // If the window isn't visible, it is already closed, and this notification
    172     // has been sent as part of the closing operation, so no need to close.
    173     [self close];
    174   }
    175 }
    176 
    177 // By implementing this, ESC causes the window to go away.
    178 - (IBAction)cancel:(id)sender {
    179   // This is not a "real" cancel as potential changes to the radio group are not
    180   // undone. That's ok.
    181   [self close];
    182 }
    183 
    184 // Takes the |anchor_| point and adjusts the window's origin accordingly.
    185 - (void)updateOriginFromAnchor {
    186   NSWindow* window = [self window];
    187   NSPoint origin = anchor_;
    188   NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
    189                               info_bubble::kBubbleArrowWidth / 2.0, 0);
    190   offsets = [[parentWindow_ contentView] convertSize:offsets toView:nil];
    191   if ([bubble_ arrowLocation] == info_bubble::kTopRight) {
    192     origin.x -= NSWidth([window frame]) - offsets.width;
    193   } else {
    194     origin.x -= offsets.width;
    195   }
    196   origin.y -= NSHeight([window frame]);
    197   [window setFrameOrigin:origin];
    198 }
    199 
    200 @end  // BaseBubbleController
    201