1 // Copyright (c) 2012 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.
5 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
7 #include "base/logging.h"
8 #include "base/mac/bundle_locations.h"
9 #include "base/mac/mac_util.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/strings/string_util.h"
12 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
13 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
14 #import "chrome/browser/ui/cocoa/tabs/tab_strip_model_observer_bridge.h"
15 #include "grit/generated_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
18 @interface BaseBubbleController (Private)
19 - (void)updateOriginFromAnchor;
20 - (void)activateTabWithContents:(content::WebContents*)newContents
21 previousContents:(content::WebContents*)oldContents
22 atIndex:(NSInteger)index
26 @implementation BaseBubbleController
28 @synthesize parentWindow = parentWindow_;
29 @synthesize anchorPoint = anchor_;
30 @synthesize bubble = bubble_;
31 @synthesize shouldOpenAsKeyWindow = shouldOpenAsKeyWindow_;
32 @synthesize shouldCloseOnResignKey = shouldCloseOnResignKey_;
34 - (id)initWithWindowNibPath:(NSString*)nibPath
35 parentWindow:(NSWindow*)parentWindow
36 anchoredAt:(NSPoint)anchoredAt {
37 nibPath = [base::mac::FrameworkBundle() pathForResource:nibPath
39 if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
40 parentWindow_ = parentWindow;
42 shouldOpenAsKeyWindow_ = YES;
43 shouldCloseOnResignKey_ = YES;
45 // Watch to see if the parent window closes, and if so, close this one.
46 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
47 [center addObserver:self
48 selector:@selector(parentWindowWillClose:)
49 name:NSWindowWillCloseNotification
50 object:parentWindow_];
55 - (id)initWithWindowNibPath:(NSString*)nibPath
56 relativeToView:(NSView*)view
57 offset:(NSPoint)offset {
58 DCHECK([view window]);
59 NSWindow* window = [view window];
60 NSRect bounds = [view convertRect:[view bounds] toView:nil];
61 NSPoint anchor = NSMakePoint(NSMinX(bounds) + offset.x,
62 NSMinY(bounds) + offset.y);
63 anchor = [window convertBaseToScreen:anchor];
64 return [self initWithWindowNibPath:nibPath
69 - (id)initWithWindow:(NSWindow*)theWindow
70 parentWindow:(NSWindow*)parentWindow
71 anchoredAt:(NSPoint)anchoredAt {
73 if ((self = [super initWithWindow:theWindow])) {
74 parentWindow_ = parentWindow;
76 shouldOpenAsKeyWindow_ = YES;
77 shouldCloseOnResignKey_ = YES;
79 DCHECK(![[self window] delegate]);
80 [theWindow setDelegate:self];
82 base::scoped_nsobject<InfoBubbleView> contentView(
83 [[InfoBubbleView alloc] initWithFrame:NSZeroRect]);
84 [theWindow setContentView:contentView.get()];
85 bubble_ = contentView.get();
87 // Watch to see if the parent window closes, and if so, close this one.
88 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
89 [center addObserver:self
90 selector:@selector(parentWindowWillClose:)
91 name:NSWindowWillCloseNotification
92 object:parentWindow_];
99 - (void)awakeFromNib {
100 // Check all connections have been made in Interface Builder.
101 DCHECK([self window]);
103 DCHECK_EQ(self, [[self window] delegate]);
105 BrowserWindowController* bwc =
106 [BrowserWindowController browserWindowControllerForWindow:parentWindow_];
108 TabStripController* tabStripController = [bwc tabStripController];
109 TabStripModel* tabStripModel = [tabStripController tabStripModel];
110 tabStripObserverBridge_.reset(new TabStripModelObserverBridge(tabStripModel,
114 [bubble_ setArrowLocation:info_bubble::kTopRight];
118 [[NSNotificationCenter defaultCenter] removeObserver:self];
122 - (void)setAnchorPoint:(NSPoint)anchor {
124 [self updateOriginFromAnchor];
127 - (NSBox*)separatorWithFrame:(NSRect)frame {
128 frame.size.height = 1.0;
129 base::scoped_nsobject<NSBox> spacer([[NSBox alloc] initWithFrame:frame]);
130 [spacer setBoxType:NSBoxSeparator];
131 [spacer setBorderType:NSLineBorder];
132 [spacer setAlphaValue:0.2];
133 return [spacer.release() autorelease];
136 - (void)parentWindowWillClose:(NSNotification*)notification {
141 - (void)windowWillClose:(NSNotification*)notification {
142 // We caught a close so we don't need to watch for the parent closing.
143 [[NSNotificationCenter defaultCenter] removeObserver:self];
147 // We want this to be a child of a browser window. addChildWindow:
148 // (called from this function) will bring the window on-screen;
149 // unfortunately, [NSWindowController showWindow:] will also bring it
150 // on-screen (but will cause unexpected changes to the window's
151 // position). We cannot have an addChildWindow: and a subsequent
152 // showWindow:. Thus, we have our own version.
153 - (void)showWindow:(id)sender {
154 NSWindow* window = [self window]; // Completes nib load.
155 [self updateOriginFromAnchor];
156 [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
157 if (shouldOpenAsKeyWindow_)
158 [window makeKeyAndOrderFront:self];
160 [window orderFront:nil];
161 [self registerKeyStateEventTap];
165 // The bubble will be closing, so remove the event taps.
167 [NSEvent removeMonitor:eventTap_];
170 if (resignationObserver_) {
171 [[NSNotificationCenter defaultCenter]
172 removeObserver:resignationObserver_
173 name:NSWindowDidResignKeyNotification
175 resignationObserver_ = nil;
178 tabStripObserverBridge_.reset();
180 [[[self window] parentWindow] removeChildWindow:[self window]];
184 // The controller is the delegate of the window so it receives did resign key
185 // notifications. When key is resigned mirror Windows behavior and close the
187 - (void)windowDidResignKey:(NSNotification*)notification {
188 NSWindow* window = [self window];
189 DCHECK_EQ([notification object], window);
190 if ([window isVisible] && [self shouldCloseOnResignKey]) {
191 // If the window isn't visible, it is already closed, and this notification
192 // has been sent as part of the closing operation, so no need to close.
197 // Since the bubble shares first responder with its parent window, set
198 // event handlers to dismiss the bubble when it would normally lose key
200 - (void)registerKeyStateEventTap {
201 // Parent key state sharing is only avaiable on 10.7+.
202 if (!base::mac::IsOSLionOrLater())
205 NSWindow* window = self.window;
206 NSNotification* note =
207 [NSNotification notificationWithName:NSWindowDidResignKeyNotification
210 // The eventTap_ catches clicks within the application that are outside the
213 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
214 handler:^NSEvent* (NSEvent* event) {
215 if (event.window != window) {
216 // Call via the runloop because this block is called in the
217 // middle of event dispatch.
218 [self performSelector:@selector(windowDidResignKey:)
225 // The resignationObserver_ watches for when a window resigns key state,
226 // meaning the key window has changed and the bubble should be dismissed.
227 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
228 resignationObserver_ =
229 [center addObserverForName:NSWindowDidResignKeyNotification
231 queue:[NSOperationQueue mainQueue]
232 usingBlock:^(NSNotification* notif) {
233 [self windowDidResignKey:note];
237 // By implementing this, ESC causes the window to go away.
238 - (IBAction)cancel:(id)sender {
239 // This is not a "real" cancel as potential changes to the radio group are not
240 // undone. That's ok.
244 // Takes the |anchor_| point and adjusts the window's origin accordingly.
245 - (void)updateOriginFromAnchor {
246 NSWindow* window = [self window];
247 NSPoint origin = anchor_;
249 switch ([bubble_ alignment]) {
250 case info_bubble::kAlignArrowToAnchor: {
251 NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
252 info_bubble::kBubbleArrowWidth / 2.0, 0);
253 offsets = [[parentWindow_ contentView] convertSize:offsets toView:nil];
254 switch ([bubble_ arrowLocation]) {
255 case info_bubble::kTopRight:
256 origin.x -= NSWidth([window frame]) - offsets.width;
258 case info_bubble::kTopLeft:
259 origin.x -= offsets.width;
261 case info_bubble::kTopCenter:
262 origin.x -= NSWidth([window frame]) / 2.0;
264 case info_bubble::kNoArrow:
271 case info_bubble::kAlignEdgeToAnchorEdge:
272 // If the arrow is to the right then move the origin so that the right
273 // edge aligns with the anchor. If the arrow is to the left then there's
274 // nothing to do because the left edge is already aligned with the left
275 // edge of the anchor.
276 if ([bubble_ arrowLocation] == info_bubble::kTopRight) {
277 origin.x -= NSWidth([window frame]);
281 case info_bubble::kAlignRightEdgeToAnchorEdge:
282 origin.x -= NSWidth([window frame]);
285 case info_bubble::kAlignLeftEdgeToAnchorEdge:
293 origin.y -= NSHeight([window frame]);
294 [window setFrameOrigin:origin];
297 - (void)activateTabWithContents:(content::WebContents*)newContents
298 previousContents:(content::WebContents*)oldContents
299 atIndex:(NSInteger)index
301 // The user switched tabs; close.
305 @end // BaseBubbleController