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