NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / base_bubble_controller.mm
blob2555f8839995c7ad1476c954e485b9de187a32af
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
23                          reason:(int)reason;
24 @end
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
38                                                    ofType:@"nib"];
39   if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
40     parentWindow_ = parentWindow;
41     anchor_ = anchoredAt;
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_];
51   }
52   return self;
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
65                         parentWindow:window
66                           anchoredAt:anchor];
69 - (id)initWithWindow:(NSWindow*)theWindow
70         parentWindow:(NSWindow*)parentWindow
71           anchoredAt:(NSPoint)anchoredAt {
72   DCHECK(theWindow);
73   if ((self = [super initWithWindow:theWindow])) {
74     parentWindow_ = parentWindow;
75     anchor_ = anchoredAt;
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_];
94     [self awakeFromNib];
95   }
96   return self;
99 - (void)awakeFromNib {
100   // Check all connections have been made in Interface Builder.
101   DCHECK([self window]);
102   DCHECK(bubble_);
103   DCHECK_EQ(self, [[self window] delegate]);
105   BrowserWindowController* bwc =
106       [BrowserWindowController browserWindowControllerForWindow:parentWindow_];
107   if (bwc) {
108     TabStripController* tabStripController = [bwc tabStripController];
109     TabStripModel* tabStripModel = [tabStripController tabStripModel];
110     tabStripObserverBridge_.reset(new TabStripModelObserverBridge(tabStripModel,
111                                                                   self));
112   }
114   [bubble_ setArrowLocation:info_bubble::kTopRight];
117 - (void)dealloc {
118   [[NSNotificationCenter defaultCenter] removeObserver:self];
119   [super dealloc];
122 - (void)setAnchorPoint:(NSPoint)anchor {
123   anchor_ = 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 {
137   parentWindow_ = nil;
138   [self close];
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];
144   [self autorelease];
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];
159   else
160     [window orderFront:nil];
161   [self registerKeyStateEventTap];
164 - (void)close {
165   // The bubble will be closing, so remove the event taps.
166   if (eventTap_) {
167     [NSEvent removeMonitor:eventTap_];
168     eventTap_ = nil;
169   }
170   if (resignationObserver_) {
171     [[NSNotificationCenter defaultCenter]
172         removeObserver:resignationObserver_
173                   name:NSWindowDidResignKeyNotification
174                 object:nil];
175     resignationObserver_ = nil;
176   }
178   tabStripObserverBridge_.reset();
180   [[[self window] parentWindow] removeChildWindow:[self window]];
181   [super close];
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
186 // window.
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.
193     [self close];
194   }
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
199 // state.
200 - (void)registerKeyStateEventTap {
201   // Parent key state sharing is only avaiable on 10.7+.
202   if (!base::mac::IsOSLionOrLater())
203     return;
205   NSWindow* window = self.window;
206   NSNotification* note =
207       [NSNotification notificationWithName:NSWindowDidResignKeyNotification
208                                     object:window];
210   // The eventTap_ catches clicks within the application that are outside the
211   // window.
212   eventTap_ = [NSEvent
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:)
219                        withObject:note
220                        afterDelay:0];
221           }
222           return event;
223       }];
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
230                           object:nil
231                            queue:[NSOperationQueue mainQueue]
232                       usingBlock:^(NSNotification* notif) {
233                           [self windowDidResignKey:note];
234                       }];
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.
241   [self close];
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;
257           break;
258         case info_bubble::kTopLeft:
259           origin.x -= offsets.width;
260           break;
261         case info_bubble::kTopCenter:
262           origin.x -= NSWidth([window frame]) / 2.0;
263           break;
264         case info_bubble::kNoArrow:
265           NOTREACHED();
266           break;
267       }
268       break;
269     }
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]);
278       }
279       break;
281     case info_bubble::kAlignRightEdgeToAnchorEdge:
282       origin.x -= NSWidth([window frame]);
283       break;
285     case info_bubble::kAlignLeftEdgeToAnchorEdge:
286       // Nothing to do.
287       break;
289     default:
290       NOTREACHED();
291   }
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
300                          reason:(int)reason {
301   // The user switched tabs; close.
302   [self close];
305 @end  // BaseBubbleController