Upstream version 10.39.225.0
[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 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
13
14 namespace {
15
16 // Maps parent windows to sheet controllers.
17 NSMutableDictionary* g_sheetControllers;
18
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];
22 }
23
24 }  // namespace
25
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_;
30 }
31 @end
32
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;
47 @end
48
49 @implementation CWSheetOverlayWindow
50
51 - (id)initWithContentRect:(NSRect)rect
52                controller:(ConstrainedWindowSheetController*)controller {
53   if ((self = [super initWithContentRect:rect
54                                styleMask:NSBorderlessWindowMask
55                                  backing:NSBackingStoreBuffered
56                                    defer:NO])) {
57     [self setOpaque:NO];
58     [self setBackgroundColor:[NSColor clearColor]];
59     [self setIgnoresMouseEvents:NO];
60     [self setReleasedWhenClosed:NO];
61     controller_.reset([controller retain]);
62   }
63   return self;
64 }
65
66 - (void)mouseDown:(NSEvent*)event {
67   [controller_ onOverlayWindowMouseDown:self];
68 }
69
70 @end
71
72 @implementation ConstrainedWindowSheetController
73
74 + (ConstrainedWindowSheetController*)
75     controllerForParentWindow:(NSWindow*)parentWindow {
76   DCHECK(parentWindow);
77   ConstrainedWindowSheetController* controller =
78       [g_sheetControllers objectForKey:GetKeyForParentWindow(parentWindow)];
79   if (controller)
80     return controller;
81
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;
90 }
91
92 + (ConstrainedWindowSheetController*)
93     controllerForSheet:(id<ConstrainedWindowSheet>)sheet {
94   for (ConstrainedWindowSheetController* controller in
95        [g_sheetControllers objectEnumerator]) {
96     if ([controller findSheetInfoForSheet:sheet])
97       return controller;
98   }
99   return nil;
100 }
101
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]])
107         return [info sheet];
108     }
109   }
110   return nil;
111 }
112
113 - (id)initWithParentWindow:(NSWindow*)parentWindow {
114   if ((self = [super init])) {
115     parentWindow_.reset([parentWindow retain]);
116     sheets_.reset([[NSMutableArray alloc] init]);
117
118     [[NSNotificationCenter defaultCenter]
119         addObserver:self
120            selector:@selector(onParentWindowWillClose:)
121                name:NSWindowWillCloseNotification
122              object:parentWindow_];
123   }
124   return self;
125 }
126
127 - (void)showSheet:(id<ConstrainedWindowSheet>)sheet
128     forParentView:(NSView*)parentView {
129   DCHECK(sheet);
130   DCHECK(parentView);
131   if (!activeView_.get())
132     activeView_.reset([parentView retain]);
133
134   // Observe the parent window's size.
135   [[NSNotificationCenter defaultCenter]
136       addObserver:self
137          selector:@selector(onParentWindowSizeDidChange:)
138              name:NSWindowDidResizeNotification
139            object:parentWindow_];
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 - (void)updateSheetPosition {
196   [self updateSheetPosition:activeView_];
197 }
198
199 - (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
200   for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
201     if ([parentView isEqual:[info parentView]])
202       return info;
203   }
204   return NULL;
205 }
206
207 - (ConstrainedWindowSheetInfo*)
208     findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
209   for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
210     if ([sheet isEqual:[info sheet]])
211       return info;
212   }
213   return NULL;
214 }
215
216 - (void)onParentWindowWillClose:(NSNotification*)note {
217   [[NSNotificationCenter defaultCenter]
218       removeObserver:self
219                 name:NSWindowWillCloseNotification
220               object:parentWindow_];
221
222   // Close all sheets.
223   NSArray* sheets = [NSArray arrayWithArray:sheets_];
224   for (ConstrainedWindowSheetInfo* info in sheets)
225     [self closeSheet:info withAnimation:NO];
226
227   // Delete this instance.
228   [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
229   if (![g_sheetControllers count]) {
230     [g_sheetControllers release];
231     g_sheetControllers = nil;
232   }
233 }
234
235 - (void)onParentWindowSizeDidChange:(NSNotification*)note {
236   [self updateSheetPosition:activeView_];
237 }
238
239 - (void)updateSheetPosition:(NSView*)parentView {
240   ConstrainedWindowSheetInfo* info =
241       [self findSheetInfoForParentView:parentView];
242   if (!info)
243     return;
244
245   NSRect rect = [self overlayWindowFrameForParentView:parentView];
246   [[info overlayWindow] setFrame:rect display:YES];
247   [[info sheet] updateSheetPosition];
248 }
249
250 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
251   NSRect viewFrame = GetSheetParentBoundsForParentView(parentView);
252
253   id<NSWindowDelegate> delegate = [[parentView window] delegate];
254   if ([delegate respondsToSelector:@selector(window:
255                                   willPositionSheet:
256                                           usingRect:)]) {
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);
265   }
266
267   viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
268   return viewFrame;
269 }
270
271 - (NSPoint)originForSheetSize:(NSSize)sheetSize
272               inContainerRect:(NSRect)containerRect {
273   NSPoint origin;
274   origin.x = roundf(NSMinX(containerRect) +
275                     (NSWidth(containerRect) - sheetSize.width) / 2.0);
276   origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
277   return origin;
278 }
279
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];
285       break;
286     }
287   }
288 }
289
290 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
291      withAnimation:(BOOL)withAnimation {
292   if (![sheets_ containsObject:info])
293     return;
294
295   [[NSNotificationCenter defaultCenter]
296       removeObserver:self
297                 name:NSWindowDidResizeNotification
298               object:parentWindow_];
299
300   [parentWindow_ removeChildWindow:[info overlayWindow]];
301   [[info sheet] closeSheetWithAnimation:withAnimation];
302   [[info overlayWindow] close];
303   [sheets_ removeObject:info];
304 }
305
306 @end