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 {
154 if (!activeView_.get())
155 activeView_.reset([parentView retain]);
157 // Observe the parent window's size.
158 [[NSNotificationCenter defaultCenter]
160 selector:@selector(onParentWindowSizeDidChange:)
161 name:NSWindowDidResizeNotification
162 object:parentWindow_];
164 // Create an invisible overlay window.
165 NSRect rect = [self overlayWindowFrameForParentView:parentView];
166 base::scoped_nsobject<NSWindow> overlayWindow(
167 [[CWSheetOverlayWindow alloc] initWithContentRect:rect controller:self]);
168 [parentWindow_ addChildWindow:overlayWindow
169 ordered:NSWindowAbove];
171 // Add an entry for the sheet.
172 base::scoped_nsobject<ConstrainedWindowSheetInfo> info(
173 [[ConstrainedWindowSheetInfo alloc] initWithSheet:sheet
174 parentView:parentView
175 overlayWindow:overlayWindow]);
176 [sheets_ addObject:info];
178 // Show or hide the sheet.
179 if ([activeView_ isEqual:parentView])
185 - (NSPoint)originForSheet:(id<ConstrainedWindowSheet>)sheet
186 withWindowSize:(NSSize)size {
187 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
189 NSRect containerRect =
190 [self overlayWindowFrameForParentView:[info parentView]];
191 return [self originForSheetSize:size inContainerRect:containerRect];
194 - (void)closeSheet:(id<ConstrainedWindowSheet>)sheet {
195 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
197 [self closeSheet:info withAnimation:YES];
200 - (void)parentViewDidBecomeActive:(NSView*)parentView {
201 [[self findSheetInfoForParentView:activeView_] hideSheet];
202 activeView_.reset([parentView retain]);
203 [self updateSheetPosition:parentView];
204 [[self findSheetInfoForParentView:activeView_] showSheet];
207 - (void)pulseSheet:(id<ConstrainedWindowSheet>)sheet {
208 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
210 if ([activeView_ isEqual:[info parentView]])
211 [[info sheet] pulseSheet];
215 return [sheets_ count];
218 - (NSSize)overlayWindowSizeForParentView:(NSView*)parentView {
219 return [self overlayWindowFrameForParentView:parentView].size;
222 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
223 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
224 if ([parentView isEqual:[info parentView]])
230 - (ConstrainedWindowSheetInfo*)
231 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
232 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
233 if ([sheet isEqual:[info sheet]])
239 - (void)onParentWindowWillClose:(NSNotification*)note {
240 [[NSNotificationCenter defaultCenter]
242 name:NSWindowWillCloseNotification
243 object:parentWindow_];
246 NSArray* sheets = [NSArray arrayWithArray:sheets_];
247 for (ConstrainedWindowSheetInfo* info in sheets)
248 [self closeSheet:info withAnimation:NO];
252 // Delete this instance.
253 [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
254 if (![g_sheetControllers count]) {
255 [g_sheetControllers release];
256 g_sheetControllers = nil;
260 - (void)onParentWindowSizeDidChange:(NSNotification*)note {
261 [self updateSheetPosition:activeView_];
264 - (void)updateSheetPosition:(NSView*)parentView {
265 ConstrainedWindowSheetInfo* info =
266 [self findSheetInfoForParentView:parentView];
270 NSRect rect = [self overlayWindowFrameForParentView:parentView];
271 [[info overlayWindow] setFrame:rect display:YES];
272 [[info sheet] updateSheetPosition];
275 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
276 NSRect viewFrame = GetSheetParentBoundsForParentView(parentView);
278 id<NSWindowDelegate> delegate = [[parentView window] delegate];
279 if ([delegate respondsToSelector:@selector(window:
282 NSRect sheetFrame = NSZeroRect;
283 // This API needs Y to be the distance from the bottom of the overlay to
284 // the top of the sheet. X, width, and height are ignored.
285 sheetFrame.origin.y = NSMaxY(viewFrame);
286 NSRect customSheetFrame = [delegate window:[parentView window]
287 willPositionSheet:nil
288 usingRect:sheetFrame];
289 viewFrame.size.height += NSMinY(customSheetFrame) - NSMinY(sheetFrame);
292 viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
296 - (NSPoint)originForSheetSize:(NSSize)sheetSize
297 inContainerRect:(NSRect)containerRect {
299 origin.x = roundf(NSMinX(containerRect) +
300 (NSWidth(containerRect) - sheetSize.width) / 2.0);
301 origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
305 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow {
306 for (ConstrainedWindowSheetInfo* curInfo in sheets_.get()) {
307 if ([overlayWindow isEqual:[curInfo overlayWindow]]) {
308 [self pulseSheet:[curInfo sheet]];
309 [[curInfo sheet] makeSheetKeyAndOrderFront];
315 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
316 withAnimation:(BOOL)withAnimation {
317 if (![sheets_ containsObject:info])
320 [[NSNotificationCenter defaultCenter]
322 name:NSWindowDidResizeNotification
323 object:parentWindow_];
325 [parentWindow_ removeChildWindow:[info overlayWindow]];
326 [[info sheet] closeSheetWithAnimation:withAnimation];
327 [[info overlayWindow] close];
328 [sheets_ removeObject:info];