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 - (void)updateSheetPosition {
196 [self updateSheetPosition:activeView_];
199 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
200 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
201 if ([parentView isEqual:[info parentView]])
207 - (ConstrainedWindowSheetInfo*)
208 findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
209 for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
210 if ([sheet isEqual:[info sheet]])
216 - (void)onParentWindowWillClose:(NSNotification*)note {
217 [[NSNotificationCenter defaultCenter]
219 name:NSWindowWillCloseNotification
220 object:parentWindow_];
223 NSArray* sheets = [NSArray arrayWithArray:sheets_];
224 for (ConstrainedWindowSheetInfo* info in sheets)
225 [self closeSheet:info withAnimation:NO];
227 // Delete this instance.
228 [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
229 if (![g_sheetControllers count]) {
230 [g_sheetControllers release];
231 g_sheetControllers = nil;
235 - (void)onParentWindowSizeDidChange:(NSNotification*)note {
236 [self updateSheetPosition:activeView_];
239 - (void)updateSheetPosition:(NSView*)parentView {
240 ConstrainedWindowSheetInfo* info =
241 [self findSheetInfoForParentView:parentView];
245 NSRect rect = [self overlayWindowFrameForParentView:parentView];
246 [[info overlayWindow] setFrame:rect display:YES];
247 [[info sheet] updateSheetPosition];
250 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
251 NSRect viewFrame = GetSheetParentBoundsForParentView(parentView);
253 id<NSWindowDelegate> delegate = [[parentView window] delegate];
254 if ([delegate respondsToSelector:@selector(window:
257 NSRect sheetFrame = NSZeroRect;
258 // This API needs Y to be the distance from the bottom of the overlay to
259 // the top of the sheet. X, width, and height are ignored.
260 sheetFrame.origin.y = NSMaxY(viewFrame);
261 NSRect customSheetFrame = [delegate window:[parentView window]
262 willPositionSheet:nil
263 usingRect:sheetFrame];
264 viewFrame.size.height += NSMinY(customSheetFrame) - NSMinY(sheetFrame);
267 viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
271 - (NSPoint)originForSheetSize:(NSSize)sheetSize
272 inContainerRect:(NSRect)containerRect {
274 origin.x = roundf(NSMinX(containerRect) +
275 (NSWidth(containerRect) - sheetSize.width) / 2.0);
276 origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
280 - (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow {
281 for (ConstrainedWindowSheetInfo* curInfo in sheets_.get()) {
282 if ([overlayWindow isEqual:[curInfo overlayWindow]]) {
283 [self pulseSheet:[curInfo sheet]];
284 [[curInfo sheet] makeSheetKeyAndOrderFront];
290 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
291 withAnimation:(BOOL)withAnimation {
292 if (![sheets_ containsObject:info])
295 [[NSNotificationCenter defaultCenter]
297 name:NSWindowDidResizeNotification
298 object:parentWindow_];
300 [parentWindow_ removeChildWindow:[info overlayWindow]];
301 [[info sheet] closeSheetWithAnimation:withAnimation];
302 [[info overlayWindow] close];
303 [sheets_ removeObject:info];