Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / constrained_window / constrained_window_sheet_controller.mm
blob0c66e6fb62fce2d60b06661367919a0a5310cbdd
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/constrained_window/constrained_window_sheet_controller.h"
7 #include <map>
9 #include "base/logging.h"
10 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet.h"
11 #include "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet_info.h"
12 #import "chrome/browser/ui/cocoa/web_contents_modal_dialog_host_cocoa.h"
14 namespace {
16 // Maps parent windows to sheet controllers.
17 NSMutableDictionary* g_sheetControllers;
19 // Get a value for the given window that can be used as a key in a dictionary.
20 NSValue* GetKeyForParentWindow(NSWindow* parent_window) {
21   return [NSValue valueWithNonretainedObject:parent_window];
24 // Returns the bounds to use when showing a sheet for a given parent view. This
25 // returns a rect in window coordinates.
26 NSRect GetSheetParentBoundsForParentView(NSView* view) {
27   // If the devtools view is open, it shrinks the size of the WebContents, so go
28   // up the hierarchy to the devtools container view to avoid that. Note that
29   // the devtools view is always in the hierarchy even if it is not open or it
30   // is detached.
31   NSView* devtools_view = [[[view superview] superview] superview];
32   if (devtools_view)
33     view = devtools_view;
34   return [view convertRect:[view bounds] toView:nil];
37 }  // namespace
39 // An invisible overlay window placed on top of the sheet's parent view.
40 // This window blocks interaction with the underlying view.
41 @interface CWSheetOverlayWindow : NSWindow {
42   base::scoped_nsobject<ConstrainedWindowSheetController> controller_;
44 @end
46 @interface ConstrainedWindowSheetController ()
47 - (id)initWithParentWindow:(NSWindow*)parentWindow;
48 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView;
49 - (ConstrainedWindowSheetInfo*)
50     findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet;
51 - (void)onParentWindowWillClose:(NSNotification*)note;
52 - (void)onParentWindowSizeDidChange:(NSNotification*)note;
53 - (void)updateSheetPosition:(NSView*)parentView;
54 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView;
55 - (NSPoint)originForSheetSize:(NSSize)sheetSize
56               inContainerRect:(NSRect)containerRect;
57 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow;
58 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
59      withAnimation:(BOOL)withAnimation;
60 @end
62 @implementation CWSheetOverlayWindow
64 - (id)initWithContentRect:(NSRect)rect
65                controller:(ConstrainedWindowSheetController*)controller {
66   if ((self = [super initWithContentRect:rect
67                                styleMask:NSBorderlessWindowMask
68                                  backing:NSBackingStoreBuffered
69                                    defer:NO])) {
70     [self setOpaque:NO];
71     [self setBackgroundColor:[NSColor clearColor]];
72     [self setIgnoresMouseEvents:NO];
73     [self setReleasedWhenClosed:NO];
74     controller_.reset([controller retain]);
75   }
76   return self;
79 - (void)mouseDown:(NSEvent*)event {
80   [controller_ onOverlayWindowMouseDown:self];
83 @end
85 @implementation ConstrainedWindowSheetController
87 + (ConstrainedWindowSheetController*)
88     controllerForParentWindow:(NSWindow*)parentWindow {
89   DCHECK(parentWindow);
90   ConstrainedWindowSheetController* controller =
91       [g_sheetControllers objectForKey:GetKeyForParentWindow(parentWindow)];
92   if (controller)
93     return controller;
95   base::scoped_nsobject<ConstrainedWindowSheetController> new_controller(
96       [[ConstrainedWindowSheetController alloc]
97           initWithParentWindow:parentWindow]);
98   if (!g_sheetControllers)
99     g_sheetControllers = [[NSMutableDictionary alloc] init];
100   [g_sheetControllers setObject:new_controller
101                          forKey:GetKeyForParentWindow(parentWindow)];
102   return new_controller;
105 + (ConstrainedWindowSheetController*)
106     controllerForSheet:(id<ConstrainedWindowSheet>)sheet {
107   for (ConstrainedWindowSheetController* controller in
108        [g_sheetControllers objectEnumerator]) {
109     if ([controller findSheetInfoForSheet:sheet])
110       return controller;
111   }
112   return nil;
115 + (id<ConstrainedWindowSheet>)sheetForOverlayWindow:(NSWindow*)overlayWindow {
116   for (ConstrainedWindowSheetController* controller in
117           [g_sheetControllers objectEnumerator]) {
118     for (ConstrainedWindowSheetInfo* info in controller->sheets_.get()) {
119       if ([overlayWindow isEqual:[info overlayWindow]])
120         return [info sheet];
121     }
122   }
123   return nil;
126 - (id)initWithParentWindow:(NSWindow*)parentWindow {
127   if ((self = [super init])) {
128     parentWindow_.reset([parentWindow retain]);
129     sheets_.reset([[NSMutableArray alloc] init]);
131     [[NSNotificationCenter defaultCenter]
132         addObserver:self
133            selector:@selector(onParentWindowWillClose:)
134                name:NSWindowWillCloseNotification
135              object:parentWindow_];
136   }
137   return self;
140 - (web_modal::WebContentsModalDialogHost*)dialogHost {
141   if (!dialogHost_)
142     dialogHost_.reset(new WebContentsModalDialogHostCocoa(self));
143   return dialogHost_.get();
146 - (NSWindow*)parentWindow {
147   return parentWindow_.get();
150 - (void)showSheet:(id<ConstrainedWindowSheet>)sheet
151     forParentView:(NSView*)parentView {
152   DCHECK(sheet);
153   DCHECK(parentView);
155   // At maximum one active view is allowed.
156   DCHECK(!activeView_.get() || [activeView_ isEqual:parentView]);
157   if (!activeView_.get())
158     activeView_.reset([parentView retain]);
160   // This function can be called multiple times for the same
161   // |parentView|, so sheet info could be created already.
162   ConstrainedWindowSheetInfo* existingInfo =
163       [self findSheetInfoForParentView:activeView_];
164   if (existingInfo) {
165     DCHECK([[existingInfo sheet] isEqual:sheet]);
166     [self updateSheetPosition:activeView_];
167     [existingInfo showSheet];
168     return;
169   }
171   // Observe the parent window's size.
172   [[NSNotificationCenter defaultCenter]
173       addObserver:self
174          selector:@selector(onParentWindowSizeDidChange:)
175              name:NSWindowDidResizeNotification
176            object:parentWindow_];
178   // Create an invisible overlay window.
179   NSRect rect = [self overlayWindowFrameForParentView:parentView];
180   base::scoped_nsobject<NSWindow> overlayWindow(
181       [[CWSheetOverlayWindow alloc] initWithContentRect:rect controller:self]);
182   [parentWindow_ addChildWindow:overlayWindow
183                         ordered:NSWindowAbove];
185   // Add an entry for the sheet.
186   base::scoped_nsobject<ConstrainedWindowSheetInfo> info(
187       [[ConstrainedWindowSheetInfo alloc] initWithSheet:sheet
188                                              parentView:parentView
189                                           overlayWindow:overlayWindow]);
190   [sheets_ addObject:info];
192   // Show the sheet.
193   [info showSheet];
196 - (void)hideSheet {
197   [[self findSheetInfoForParentView:activeView_] hideSheet];
198   activeView_.reset();
201 - (NSPoint)originForSheet:(id<ConstrainedWindowSheet>)sheet
202            withWindowSize:(NSSize)size {
203   ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
204   DCHECK(info);
205   NSRect containerRect =
206       [self overlayWindowFrameForParentView:[info parentView]];
207   return [self originForSheetSize:size inContainerRect:containerRect];
210 - (void)closeSheet:(id<ConstrainedWindowSheet>)sheet {
211   ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
212   DCHECK(info);
213   [self closeSheet:info withAnimation:YES];
216 - (void)pulseSheet:(id<ConstrainedWindowSheet>)sheet {
217   ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
218   DCHECK(info);
219   if ([activeView_ isEqual:[info parentView]])
220     [[info sheet] pulseSheet];
223 - (int)sheetCount {
224   return [sheets_ count];
227 - (NSSize)overlayWindowSizeForParentView:(NSView*)parentView {
228   return [self overlayWindowFrameForParentView:parentView].size;
231 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
232   for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
233     if ([parentView isEqual:[info parentView]])
234       return info;
235   }
236   return NULL;
239 - (ConstrainedWindowSheetInfo*)
240     findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
241   for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
242     if ([sheet isEqual:[info sheet]])
243       return info;
244   }
245   return NULL;
248 - (void)onParentWindowWillClose:(NSNotification*)note {
249   [[NSNotificationCenter defaultCenter]
250       removeObserver:self
251                 name:NSWindowWillCloseNotification
252               object:parentWindow_];
254   // Close all sheets.
255   NSArray* sheets = [NSArray arrayWithArray:sheets_];
256   for (ConstrainedWindowSheetInfo* info in sheets)
257     [self closeSheet:info withAnimation:NO];
259   dialogHost_.reset();
261   // Delete this instance.
262   [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
263   if (![g_sheetControllers count]) {
264     [g_sheetControllers release];
265     g_sheetControllers = nil;
266   }
269 - (void)onParentWindowSizeDidChange:(NSNotification*)note {
270   [self updateSheetPosition:activeView_];
273 - (void)updateSheetPosition:(NSView*)parentView {
274   ConstrainedWindowSheetInfo* info =
275       [self findSheetInfoForParentView:parentView];
276   if (!info)
277     return;
279   NSRect rect = [self overlayWindowFrameForParentView:parentView];
280   [[info overlayWindow] setFrame:rect display:YES];
281   [[info sheet] updateSheetPosition];
284 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
285   NSRect viewFrame = GetSheetParentBoundsForParentView(parentView);
287   id<NSWindowDelegate> delegate = [[parentView window] delegate];
288   if ([delegate respondsToSelector:@selector(window:
289                                   willPositionSheet:
290                                           usingRect:)]) {
291     NSRect sheetFrame = NSZeroRect;
292     // This API needs Y to be the distance from the bottom of the overlay to
293     // the top of the sheet. X, width, and height are ignored.
294     sheetFrame.origin.y = NSMaxY(viewFrame);
295     NSRect customSheetFrame = [delegate window:[parentView window]
296                              willPositionSheet:nil
297                                      usingRect:sheetFrame];
298     viewFrame.size.height += NSMinY(customSheetFrame) - NSMinY(sheetFrame);
299   }
301   viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
302   return viewFrame;
305 - (NSPoint)originForSheetSize:(NSSize)sheetSize
306               inContainerRect:(NSRect)containerRect {
307   NSPoint origin;
308   origin.x = roundf(NSMinX(containerRect) +
309                     (NSWidth(containerRect) - sheetSize.width) / 2.0);
310   origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
311   return origin;
314 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow {
315   for (ConstrainedWindowSheetInfo* curInfo in sheets_.get()) {
316     if ([overlayWindow isEqual:[curInfo overlayWindow]]) {
317       [self pulseSheet:[curInfo sheet]];
318       [[curInfo sheet] makeSheetKeyAndOrderFront];
319       break;
320     }
321   }
324 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
325      withAnimation:(BOOL)withAnimation {
326   if (![sheets_ containsObject:info])
327     return;
329   [[NSNotificationCenter defaultCenter]
330       removeObserver:self
331                 name:NSWindowDidResizeNotification
332               object:parentWindow_];
334   if ([activeView_ isEqual:[info parentView]])
335     activeView_.reset();
337   [parentWindow_ removeChildWindow:[info overlayWindow]];
338   [[info sheet] closeSheetWithAnimation:withAnimation];
339   [[info overlayWindow] close];
340   [sheets_ removeObject:info];
343 @end