Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / base_bubble_controller.mm
blob11b921226f8e8906ff8a87a92d6cfb78930414f4
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/foundation_util.h"
10 #include "base/mac/mac_util.h"
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/mac/sdk_forward_declarations.h"
13 #include "base/strings/string_util.h"
14 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
15 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
16 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
17 #import "chrome/browser/ui/cocoa/tabs/tab_strip_model_observer_bridge.h"
19 @interface BaseBubbleController (Private)
20 - (void)registerForNotifications;
21 - (void)updateOriginFromAnchor;
22 - (void)activateTabWithContents:(content::WebContents*)newContents
23                previousContents:(content::WebContents*)oldContents
24                         atIndex:(NSInteger)index
25                          reason:(int)reason;
26 - (void)recordAnchorOffset;
27 - (void)parentWindowDidResize:(NSNotification*)notification;
28 - (void)parentWindowWillClose:(NSNotification*)notification;
29 - (void)parentWindowWillToggleFullScreen:(NSNotification*)notification;
30 - (void)closeCleanup;
31 @end
33 @implementation BaseBubbleController
35 @synthesize anchorPoint = anchor_;
36 @synthesize bubble = bubble_;
37 @synthesize shouldOpenAsKeyWindow = shouldOpenAsKeyWindow_;
38 @synthesize shouldCloseOnResignKey = shouldCloseOnResignKey_;
40 - (id)initWithWindowNibPath:(NSString*)nibPath
41                parentWindow:(NSWindow*)parentWindow
42                  anchoredAt:(NSPoint)anchoredAt {
43   nibPath = [base::mac::FrameworkBundle() pathForResource:nibPath
44                                                    ofType:@"nib"];
45   if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
46     [self setParentWindow:parentWindow];
47     anchor_ = anchoredAt;
48     shouldOpenAsKeyWindow_ = YES;
49     shouldCloseOnResignKey_ = YES;
50   }
51   return self;
54 - (id)initWithWindowNibPath:(NSString*)nibPath
55              relativeToView:(NSView*)view
56                      offset:(NSPoint)offset {
57   DCHECK([view window]);
58   NSWindow* window = [view window];
59   NSRect bounds = [view convertRect:[view bounds] toView:nil];
60   NSPoint anchor = NSMakePoint(NSMinX(bounds) + offset.x,
61                                NSMinY(bounds) + offset.y);
62   anchor = [window convertBaseToScreen:anchor];
63   return [self initWithWindowNibPath:nibPath
64                         parentWindow:window
65                           anchoredAt:anchor];
68 - (id)initWithWindow:(NSWindow*)theWindow
69         parentWindow:(NSWindow*)parentWindow
70           anchoredAt:(NSPoint)anchoredAt {
71   DCHECK(theWindow);
72   if ((self = [super initWithWindow:theWindow])) {
73     [self setParentWindow:parentWindow];
74     shouldOpenAsKeyWindow_ = YES;
75     shouldCloseOnResignKey_ = YES;
77     DCHECK(![[self window] delegate]);
78     [theWindow setDelegate:self];
80     base::scoped_nsobject<InfoBubbleView> contentView(
81         [[InfoBubbleView alloc] initWithFrame:NSZeroRect]);
82     [theWindow setContentView:contentView.get()];
83     bubble_ = contentView.get();
85     [self awakeFromNib];
86     [self setAnchorPoint:anchoredAt];
87   }
88   return self;
91 - (void)awakeFromNib {
92   // Check all connections have been made in Interface Builder.
93   DCHECK([self window]);
94   DCHECK(bubble_);
95   DCHECK_EQ(self, [[self window] delegate]);
97   BrowserWindowController* bwc =
98       [BrowserWindowController browserWindowControllerForWindow:parentWindow_];
99   if (bwc) {
100     TabStripController* tabStripController = [bwc tabStripController];
101     TabStripModel* tabStripModel = [tabStripController tabStripModel];
102     tabStripObserverBridge_.reset(new TabStripModelObserverBridge(tabStripModel,
103                                                                   self));
104   }
106   [bubble_ setArrowLocation:info_bubble::kTopRight];
109 - (void)dealloc {
110   [[NSNotificationCenter defaultCenter] removeObserver:self];
111   [super dealloc];
114 - (void)registerForNotifications {
115   // No window to register notifications for.
116   if (!parentWindow_)
117     return;
119   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
120   // Watch to see if the parent window closes, and if so, close this one.
121   [center addObserver:self
122              selector:@selector(parentWindowWillClose:)
123                  name:NSWindowWillCloseNotification
124                object:parentWindow_];
125   // Watch for the full screen event, if so, close the bubble
126   [center addObserver:self
127              selector:@selector(parentWindowWillToggleFullScreen:)
128                  name:NSWindowWillEnterFullScreenNotification
129                object:parentWindow_];
130   // Watch for the full screen exit event, if so, close the bubble
131   [center addObserver:self
132              selector:@selector(parentWindowWillToggleFullScreen:)
133                  name:NSWindowWillExitFullScreenNotification
134                object:parentWindow_];
135   // Watch for parent window's resizing, to ensure this one is always
136   // anchored correctly.
137   [center addObserver:self
138              selector:@selector(parentWindowDidResize:)
139                  name:NSWindowDidResizeNotification
140                object:parentWindow_];
143 - (void)unregisterFromNotifications {
144   // No window to unregister notifications.
145   if (!parentWindow_)
146     return;
148   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
149   [center removeObserver:self
150                     name:NSWindowWillCloseNotification
151                   object:parentWindow_];
152   [center removeObserver:self
153                     name:NSWindowWillEnterFullScreenNotification
154                   object:parentWindow_];
155   [center removeObserver:self
156                     name:NSWindowWillExitFullScreenNotification
157                   object:parentWindow_];
158   [center removeObserver:self
159                     name:NSWindowDidResizeNotification
160                   object:parentWindow_];
163 - (NSWindow*)parentWindow {
164   return parentWindow_;
167 - (void)setParentWindow:(NSWindow*)parentWindow {
168   if (parentWindow_ == parentWindow) {
169     return;
170   }
172   [self unregisterFromNotifications];
174   if (parentWindow_ && [[self window] isVisible]) {
175     [parentWindow_ removeChildWindow:[self window]];
176     parentWindow_ = parentWindow;
177     [parentWindow_ addChildWindow:[self window] ordered:NSWindowAbove];
178   } else {
179     parentWindow_ = parentWindow;
180   }
182   [self registerForNotifications];
185 - (void)setAnchorPoint:(NSPoint)anchor {
186   anchor_ = anchor;
187   [self updateOriginFromAnchor];
190 - (void)recordAnchorOffset {
191   // The offset of the anchor from the parent's upper-left-hand corner is kept
192   // to ensure the bubble stays anchored correctly if the parent is resized.
193   anchorOffset_ = NSMakePoint(NSMinX([parentWindow_ frame]),
194                               NSMaxY([parentWindow_ frame]));
195   anchorOffset_.x -= anchor_.x;
196   anchorOffset_.y -= anchor_.y;
199 - (NSBox*)horizontalSeparatorWithFrame:(NSRect)frame {
200   frame.size.height = 1.0;
201   base::scoped_nsobject<NSBox> spacer([[NSBox alloc] initWithFrame:frame]);
202   [spacer setBoxType:NSBoxSeparator];
203   [spacer setBorderType:NSLineBorder];
204   [spacer setAlphaValue:0.2];
205   return [spacer.release() autorelease];
208 - (NSBox*)verticalSeparatorWithFrame:(NSRect)frame {
209   frame.size.width = 1.0;
210   base::scoped_nsobject<NSBox> spacer([[NSBox alloc] initWithFrame:frame]);
211   [spacer setBoxType:NSBoxSeparator];
212   [spacer setBorderType:NSLineBorder];
213   [spacer setAlphaValue:0.2];
214   return [spacer.release() autorelease];
217 - (void)parentWindowDidResize:(NSNotification*)notification {
218   if (!parentWindow_)
219     return;
221   DCHECK_EQ(parentWindow_, [notification object]);
222   NSPoint newOrigin = NSMakePoint(NSMinX([parentWindow_ frame]),
223                                   NSMaxY([parentWindow_ frame]));
224   newOrigin.x -= anchorOffset_.x;
225   newOrigin.y -= anchorOffset_.y;
226   [self setAnchorPoint:newOrigin];
229 - (void)parentWindowWillClose:(NSNotification*)notification {
230   [self setParentWindow:nil];
231   [self close];
234 - (void)parentWindowWillToggleFullScreen:(NSNotification*)notification {
235   [self setParentWindow:nil];
236   [self close];
239 - (void)closeCleanup {
240   if (eventTap_) {
241     [NSEvent removeMonitor:eventTap_];
242     eventTap_ = nil;
243   }
244   if (resignationObserver_) {
245     [[NSNotificationCenter defaultCenter]
246         removeObserver:resignationObserver_
247                   name:NSWindowDidResignKeyNotification
248                 object:nil];
249     resignationObserver_ = nil;
250   }
252   tabStripObserverBridge_.reset();
255 - (void)windowWillClose:(NSNotification*)notification {
256   [self closeCleanup];
257   [[NSNotificationCenter defaultCenter] removeObserver:self];
258   [self autorelease];
261 // We want this to be a child of a browser window.  addChildWindow:
262 // (called from this function) will bring the window on-screen;
263 // unfortunately, [NSWindowController showWindow:] will also bring it
264 // on-screen (but will cause unexpected changes to the window's
265 // position).  We cannot have an addChildWindow: and a subsequent
266 // showWindow:. Thus, we have our own version.
267 - (void)showWindow:(id)sender {
268   NSWindow* window = [self window];  // Completes nib load.
269   [self updateOriginFromAnchor];
270   [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
271   if (shouldOpenAsKeyWindow_)
272     [window makeKeyAndOrderFront:self];
273   else
274     [window orderFront:nil];
275   [self registerKeyStateEventTap];
276   [self recordAnchorOffset];
279 - (void)close {
280   [self closeCleanup];
281   [super close];
284 // The controller is the delegate of the window so it receives did resign key
285 // notifications. When key is resigned mirror Windows behavior and close the
286 // window.
287 - (void)windowDidResignKey:(NSNotification*)notification {
288   NSWindow* window = [self window];
289   DCHECK_EQ([notification object], window);
291   // If the window isn't visible, it is already closed, and this notification
292   // has been sent as part of the closing operation, so no need to close.
293   if (![window isVisible])
294     return;
296   // Don't close when explicily disabled, or if there's an attached sheet (e.g.
297   // Open File dialog).
298   if ([self shouldCloseOnResignKey] && ![window attachedSheet]) {
299     [self close];
300     return;
301   }
303   // The bubble should not receive key events when it is no longer key window,
304   // so disable sharing parent key state. Share parent key state is only used
305   // to enable the close/minimize/maximize buttons of the parent window when
306   // the bubble has key state, so disabling it here is safe.
307   InfoBubbleWindow* bubbleWindow =
308       base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
309   [bubbleWindow setAllowShareParentKeyState:NO];
312 - (void)windowDidBecomeKey:(NSNotification*)notification {
313   // Re-enable share parent key state to make sure the close/minimize/maximize
314   // buttons of the parent window are active.
315   InfoBubbleWindow* bubbleWindow =
316       base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
317   [bubbleWindow setAllowShareParentKeyState:YES];
320 // Since the bubble shares first responder with its parent window, set event
321 // handlers to dismiss the bubble when it would normally lose key state.
322 // Events on sheets are ignored: this assumes the sheet belongs to the bubble
323 // since, to affect a sheet on a different window, the bubble would also lose
324 // key status in -[NSWindowDelegate windowDidResignKey:]. This keeps the logic
325 // simple, since -[NSWindow attachedSheet] returns nil while the sheet is still
326 // closing.
327 - (void)registerKeyStateEventTap {
328   // Parent key state sharing is only avaiable on 10.7+.
329   if (!base::mac::IsOSLionOrLater())
330     return;
332   NSWindow* window = self.window;
333   NSNotification* note =
334       [NSNotification notificationWithName:NSWindowDidResignKeyNotification
335                                     object:window];
337   // The eventTap_ catches clicks within the application that are outside the
338   // window.
339   eventTap_ = [NSEvent
340       addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask |
341                                            NSRightMouseDownMask
342       handler:^NSEvent* (NSEvent* event) {
343           if ([event window] != window && ![[event window] isSheet]) {
344             // Do it right now, because if this event is right mouse event,
345             // it may pop up a menu. windowDidResignKey: will not run until
346             // the menu is closed.
347             if ([self respondsToSelector:@selector(windowDidResignKey:)]) {
348               [self windowDidResignKey:note];
349             }
350           }
351           return event;
352       }];
354   // The resignationObserver_ watches for when a window resigns key state,
355   // meaning the key window has changed and the bubble should be dismissed.
356   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
357   resignationObserver_ =
358       [center addObserverForName:NSWindowDidResignKeyNotification
359                           object:nil
360                            queue:[NSOperationQueue mainQueue]
361                       usingBlock:^(NSNotification* notif) {
362                           if (![[notif object] isSheet] &&
363                               [NSApp keyWindow] != [self window])
364                             [self windowDidResignKey:note];
365                       }];
368 // By implementing this, ESC causes the window to go away.
369 - (IBAction)cancel:(id)sender {
370   // This is not a "real" cancel as potential changes to the radio group are not
371   // undone. That's ok.
372   [self close];
375 // Takes the |anchor_| point and adjusts the window's origin accordingly.
376 - (void)updateOriginFromAnchor {
377   NSWindow* window = [self window];
378   NSPoint origin = anchor_;
380   switch ([bubble_ alignment]) {
381     case info_bubble::kAlignArrowToAnchor: {
382       NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
383                                   info_bubble::kBubbleArrowWidth / 2.0, 0);
384       offsets = [[parentWindow_ contentView] convertSize:offsets toView:nil];
385       switch ([bubble_ arrowLocation]) {
386         case info_bubble::kTopRight:
387           origin.x -= NSWidth([window frame]) - offsets.width;
388           break;
389         case info_bubble::kTopLeft:
390           origin.x -= offsets.width;
391           break;
392         case info_bubble::kNoArrow:
393         // FALLTHROUGH.
394         case info_bubble::kTopCenter:
395           origin.x -= NSWidth([window frame]) / 2.0;
396           break;
397       }
398       break;
399     }
401     case info_bubble::kAlignEdgeToAnchorEdge:
402       // If the arrow is to the right then move the origin so that the right
403       // edge aligns with the anchor. If the arrow is to the left then there's
404       // nothing to do because the left edge is already aligned with the left
405       // edge of the anchor.
406       if ([bubble_ arrowLocation] == info_bubble::kTopRight) {
407         origin.x -= NSWidth([window frame]);
408       }
409       break;
411     case info_bubble::kAlignRightEdgeToAnchorEdge:
412       origin.x -= NSWidth([window frame]);
413       break;
415     case info_bubble::kAlignLeftEdgeToAnchorEdge:
416       // Nothing to do.
417       break;
419     default:
420       NOTREACHED();
421   }
423   origin.y -= NSHeight([window frame]);
424   [window setFrameOrigin:origin];
427 - (void)activateTabWithContents:(content::WebContents*)newContents
428                previousContents:(content::WebContents*)oldContents
429                         atIndex:(NSInteger)index
430                          reason:(int)reason {
431   // The user switched tabs; close.
432   [self close];
435 @end  // BaseBubbleController