Upstream version 11.40.277.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 - (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)onParentWindowSizeDidChange:(NSNotification*)note {
232   [self updateSheetPosition:activeView_];
233 }
234
235 - (void)updateSheetPosition:(NSView*)parentView {
236   ConstrainedWindowSheetInfo* info =
237       [self findSheetInfoForParentView:parentView];
238   if (!info)
239     return;
240
241   NSRect rect = [self overlayWindowFrameForParentView:parentView];
242   [[info overlayWindow] setFrame:rect display:YES];
243   [[info sheet] updateSheetPosition];
244 }
245
246 - (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
247   NSRect viewFrame = GetSheetParentBoundsForParentView(parentView);
248
249   id<NSWindowDelegate> delegate = [[parentView window] delegate];
250   if ([delegate respondsToSelector:@selector(window:
251                                   willPositionSheet:
252                                           usingRect:)]) {
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);
261   }
262
263   viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
264   return viewFrame;
265 }
266
267 - (NSPoint)originForSheetSize:(NSSize)sheetSize
268               inContainerRect:(NSRect)containerRect {
269   NSPoint origin;
270   origin.x = roundf(NSMinX(containerRect) +
271                     (NSWidth(containerRect) - sheetSize.width) / 2.0);
272   origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
273   return origin;
274 }
275
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];
281       break;
282     }
283   }
284 }
285
286 - (void)closeSheet:(ConstrainedWindowSheetInfo*)info
287      withAnimation:(BOOL)withAnimation {
288   if (![sheets_ containsObject:info])
289     return;
290
291   [[NSNotificationCenter defaultCenter]
292       removeObserver:self
293                 name:NSWindowDidResizeNotification
294               object:parentWindow_];
295
296   [parentWindow_ removeChildWindow:[info overlayWindow]];
297   [[info sheet] closeSheetWithAnimation:withAnimation];
298   [[info overlayWindow] close];
299   [sheets_ removeObject:info];
300 }
301
302 @end