- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / constrained_window / constrained_window_sheet_controller.mm
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.
4
5 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet_controller.h"
6
7 #include <map>
8
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
13 namespace {
14
15 // Maps parent windows to sheet controllers.
16 NSMutableDictionary* g_sheetControllers;
17
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];
21 }
22
23 }  // namespace
24
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_;
29 }
30 @end
31
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;
46 @end
47
48 @implementation CWSheetOverlayWindow
49
50 - (id)initWithContentRect:(NSRect)rect
51                controller:(ConstrainedWindowSheetController*)controller {
52   if ((self = [super initWithContentRect:rect
53                                styleMask:NSBorderlessWindowMask
54                                  backing:NSBackingStoreBuffered
55                                    defer:NO])) {
56     [self setOpaque:NO];
57     [self setBackgroundColor:[NSColor clearColor]];
58     [self setIgnoresMouseEvents:NO];
59     [self setReleasedWhenClosed:NO];
60     controller_.reset([controller retain]);
61   }
62   return self;
63 }
64
65 - (void)mouseDown:(NSEvent*)event {
66   [controller_ onOverlayWindowMouseDown:self];
67 }
68
69 @end
70
71 @implementation ConstrainedWindowSheetController
72
73 + (ConstrainedWindowSheetController*)
74     controllerForParentWindow:(NSWindow*)parentWindow {
75   DCHECK(parentWindow);
76   ConstrainedWindowSheetController* controller =
77       [g_sheetControllers objectForKey:GetKeyForParentWindow(parentWindow)];
78   if (controller)
79     return controller;
80
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;
89 }
90
91 + (ConstrainedWindowSheetController*)
92     controllerForSheet:(id<ConstrainedWindowSheet>)sheet {
93   for (ConstrainedWindowSheetController* controller in
94        [g_sheetControllers objectEnumerator]) {
95     if ([controller findSheetInfoForSheet:sheet])
96       return controller;
97   }
98   return nil;
99 }
100
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]])
106         return [info sheet];
107     }
108   }
109   return nil;
110 }
111
112 - (id)initWithParentWindow:(NSWindow*)parentWindow {
113   if ((self = [super init])) {
114     parentWindow_.reset([parentWindow retain]);
115     sheets_.reset([[NSMutableArray alloc] init]);
116
117     [[NSNotificationCenter defaultCenter]
118         addObserver:self
119            selector:@selector(onParentWindowWillClose:)
120                name:NSWindowWillCloseNotification
121              object:parentWindow_];
122   }
123   return self;
124 }
125
126 - (void)showSheet:(id<ConstrainedWindowSheet>)sheet
127     forParentView:(NSView*)parentView {
128   DCHECK(sheet);
129   DCHECK(parentView);
130   if (!activeView_.get())
131     activeView_.reset([parentView retain]);
132
133   // Observer the parent view's frame.
134   [parentView setPostsFrameChangedNotifications:YES];
135   [[NSNotificationCenter defaultCenter]
136       addObserver:self
137          selector:@selector(onParentViewFrameDidChange:)
138              name:NSViewFrameDidChangeNotification
139            object:parentView];
140
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];
147
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];
154
155   // Show or hide the sheet.
156   if ([activeView_ isEqual:parentView])
157     [info showSheet];
158   else
159     [info hideSheet];
160 }
161
162 - (NSPoint)originForSheet:(id<ConstrainedWindowSheet>)sheet
163            withWindowSize:(NSSize)size {
164   ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
165   DCHECK(info);
166   NSRect containerRect =
167       [self overlayWindowFrameForParentView:[info parentView]];
168   return [self originForSheetSize:size inContainerRect:containerRect];
169 }
170
171 - (void)closeSheet:(id<ConstrainedWindowSheet>)sheet {
172   ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
173   DCHECK(info);
174   [self closeSheet:info withAnimation:YES];
175 }
176
177 - (void)parentViewDidBecomeActive:(NSView*)parentView {
178   [[self findSheetInfoForParentView:activeView_] hideSheet];
179   activeView_.reset([parentView retain]);
180   [self updateSheetPosition:parentView];
181   [[self findSheetInfoForParentView:activeView_] showSheet];
182 }
183
184 - (void)pulseSheet:(id<ConstrainedWindowSheet>)sheet {
185   ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
186   DCHECK(info);
187   if ([activeView_ isEqual:[info parentView]])
188     [[info sheet] pulseSheet];
189 }
190
191 - (int)sheetCount {
192   return [sheets_ count];
193 }
194
195 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
196   for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
197     if ([parentView isEqual:[info parentView]])
198       return info;
199   }
200   return NULL;
201 }
202
203 - (ConstrainedWindowSheetInfo*)
204     findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
205   for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
206     if ([sheet isEqual:[info sheet]])
207       return info;
208   }
209   return NULL;
210 }
211
212 - (void)onParentWindowWillClose:(NSNotification*)note {
213   [[NSNotificationCenter defaultCenter]
214       removeObserver:self
215                 name:NSWindowWillCloseNotification
216               object:parentWindow_];
217
218   // Close all sheets.
219   NSArray* sheets = [NSArray arrayWithArray:sheets_];
220   for (ConstrainedWindowSheetInfo* info in sheets)
221     [self closeSheet:info withAnimation:NO];
222
223   // Delete this instance.
224   [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
225   if (![g_sheetControllers count]) {
226     [g_sheetControllers release];
227     g_sheetControllers = nil;
228   }
229 }
230
231 - (void)onParentViewFrameDidChange:(NSNotification*)note {
232   NSView* parentView = [note object];
233   if (![activeView_ isEqual:parentView])
234     return;
235   [self updateSheetPosition:parentView];
236 }
237
238 - (void)updateSheetPosition:(NSView*)parentView {
239   ConstrainedWindowSheetInfo* info =
240       [self findSheetInfoForParentView:parentView];
241   if (!info)
242     return;
243
244   NSRect rect = [self overlayWindowFrameForParentView:parentView];
245   [[info overlayWindow] setFrame:rect display:YES];
246   [[info sheet] updateSheetPosition];
247 }
248
249 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
250   NSRect viewFrame = [parentView convertRect:[parentView bounds] toView:nil];
251
252   id<NSWindowDelegate> delegate = [[parentView window] delegate];
253   if ([delegate respondsToSelector:@selector(window:
254                                   willPositionSheet:
255                                           usingRect:)]) {
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);
264   }
265
266   viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
267   return viewFrame;
268 }
269
270 - (NSPoint)originForSheetSize:(NSSize)sheetSize
271               inContainerRect:(NSRect)containerRect {
272   NSPoint origin;
273   origin.x = roundf(NSMinX(containerRect) +
274                     (NSWidth(containerRect) - sheetSize.width) / 2.0);
275   origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
276   return origin;
277 }
278
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];
284       break;
285     }
286   }
287 }
288
289 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
290      withAnimation:(BOOL)withAnimation {
291   if (![sheets_ containsObject:info])
292     return;
293
294   [[NSNotificationCenter defaultCenter]
295       removeObserver:self
296                 name:NSViewFrameDidChangeNotification
297               object:[info parentView]];
298
299   [parentWindow_ removeChildWindow:[info overlayWindow]];
300   [[info sheet] closeSheetWithAnimation:withAnimation];
301   [[info overlayWindow] close];
302   [sheets_ removeObject:info];
303 }
304
305 @end