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"
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"
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
31 NSView* devtools_view = [[[view superview] superview] superview];
34 return [view convertRect:[view bounds] toView:nil];
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_;
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;
62 @implementation CWSheetOverlayWindow
64 - (id)initWithContentRect:(NSRect)rect
65 controller:(ConstrainedWindowSheetController*)controller {
66 if ((self = [super initWithContentRect:rect
67 styleMask:NSBorderlessWindowMask
68 backing:NSBackingStoreBuffered
71 [self setBackgroundColor:[NSColor clearColor]];
72 [self setIgnoresMouseEvents:NO];
73 [self setReleasedWhenClosed:NO];
74 controller_.reset([controller retain]);
79 - (void)mouseDown:(NSEvent*)event {
80 [controller_ onOverlayWindowMouseDown:self];
85 @implementation ConstrainedWindowSheetController
87 + (ConstrainedWindowSheetController*)
88 controllerForParentWindow:(NSWindow*)parentWindow {
90 ConstrainedWindowSheetController* controller =
91 [g_sheetControllers objectForKey:GetKeyForParentWindow(parentWindow)];
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])
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]])
126 - (id)initWithParentWindow:(NSWindow*)parentWindow {
127 if ((self = [super init])) {
128 parentWindow_.reset([parentWindow retain]);
129 sheets_.reset([[NSMutableArray alloc] init]);
131 [[NSNotificationCenter defaultCenter]
133 selector:@selector(onParentWindowWillClose:)
134 name:NSWindowWillCloseNotification
135 object:parentWindow_];
140 - (web_modal::WebContentsModalDialogHost*)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 {
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_];
165 DCHECK([[existingInfo sheet] isEqual:sheet]);
166 [self updateSheetPosition:activeView_];
167 [existingInfo showSheet];
171 // Observe the parent window's size.
172 [[NSNotificationCenter defaultCenter]
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];
197 [[self findSheetInfoForParentView:activeView_] hideSheet];
201 - (NSPoint)originForSheet:(id<ConstrainedWindowSheet>)sheet
202 withWindowSize:(NSSize)size {
203 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
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];
213 [self closeSheet:info withAnimation:YES];
216 - (void)pulseSheet:(id<ConstrainedWindowSheet>)sheet {
217 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
219 if ([activeView_ isEqual:[info parentView]])
220 [[info sheet] pulseSheet];
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]])
239 - (ConstrainedWindowSheetInfo*)
240 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
241 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
242 if ([sheet isEqual:[info sheet]])
248 - (void)onParentWindowWillClose:(NSNotification*)note {
249 [[NSNotificationCenter defaultCenter]
251 name:NSWindowWillCloseNotification
252 object:parentWindow_];
255 NSArray* sheets = [NSArray arrayWithArray:sheets_];
256 for (ConstrainedWindowSheetInfo* info in sheets)
257 [self closeSheet:info withAnimation:NO];
261 // Delete this instance.
262 [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
263 if (![g_sheetControllers count]) {
264 [g_sheetControllers release];
265 g_sheetControllers = nil;
269 - (void)onParentWindowSizeDidChange:(NSNotification*)note {
270 [self updateSheetPosition:activeView_];
273 - (void)updateSheetPosition:(NSView*)parentView {
274 ConstrainedWindowSheetInfo* info =
275 [self findSheetInfoForParentView:parentView];
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:
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);
301 viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
305 - (NSPoint)originForSheetSize:(NSSize)sheetSize
306 inContainerRect:(NSRect)containerRect {
308 origin.x = roundf(NSMinX(containerRect) +
309 (NSWidth(containerRect) - sheetSize.width) / 2.0);
310 origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
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];
324 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
325 withAnimation:(BOOL)withAnimation {
326 if (![sheets_ containsObject:info])
329 [[NSNotificationCenter defaultCenter]
331 name:NSWindowDidResizeNotification
332 object:parentWindow_];
334 if ([activeView_ isEqual:[info parentView]])
337 [parentWindow_ removeChildWindow:[info overlayWindow]];
338 [[info sheet] closeSheetWithAnimation:withAnimation];
339 [[info overlayWindow] close];
340 [sheets_ removeObject:info];