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/tabs/tab_strip_controller.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];
26 // An invisible overlay window placed on top of the sheet's parent view.
27 // This window blocks interaction with the underlying view.
28 @interface CWSheetOverlayWindow : NSWindow {
29 base::scoped_nsobject<ConstrainedWindowSheetController> controller_;
33 @interface ConstrainedWindowSheetController ()
34 - (id)initWithParentWindow:(NSWindow*)parentWindow;
35 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView;
36 - (ConstrainedWindowSheetInfo*)
37 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet;
38 - (void)onParentWindowWillClose:(NSNotification*)note;
39 - (void)onParentWindowSizeDidChange:(NSNotification*)note;
40 - (void)updateSheetPosition:(NSView*)parentView;
41 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView;
42 - (NSPoint)originForSheetSize:(NSSize)sheetSize
43 inContainerRect:(NSRect)containerRect;
44 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow;
45 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
46 withAnimation:(BOOL)withAnimation;
49 @implementation CWSheetOverlayWindow
51 - (id)initWithContentRect:(NSRect)rect
52 controller:(ConstrainedWindowSheetController*)controller {
53 if ((self = [super initWithContentRect:rect
54 styleMask:NSBorderlessWindowMask
55 backing:NSBackingStoreBuffered
58 [self setBackgroundColor:[NSColor clearColor]];
59 [self setIgnoresMouseEvents:NO];
60 [self setReleasedWhenClosed:NO];
61 controller_.reset([controller retain]);
66 - (void)mouseDown:(NSEvent*)event {
67 [controller_ onOverlayWindowMouseDown:self];
72 @implementation ConstrainedWindowSheetController
74 + (ConstrainedWindowSheetController*)
75 controllerForParentWindow:(NSWindow*)parentWindow {
77 ConstrainedWindowSheetController* controller =
78 [g_sheetControllers objectForKey:GetKeyForParentWindow(parentWindow)];
82 base::scoped_nsobject<ConstrainedWindowSheetController> new_controller(
83 [[ConstrainedWindowSheetController alloc]
84 initWithParentWindow:parentWindow]);
85 if (!g_sheetControllers)
86 g_sheetControllers = [[NSMutableDictionary alloc] init];
87 [g_sheetControllers setObject:new_controller
88 forKey:GetKeyForParentWindow(parentWindow)];
89 return new_controller;
92 + (ConstrainedWindowSheetController*)
93 controllerForSheet:(id<ConstrainedWindowSheet>)sheet {
94 for (ConstrainedWindowSheetController* controller in
95 [g_sheetControllers objectEnumerator]) {
96 if ([controller findSheetInfoForSheet:sheet])
102 + (id<ConstrainedWindowSheet>)sheetForOverlayWindow:(NSWindow*)overlayWindow {
103 for (ConstrainedWindowSheetController* controller in
104 [g_sheetControllers objectEnumerator]) {
105 for (ConstrainedWindowSheetInfo* info in controller->sheets_.get()) {
106 if ([overlayWindow isEqual:[info overlayWindow]])
113 - (id)initWithParentWindow:(NSWindow*)parentWindow {
114 if ((self = [super init])) {
115 parentWindow_.reset([parentWindow retain]);
116 sheets_.reset([[NSMutableArray alloc] init]);
118 [[NSNotificationCenter defaultCenter]
120 selector:@selector(onParentWindowWillClose:)
121 name:NSWindowWillCloseNotification
122 object:parentWindow_];
127 - (void)showSheet:(id<ConstrainedWindowSheet>)sheet
128 forParentView:(NSView*)parentView {
131 if (!activeView_.get())
132 activeView_.reset([parentView retain]);
134 // Observe the parent window's size.
135 [[NSNotificationCenter defaultCenter]
137 selector:@selector(onParentWindowSizeDidChange:)
138 name:NSWindowDidResizeNotification
139 object:parentWindow_];
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)onParentWindowSizeDidChange:(NSNotification*)note {
232 [self updateSheetPosition:activeView_];
235 - (void)updateSheetPosition:(NSView*)parentView {
236 ConstrainedWindowSheetInfo* info =
237 [self findSheetInfoForParentView:parentView];
241 NSRect rect = [self overlayWindowFrameForParentView:parentView];
242 [[info overlayWindow] setFrame:rect display:YES];
243 [[info sheet] updateSheetPosition];
246 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
247 NSRect viewFrame = GetSheetParentBoundsForParentView(parentView);
249 id<NSWindowDelegate> delegate = [[parentView window] delegate];
250 if ([delegate respondsToSelector:@selector(window:
253 NSRect sheetFrame = NSZeroRect;
254 // This API needs Y to be the distance from the bottom of the overlay to
255 // the top of the sheet. X, width, and height are ignored.
256 sheetFrame.origin.y = NSMaxY(viewFrame);
257 NSRect customSheetFrame = [delegate window:[parentView window]
258 willPositionSheet:nil
259 usingRect:sheetFrame];
260 viewFrame.size.height += NSMinY(customSheetFrame) - NSMinY(sheetFrame);
263 viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
267 - (NSPoint)originForSheetSize:(NSSize)sheetSize
268 inContainerRect:(NSRect)containerRect {
270 origin.x = roundf(NSMinX(containerRect) +
271 (NSWidth(containerRect) - sheetSize.width) / 2.0);
272 origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
276 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow {
277 for (ConstrainedWindowSheetInfo* curInfo in sheets_.get()) {
278 if ([overlayWindow isEqual:[curInfo overlayWindow]]) {
279 [self pulseSheet:[curInfo sheet]];
280 [[curInfo sheet] makeSheetKeyAndOrderFront];
286 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
287 withAnimation:(BOOL)withAnimation {
288 if (![sheets_ containsObject:info])
291 [[NSNotificationCenter defaultCenter]
293 name:NSWindowDidResizeNotification
294 object:parentWindow_];
296 [parentWindow_ removeChildWindow:[info overlayWindow]];
297 [[info sheet] closeSheetWithAnimation:withAnimation];
298 [[info overlayWindow] close];
299 [sheets_ removeObject:info];