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"
15 // Maps parent windows to sheet controllers.
16 NSMutableDictionary* g_sheetControllers;
18 // Get a value for the given window that can be used as a key in a dictionary.
19 NSValue* GetKeyForParentWindow(NSWindow* parent_window) {
20 return [NSValue valueWithNonretainedObject:parent_window];
23 // Returns the bounds to use when showing a sheet for a given parent view. This
24 // returns a rect in window coordinates.
25 NSRect GetSheetParentBoundsForParentView(NSView* view) {
26 // If the devtools view is open, it shrinks the size of the WebContents, so go
27 // up the hierarchy to the devtools container view to avoid that. Note that
28 // the devtools view is always in the hierarchy even if it is not open or it
30 NSView* devtools_view = [[[view superview] superview] superview];
33 return [view convertRect:[view bounds] toView:nil];
38 // An invisible overlay window placed on top of the sheet's parent view.
39 // This window blocks interaction with the underlying view.
40 @interface CWSheetOverlayWindow : NSWindow {
41 base::scoped_nsobject<ConstrainedWindowSheetController> controller_;
45 @interface ConstrainedWindowSheetController ()
46 - (id)initWithParentWindow:(NSWindow*)parentWindow;
47 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView;
48 - (ConstrainedWindowSheetInfo*)
49 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet;
50 - (void)onParentWindowWillClose:(NSNotification*)note;
51 - (void)onParentWindowSizeDidChange:(NSNotification*)note;
52 - (void)updateSheetPosition:(NSView*)parentView;
53 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView;
54 - (NSPoint)originForSheetSize:(NSSize)sheetSize
55 inContainerRect:(NSRect)containerRect;
56 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow;
57 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
58 withAnimation:(BOOL)withAnimation;
61 @implementation CWSheetOverlayWindow
63 - (id)initWithContentRect:(NSRect)rect
64 controller:(ConstrainedWindowSheetController*)controller {
65 if ((self = [super initWithContentRect:rect
66 styleMask:NSBorderlessWindowMask
67 backing:NSBackingStoreBuffered
70 [self setBackgroundColor:[NSColor clearColor]];
71 [self setIgnoresMouseEvents:NO];
72 [self setReleasedWhenClosed:NO];
73 controller_.reset([controller retain]);
78 - (void)mouseDown:(NSEvent*)event {
79 [controller_ onOverlayWindowMouseDown:self];
84 @implementation ConstrainedWindowSheetController
86 + (ConstrainedWindowSheetController*)
87 controllerForParentWindow:(NSWindow*)parentWindow {
89 ConstrainedWindowSheetController* controller =
90 [g_sheetControllers objectForKey:GetKeyForParentWindow(parentWindow)];
94 base::scoped_nsobject<ConstrainedWindowSheetController> new_controller(
95 [[ConstrainedWindowSheetController alloc]
96 initWithParentWindow:parentWindow]);
97 if (!g_sheetControllers)
98 g_sheetControllers = [[NSMutableDictionary alloc] init];
99 [g_sheetControllers setObject:new_controller
100 forKey:GetKeyForParentWindow(parentWindow)];
101 return new_controller;
104 + (ConstrainedWindowSheetController*)
105 controllerForSheet:(id<ConstrainedWindowSheet>)sheet {
106 for (ConstrainedWindowSheetController* controller in
107 [g_sheetControllers objectEnumerator]) {
108 if ([controller findSheetInfoForSheet:sheet])
114 + (id<ConstrainedWindowSheet>)sheetForOverlayWindow:(NSWindow*)overlayWindow {
115 for (ConstrainedWindowSheetController* controller in
116 [g_sheetControllers objectEnumerator]) {
117 for (ConstrainedWindowSheetInfo* info in controller->sheets_.get()) {
118 if ([overlayWindow isEqual:[info overlayWindow]])
125 - (id)initWithParentWindow:(NSWindow*)parentWindow {
126 if ((self = [super init])) {
127 parentWindow_.reset([parentWindow retain]);
128 sheets_.reset([[NSMutableArray alloc] init]);
130 [[NSNotificationCenter defaultCenter]
132 selector:@selector(onParentWindowWillClose:)
133 name:NSWindowWillCloseNotification
134 object:parentWindow_];
139 - (void)showSheet:(id<ConstrainedWindowSheet>)sheet
140 forParentView:(NSView*)parentView {
143 if (!activeView_.get())
144 activeView_.reset([parentView retain]);
146 // Observe the parent window's size.
147 [[NSNotificationCenter defaultCenter]
149 selector:@selector(onParentWindowSizeDidChange:)
150 name:NSWindowDidResizeNotification
151 object:parentWindow_];
153 // Create an invisible overlay window.
154 NSRect rect = [self overlayWindowFrameForParentView:parentView];
155 base::scoped_nsobject<NSWindow> overlayWindow(
156 [[CWSheetOverlayWindow alloc] initWithContentRect:rect controller:self]);
157 [parentWindow_ addChildWindow:overlayWindow
158 ordered:NSWindowAbove];
160 // Add an entry for the sheet.
161 base::scoped_nsobject<ConstrainedWindowSheetInfo> info(
162 [[ConstrainedWindowSheetInfo alloc] initWithSheet:sheet
163 parentView:parentView
164 overlayWindow:overlayWindow]);
165 [sheets_ addObject:info];
167 // Show or hide the sheet.
168 if ([activeView_ isEqual:parentView])
174 - (NSPoint)originForSheet:(id<ConstrainedWindowSheet>)sheet
175 withWindowSize:(NSSize)size {
176 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
178 NSRect containerRect =
179 [self overlayWindowFrameForParentView:[info parentView]];
180 return [self originForSheetSize:size inContainerRect:containerRect];
183 - (void)closeSheet:(id<ConstrainedWindowSheet>)sheet {
184 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
186 [self closeSheet:info withAnimation:YES];
189 - (void)parentViewDidBecomeActive:(NSView*)parentView {
190 [[self findSheetInfoForParentView:activeView_] hideSheet];
191 activeView_.reset([parentView retain]);
192 [self updateSheetPosition:parentView];
193 [[self findSheetInfoForParentView:activeView_] showSheet];
196 - (void)pulseSheet:(id<ConstrainedWindowSheet>)sheet {
197 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
199 if ([activeView_ isEqual:[info parentView]])
200 [[info sheet] pulseSheet];
204 return [sheets_ count];
207 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
208 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
209 if ([parentView isEqual:[info parentView]])
215 - (ConstrainedWindowSheetInfo*)
216 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
217 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
218 if ([sheet isEqual:[info sheet]])
224 - (void)onParentWindowWillClose:(NSNotification*)note {
225 [[NSNotificationCenter defaultCenter]
227 name:NSWindowWillCloseNotification
228 object:parentWindow_];
231 NSArray* sheets = [NSArray arrayWithArray:sheets_];
232 for (ConstrainedWindowSheetInfo* info in sheets)
233 [self closeSheet:info withAnimation:NO];
235 // Delete this instance.
236 [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
237 if (![g_sheetControllers count]) {
238 [g_sheetControllers release];
239 g_sheetControllers = nil;
243 - (void)onParentWindowSizeDidChange:(NSNotification*)note {
244 [self updateSheetPosition:activeView_];
247 - (void)updateSheetPosition:(NSView*)parentView {
248 ConstrainedWindowSheetInfo* info =
249 [self findSheetInfoForParentView:parentView];
253 NSRect rect = [self overlayWindowFrameForParentView:parentView];
254 [[info overlayWindow] setFrame:rect display:YES];
255 [[info sheet] updateSheetPosition];
258 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
259 NSRect viewFrame = GetSheetParentBoundsForParentView(parentView);
261 id<NSWindowDelegate> delegate = [[parentView window] delegate];
262 if ([delegate respondsToSelector:@selector(window:
265 NSRect sheetFrame = NSZeroRect;
266 // This API needs Y to be the distance from the bottom of the overlay to
267 // the top of the sheet. X, width, and height are ignored.
268 sheetFrame.origin.y = NSMaxY(viewFrame);
269 NSRect customSheetFrame = [delegate window:[parentView window]
270 willPositionSheet:nil
271 usingRect:sheetFrame];
272 viewFrame.size.height += NSMinY(customSheetFrame) - NSMinY(sheetFrame);
275 viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
279 - (NSPoint)originForSheetSize:(NSSize)sheetSize
280 inContainerRect:(NSRect)containerRect {
282 origin.x = roundf(NSMinX(containerRect) +
283 (NSWidth(containerRect) - sheetSize.width) / 2.0);
284 origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
288 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow {
289 for (ConstrainedWindowSheetInfo* curInfo in sheets_.get()) {
290 if ([overlayWindow isEqual:[curInfo overlayWindow]]) {
291 [self pulseSheet:[curInfo sheet]];
292 [[curInfo sheet] makeSheetKeyAndOrderFront];
298 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
299 withAnimation:(BOOL)withAnimation {
300 if (![sheets_ containsObject:info])
303 [[NSNotificationCenter defaultCenter]
305 name:NSWindowDidResizeNotification
306 object:parentWindow_];
308 [parentWindow_ removeChildWindow:[info overlayWindow]];
309 [[info sheet] closeSheetWithAnimation:withAnimation];
310 [[info overlayWindow] close];
311 [sheets_ removeObject:info];