Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / base_bubble_controller.mm
blobc8ca24ceb8e9ee0ab1f03a2514bad677968b67f4
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)parentWindowWillBecomeFullScreen:(NSNotification*)notification;
30 - (void)closeCleanup;
31 @end
33 @implementation BaseBubbleController
35 @synthesize parentWindow = parentWindow_;
36 @synthesize anchorPoint = anchor_;
37 @synthesize bubble = bubble_;
38 @synthesize shouldOpenAsKeyWindow = shouldOpenAsKeyWindow_;
39 @synthesize shouldCloseOnResignKey = shouldCloseOnResignKey_;
41 - (id)initWithWindowNibPath:(NSString*)nibPath
42                parentWindow:(NSWindow*)parentWindow
43                  anchoredAt:(NSPoint)anchoredAt {
44   nibPath = [base::mac::FrameworkBundle() pathForResource:nibPath
45                                                    ofType:@"nib"];
46   if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
47     parentWindow_ = parentWindow;
48     anchor_ = anchoredAt;
49     shouldOpenAsKeyWindow_ = YES;
50     shouldCloseOnResignKey_ = YES;
51     [self registerForNotifications];
52   }
53   return self;
56 - (id)initWithWindowNibPath:(NSString*)nibPath
57              relativeToView:(NSView*)view
58                      offset:(NSPoint)offset {
59   DCHECK([view window]);
60   NSWindow* window = [view window];
61   NSRect bounds = [view convertRect:[view bounds] toView:nil];
62   NSPoint anchor = NSMakePoint(NSMinX(bounds) + offset.x,
63                                NSMinY(bounds) + offset.y);
64   anchor = [window convertBaseToScreen:anchor];
65   return [self initWithWindowNibPath:nibPath
66                         parentWindow:window
67                           anchoredAt:anchor];
70 - (id)initWithWindow:(NSWindow*)theWindow
71         parentWindow:(NSWindow*)parentWindow
72           anchoredAt:(NSPoint)anchoredAt {
73   DCHECK(theWindow);
74   if ((self = [super initWithWindow:theWindow])) {
75     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     [self registerForNotifications];
88     [self awakeFromNib];
89     [self setAnchorPoint:anchoredAt];
90   }
91   return self;
94 - (void)awakeFromNib {
95   // Check all connections have been made in Interface Builder.
96   DCHECK([self window]);
97   DCHECK(bubble_);
98   DCHECK_EQ(self, [[self window] delegate]);
100   BrowserWindowController* bwc =
101       [BrowserWindowController browserWindowControllerForWindow:parentWindow_];
102   if (bwc) {
103     TabStripController* tabStripController = [bwc tabStripController];
104     TabStripModel* tabStripModel = [tabStripController tabStripModel];
105     tabStripObserverBridge_.reset(new TabStripModelObserverBridge(tabStripModel,
106                                                                   self));
107   }
109   [bubble_ setArrowLocation:info_bubble::kTopRight];
112 - (void)dealloc {
113   [[NSNotificationCenter defaultCenter] removeObserver:self];
114   [super dealloc];
117 - (void)registerForNotifications {
118   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
119   // Watch to see if the parent window closes, and if so, close this one.
120   [center addObserver:self
121              selector:@selector(parentWindowWillClose:)
122                  name:NSWindowWillCloseNotification
123                object:parentWindow_];
124   // Watch for the full screen event, if so, close the bubble
125   [center addObserver:self
126              selector:@selector(parentWindowWillBecomeFullScreen:)
127                  name:NSWindowWillEnterFullScreenNotification
128                object:parentWindow_];
129   // Watch for parent window's resizing, to ensure this one is always
130   // anchored correctly.
131   [center addObserver:self
132              selector:@selector(parentWindowDidResize:)
133                  name:NSWindowDidResizeNotification
134                object:parentWindow_];
137 - (void)setAnchorPoint:(NSPoint)anchor {
138   anchor_ = anchor;
139   [self updateOriginFromAnchor];
142 - (void)recordAnchorOffset {
143   // The offset of the anchor from the parent's upper-left-hand corner is kept
144   // to ensure the bubble stays anchored correctly if the parent is resized.
145   anchorOffset_ = NSMakePoint(NSMinX([parentWindow_ frame]),
146                               NSMaxY([parentWindow_ frame]));
147   anchorOffset_.x -= anchor_.x;
148   anchorOffset_.y -= anchor_.y;
151 - (NSBox*)horizontalSeparatorWithFrame:(NSRect)frame {
152   frame.size.height = 1.0;
153   base::scoped_nsobject<NSBox> spacer([[NSBox alloc] initWithFrame:frame]);
154   [spacer setBoxType:NSBoxSeparator];
155   [spacer setBorderType:NSLineBorder];
156   [spacer setAlphaValue:0.2];
157   return [spacer.release() autorelease];
160 - (NSBox*)verticalSeparatorWithFrame:(NSRect)frame {
161   frame.size.width = 1.0;
162   base::scoped_nsobject<NSBox> spacer([[NSBox alloc] initWithFrame:frame]);
163   [spacer setBoxType:NSBoxSeparator];
164   [spacer setBorderType:NSLineBorder];
165   [spacer setAlphaValue:0.2];
166   return [spacer.release() autorelease];
169 - (void)parentWindowDidResize:(NSNotification*)notification {
170   if (!parentWindow_)
171     return;
173   DCHECK_EQ(parentWindow_, [notification object]);
174   NSPoint newOrigin = NSMakePoint(NSMinX([parentWindow_ frame]),
175                                   NSMaxY([parentWindow_ frame]));
176   newOrigin.x -= anchorOffset_.x;
177   newOrigin.y -= anchorOffset_.y;
178   [self setAnchorPoint:newOrigin];
181 - (void)parentWindowWillClose:(NSNotification*)notification {
182   parentWindow_ = nil;
183   [self close];
186 - (void)parentWindowWillBecomeFullScreen:(NSNotification*)notification {
187   parentWindow_ = nil;
188   [self close];
191 - (void)closeCleanup {
192   if (eventTap_) {
193     [NSEvent removeMonitor:eventTap_];
194     eventTap_ = nil;
195   }
196   if (resignationObserver_) {
197     [[NSNotificationCenter defaultCenter]
198         removeObserver:resignationObserver_
199                   name:NSWindowDidResignKeyNotification
200                 object:nil];
201     resignationObserver_ = nil;
202   }
204   tabStripObserverBridge_.reset();
207 - (void)windowWillClose:(NSNotification*)notification {
208   [self closeCleanup];
209   [[NSNotificationCenter defaultCenter] removeObserver:self];
210   [self autorelease];
213 // We want this to be a child of a browser window.  addChildWindow:
214 // (called from this function) will bring the window on-screen;
215 // unfortunately, [NSWindowController showWindow:] will also bring it
216 // on-screen (but will cause unexpected changes to the window's
217 // position).  We cannot have an addChildWindow: and a subsequent
218 // showWindow:. Thus, we have our own version.
219 - (void)showWindow:(id)sender {
220   NSWindow* window = [self window];  // Completes nib load.
221   [self updateOriginFromAnchor];
222   [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
223   if (shouldOpenAsKeyWindow_)
224     [window makeKeyAndOrderFront:self];
225   else
226     [window orderFront:nil];
227   [self registerKeyStateEventTap];
228   [self recordAnchorOffset];
231 - (void)close {
232   [self closeCleanup];
233   [super close];
236 // The controller is the delegate of the window so it receives did resign key
237 // notifications. When key is resigned mirror Windows behavior and close the
238 // window.
239 - (void)windowDidResignKey:(NSNotification*)notification {
240   NSWindow* window = [self window];
241   DCHECK_EQ([notification object], window);
243   // If the window isn't visible, it is already closed, and this notification
244   // has been sent as part of the closing operation, so no need to close.
245   if (![window isVisible])
246     return;
248   // Don't close when explicily disabled, or if there's an attached sheet (e.g.
249   // Open File dialog).
250   if ([self shouldCloseOnResignKey] && ![window attachedSheet]) {
251     [self close];
252     return;
253   }
255   // The bubble should not receive key events when it is no longer key window,
256   // so disable sharing parent key state. Share parent key state is only used
257   // to enable the close/minimize/maximize buttons of the parent window when
258   // the bubble has key state, so disabling it here is safe.
259   InfoBubbleWindow* bubbleWindow =
260       base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
261   [bubbleWindow setAllowShareParentKeyState:NO];
264 - (void)windowDidBecomeKey:(NSNotification*)notification {
265   // Re-enable share parent key state to make sure the close/minimize/maximize
266   // buttons of the parent window are active.
267   InfoBubbleWindow* bubbleWindow =
268       base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
269   [bubbleWindow setAllowShareParentKeyState:YES];
272 // Since the bubble shares first responder with its parent window, set event
273 // handlers to dismiss the bubble when it would normally lose key state.
274 // Events on sheets are ignored: this assumes the sheet belongs to the bubble
275 // since, to affect a sheet on a different window, the bubble would also lose
276 // key status in -[NSWindowDelegate windowDidResignKey:]. This keeps the logic
277 // simple, since -[NSWindow attachedSheet] returns nil while the sheet is still
278 // closing.
279 - (void)registerKeyStateEventTap {
280   // Parent key state sharing is only avaiable on 10.7+.
281   if (!base::mac::IsOSLionOrLater())
282     return;
284   NSWindow* window = self.window;
285   NSNotification* note =
286       [NSNotification notificationWithName:NSWindowDidResignKeyNotification
287                                     object:window];
289   // The eventTap_ catches clicks within the application that are outside the
290   // window.
291   eventTap_ = [NSEvent
292       addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask |
293                                            NSRightMouseDownMask
294       handler:^NSEvent* (NSEvent* event) {
295           if ([event window] != window && ![[event window] isSheet]) {
296             // Do it right now, because if this event is right mouse event,
297             // it may pop up a menu. windowDidResignKey: will not run until
298             // the menu is closed.
299             if ([self respondsToSelector:@selector(windowDidResignKey:)]) {
300               [self windowDidResignKey:note];
301             }
302           }
303           return event;
304       }];
306   // The resignationObserver_ watches for when a window resigns key state,
307   // meaning the key window has changed and the bubble should be dismissed.
308   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
309   resignationObserver_ =
310       [center addObserverForName:NSWindowDidResignKeyNotification
311                           object:nil
312                            queue:[NSOperationQueue mainQueue]
313                       usingBlock:^(NSNotification* notif) {
314                           if (![[notif object] isSheet])
315                             [self windowDidResignKey:note];
316                       }];
319 // By implementing this, ESC causes the window to go away.
320 - (IBAction)cancel:(id)sender {
321   // This is not a "real" cancel as potential changes to the radio group are not
322   // undone. That's ok.
323   [self close];
326 // Takes the |anchor_| point and adjusts the window's origin accordingly.
327 - (void)updateOriginFromAnchor {
328   NSWindow* window = [self window];
329   NSPoint origin = anchor_;
331   switch ([bubble_ alignment]) {
332     case info_bubble::kAlignArrowToAnchor: {
333       NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
334                                   info_bubble::kBubbleArrowWidth / 2.0, 0);
335       offsets = [[parentWindow_ contentView] convertSize:offsets toView:nil];
336       switch ([bubble_ arrowLocation]) {
337         case info_bubble::kTopRight:
338           origin.x -= NSWidth([window frame]) - offsets.width;
339           break;
340         case info_bubble::kTopLeft:
341           origin.x -= offsets.width;
342           break;
343         case info_bubble::kTopCenter:
344           origin.x -= NSWidth([window frame]) / 2.0;
345           break;
346         case info_bubble::kNoArrow:
347           NOTREACHED();
348           break;
349       }
350       break;
351     }
353     case info_bubble::kAlignEdgeToAnchorEdge:
354       // If the arrow is to the right then move the origin so that the right
355       // edge aligns with the anchor. If the arrow is to the left then there's
356       // nothing to do because the left edge is already aligned with the left
357       // edge of the anchor.
358       if ([bubble_ arrowLocation] == info_bubble::kTopRight) {
359         origin.x -= NSWidth([window frame]);
360       }
361       break;
363     case info_bubble::kAlignRightEdgeToAnchorEdge:
364       origin.x -= NSWidth([window frame]);
365       break;
367     case info_bubble::kAlignLeftEdgeToAnchorEdge:
368       // Nothing to do.
369       break;
371     default:
372       NOTREACHED();
373   }
375   origin.y -= NSHeight([window frame]);
376   [window setFrameOrigin:origin];
379 - (void)activateTabWithContents:(content::WebContents*)newContents
380                previousContents:(content::WebContents*)oldContents
381                         atIndex:(NSInteger)index
382                          reason:(int)reason {
383   // The user switched tabs; close.
384   [self close];
387 @end  // BaseBubbleController