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];
25 // An invisible overlay window placed on top of the sheet's parent view.
26 // This window blocks interaction with the underlying view.
27 @interface CWSheetOverlayWindow : NSWindow {
28 base::scoped_nsobject<ConstrainedWindowSheetController> controller_;
32 @interface ConstrainedWindowSheetController ()
33 - (id)initWithParentWindow:(NSWindow*)parentWindow;
34 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView;
35 - (ConstrainedWindowSheetInfo*)
36 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet;
37 - (void)onParentWindowWillClose:(NSNotification*)note;
38 - (void)onParentViewFrameDidChange:(NSNotification*)note;
39 - (void)updateSheetPosition:(NSView*)parentView;
40 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView;
41 - (NSPoint)originForSheetSize:(NSSize)sheetSize
42 inContainerRect:(NSRect)containerRect;
43 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow;
44 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
45 withAnimation:(BOOL)withAnimation;
48 @implementation CWSheetOverlayWindow
50 - (id)initWithContentRect:(NSRect)rect
51 controller:(ConstrainedWindowSheetController*)controller {
52 if ((self = [super initWithContentRect:rect
53 styleMask:NSBorderlessWindowMask
54 backing:NSBackingStoreBuffered
57 [self setBackgroundColor:[NSColor clearColor]];
58 [self setIgnoresMouseEvents:NO];
59 [self setReleasedWhenClosed:NO];
60 controller_.reset([controller retain]);
65 - (void)mouseDown:(NSEvent*)event {
66 [controller_ onOverlayWindowMouseDown:self];
71 @implementation ConstrainedWindowSheetController
73 + (ConstrainedWindowSheetController*)
74 controllerForParentWindow:(NSWindow*)parentWindow {
76 ConstrainedWindowSheetController* controller =
77 [g_sheetControllers objectForKey:GetKeyForParentWindow(parentWindow)];
81 base::scoped_nsobject<ConstrainedWindowSheetController> new_controller(
82 [[ConstrainedWindowSheetController alloc]
83 initWithParentWindow:parentWindow]);
84 if (!g_sheetControllers)
85 g_sheetControllers = [[NSMutableDictionary alloc] init];
86 [g_sheetControllers setObject:new_controller
87 forKey:GetKeyForParentWindow(parentWindow)];
88 return new_controller;
91 + (ConstrainedWindowSheetController*)
92 controllerForSheet:(id<ConstrainedWindowSheet>)sheet {
93 for (ConstrainedWindowSheetController* controller in
94 [g_sheetControllers objectEnumerator]) {
95 if ([controller findSheetInfoForSheet:sheet])
101 + (id<ConstrainedWindowSheet>)sheetForOverlayWindow:(NSWindow*)overlayWindow {
102 for (ConstrainedWindowSheetController* controller in
103 [g_sheetControllers objectEnumerator]) {
104 for (ConstrainedWindowSheetInfo* info in controller->sheets_.get()) {
105 if ([overlayWindow isEqual:[info overlayWindow]])
112 - (id)initWithParentWindow:(NSWindow*)parentWindow {
113 if ((self = [super init])) {
114 parentWindow_.reset([parentWindow retain]);
115 sheets_.reset([[NSMutableArray alloc] init]);
117 [[NSNotificationCenter defaultCenter]
119 selector:@selector(onParentWindowWillClose:)
120 name:NSWindowWillCloseNotification
121 object:parentWindow_];
126 - (void)showSheet:(id<ConstrainedWindowSheet>)sheet
127 forParentView:(NSView*)parentView {
130 if (!activeView_.get())
131 activeView_.reset([parentView retain]);
133 // Observer the parent view's frame.
134 [parentView setPostsFrameChangedNotifications:YES];
135 [[NSNotificationCenter defaultCenter]
137 selector:@selector(onParentViewFrameDidChange:)
138 name:NSViewFrameDidChangeNotification
141 // Create an invisible overlay window.
142 NSRect rect = [self overlayWindowFrameForParentView:parentView];
143 base::scoped_nsobject<NSWindow> overlayWindow(
144 [[CWSheetOverlayWindow alloc] initWithContentRect:rect controller:self]);
145 [parentWindow_ addChildWindow:overlayWindow
146 ordered:NSWindowAbove];
148 // Add an entry for the sheet.
149 base::scoped_nsobject<ConstrainedWindowSheetInfo> info(
150 [[ConstrainedWindowSheetInfo alloc] initWithSheet:sheet
151 parentView:parentView
152 overlayWindow:overlayWindow]);
153 [sheets_ addObject:info];
155 // Show or hide the sheet.
156 if ([activeView_ isEqual:parentView])
162 - (NSPoint)originForSheet:(id<ConstrainedWindowSheet>)sheet
163 withWindowSize:(NSSize)size {
164 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
166 NSRect containerRect =
167 [self overlayWindowFrameForParentView:[info parentView]];
168 return [self originForSheetSize:size inContainerRect:containerRect];
171 - (void)closeSheet:(id<ConstrainedWindowSheet>)sheet {
172 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
174 [self closeSheet:info withAnimation:YES];
177 - (void)parentViewDidBecomeActive:(NSView*)parentView {
178 [[self findSheetInfoForParentView:activeView_] hideSheet];
179 activeView_.reset([parentView retain]);
180 [self updateSheetPosition:parentView];
181 [[self findSheetInfoForParentView:activeView_] showSheet];
184 - (void)pulseSheet:(id<ConstrainedWindowSheet>)sheet {
185 ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
187 if ([activeView_ isEqual:[info parentView]])
188 [[info sheet] pulseSheet];
192 return [sheets_ count];
195 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
196 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
197 if ([parentView isEqual:[info parentView]])
203 - (ConstrainedWindowSheetInfo*)
204 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
205 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
206 if ([sheet isEqual:[info sheet]])
212 - (void)onParentWindowWillClose:(NSNotification*)note {
213 [[NSNotificationCenter defaultCenter]
215 name:NSWindowWillCloseNotification
216 object:parentWindow_];
219 NSArray* sheets = [NSArray arrayWithArray:sheets_];
220 for (ConstrainedWindowSheetInfo* info in sheets)
221 [self closeSheet:info withAnimation:NO];
223 // Delete this instance.
224 [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
225 if (![g_sheetControllers count]) {
226 [g_sheetControllers release];
227 g_sheetControllers = nil;
231 - (void)onParentViewFrameDidChange:(NSNotification*)note {
232 NSView* parentView = [note object];
233 if (![activeView_ isEqual:parentView])
235 [self updateSheetPosition:parentView];
238 - (void)updateSheetPosition:(NSView*)parentView {
239 ConstrainedWindowSheetInfo* info =
240 [self findSheetInfoForParentView:parentView];
244 NSRect rect = [self overlayWindowFrameForParentView:parentView];
245 [[info overlayWindow] setFrame:rect display:YES];
246 [[info sheet] updateSheetPosition];
249 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
250 NSRect viewFrame = [parentView convertRect:[parentView bounds] toView:nil];
252 id<NSWindowDelegate> delegate = [[parentView window] delegate];
253 if ([delegate respondsToSelector:@selector(window:
256 NSRect sheetFrame = NSZeroRect;
257 // This API needs Y to be the distance from the bottom of the overlay to
258 // the top of the sheet. X, width, and height are ignored.
259 sheetFrame.origin.y = NSMaxY(viewFrame);
260 NSRect customSheetFrame = [delegate window:[parentView window]
261 willPositionSheet:nil
262 usingRect:sheetFrame];
263 viewFrame.size.height += NSMinY(customSheetFrame) - NSMinY(sheetFrame);
266 viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
270 - (NSPoint)originForSheetSize:(NSSize)sheetSize
271 inContainerRect:(NSRect)containerRect {
273 origin.x = roundf(NSMinX(containerRect) +
274 (NSWidth(containerRect) - sheetSize.width) / 2.0);
275 origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
279 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow {
280 for (ConstrainedWindowSheetInfo* curInfo in sheets_.get()) {
281 if ([overlayWindow isEqual:[curInfo overlayWindow]]) {
282 [self pulseSheet:[curInfo sheet]];
283 [[curInfo sheet] makeSheetKeyAndOrderFront];
289 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
290 withAnimation:(BOOL)withAnimation {
291 if (![sheets_ containsObject:info])
294 [[NSNotificationCenter defaultCenter]
296 name:NSViewFrameDidChangeNotification
297 object:[info parentView]];
299 [parentWindow_ removeChildWindow:[info overlayWindow]];
300 [[info sheet] closeSheetWithAnimation:withAnimation];
301 [[info overlayWindow] close];
302 [sheets_ removeObject:info];