• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
13namespace {
14
15// Maps parent windows to sheet controllers.
16NSMutableDictionary* g_sheetControllers;
17
18// Get a value for the given window that can be used as a key in a dictionary.
19NSValue* 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)onParentWindowSizeDidChange:(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  // Observe the parent window's size.
134  [[NSNotificationCenter defaultCenter]
135      addObserver:self
136         selector:@selector(onParentWindowSizeDidChange:)
137             name:NSWindowDidResizeNotification
138           object:parentWindow_];
139
140  // Create an invisible overlay window.
141  NSRect rect = [self overlayWindowFrameForParentView:parentView];
142  base::scoped_nsobject<NSWindow> overlayWindow(
143      [[CWSheetOverlayWindow alloc] initWithContentRect:rect controller:self]);
144  [parentWindow_ addChildWindow:overlayWindow
145                        ordered:NSWindowAbove];
146
147  // Add an entry for the sheet.
148  base::scoped_nsobject<ConstrainedWindowSheetInfo> info(
149      [[ConstrainedWindowSheetInfo alloc] initWithSheet:sheet
150                                             parentView:parentView
151                                          overlayWindow:overlayWindow]);
152  [sheets_ addObject:info];
153
154  // Show or hide the sheet.
155  if ([activeView_ isEqual:parentView])
156    [info showSheet];
157  else
158    [info hideSheet];
159}
160
161- (NSPoint)originForSheet:(id<ConstrainedWindowSheet>)sheet
162           withWindowSize:(NSSize)size {
163  ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
164  DCHECK(info);
165  NSRect containerRect =
166      [self overlayWindowFrameForParentView:[info parentView]];
167  return [self originForSheetSize:size inContainerRect:containerRect];
168}
169
170- (void)closeSheet:(id<ConstrainedWindowSheet>)sheet {
171  ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
172  DCHECK(info);
173  [self closeSheet:info withAnimation:YES];
174}
175
176- (void)parentViewDidBecomeActive:(NSView*)parentView {
177  [[self findSheetInfoForParentView:activeView_] hideSheet];
178  activeView_.reset([parentView retain]);
179  [self updateSheetPosition:parentView];
180  [[self findSheetInfoForParentView:activeView_] showSheet];
181}
182
183- (void)pulseSheet:(id<ConstrainedWindowSheet>)sheet {
184  ConstrainedWindowSheetInfo* info = [self findSheetInfoForSheet:sheet];
185  DCHECK(info);
186  if ([activeView_ isEqual:[info parentView]])
187    [[info sheet] pulseSheet];
188}
189
190- (int)sheetCount {
191  return [sheets_ count];
192}
193
194- (ConstrainedWindowSheetInfo*)findSheetInfoForParentView:(NSView*)parentView {
195  for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
196    if ([parentView isEqual:[info parentView]])
197      return info;
198  }
199  return NULL;
200}
201
202- (ConstrainedWindowSheetInfo*)
203    findSheetInfoForSheet:(id<ConstrainedWindowSheet>)sheet {
204  for (ConstrainedWindowSheetInfo* info in sheets_.get()) {
205    if ([sheet isEqual:[info sheet]])
206      return info;
207  }
208  return NULL;
209}
210
211- (void)onParentWindowWillClose:(NSNotification*)note {
212  [[NSNotificationCenter defaultCenter]
213      removeObserver:self
214                name:NSWindowWillCloseNotification
215              object:parentWindow_];
216
217  // Close all sheets.
218  NSArray* sheets = [NSArray arrayWithArray:sheets_];
219  for (ConstrainedWindowSheetInfo* info in sheets)
220    [self closeSheet:info withAnimation:NO];
221
222  // Delete this instance.
223  [g_sheetControllers removeObjectForKey:GetKeyForParentWindow(parentWindow_)];
224  if (![g_sheetControllers count]) {
225    [g_sheetControllers release];
226    g_sheetControllers = nil;
227  }
228}
229
230- (void)onParentWindowSizeDidChange:(NSNotification*)note {
231  [self updateSheetPosition:activeView_];
232}
233
234- (void)updateSheetPosition:(NSView*)parentView {
235  ConstrainedWindowSheetInfo* info =
236      [self findSheetInfoForParentView:parentView];
237  if (!info)
238    return;
239
240  NSRect rect = [self overlayWindowFrameForParentView:parentView];
241  [[info overlayWindow] setFrame:rect display:YES];
242  [[info sheet] updateSheetPosition];
243}
244
245- (NSRect)overlayWindowFrameForParentView:(NSView*)parentView {
246  NSRect viewFrame = [parentView convertRect:[parentView bounds] toView:nil];
247
248  id<NSWindowDelegate> delegate = [[parentView window] delegate];
249  if ([delegate respondsToSelector:@selector(window:
250                                  willPositionSheet:
251                                          usingRect:)]) {
252    NSRect sheetFrame = NSZeroRect;
253    // This API needs Y to be the distance from the bottom of the overlay to
254    // the top of the sheet. X, width, and height are ignored.
255    sheetFrame.origin.y = NSMaxY(viewFrame);
256    NSRect customSheetFrame = [delegate window:[parentView window]
257                             willPositionSheet:nil
258                                     usingRect:sheetFrame];
259    viewFrame.size.height += NSMinY(customSheetFrame) - NSMinY(sheetFrame);
260  }
261
262  viewFrame.origin = [[parentView window] convertBaseToScreen:viewFrame.origin];
263  return viewFrame;
264}
265
266- (NSPoint)originForSheetSize:(NSSize)sheetSize
267              inContainerRect:(NSRect)containerRect {
268  NSPoint origin;
269  origin.x = roundf(NSMinX(containerRect) +
270                    (NSWidth(containerRect) - sheetSize.width) / 2.0);
271  origin.y = NSMaxY(containerRect) + 5 - sheetSize.height;
272  return origin;
273}
274
275- (void)onOverlayWindowMouseDown:(CWSheetOverlayWindow*)overlayWindow {
276  for (ConstrainedWindowSheetInfo* curInfo in sheets_.get()) {
277    if ([overlayWindow isEqual:[curInfo overlayWindow]]) {
278      [self pulseSheet:[curInfo sheet]];
279      [[curInfo sheet] makeSheetKeyAndOrderFront];
280      break;
281    }
282  }
283}
284
285- (void)closeSheet:(ConstrainedWindowSheetInfo*)info
286     withAnimation:(BOOL)withAnimation {
287  if (![sheets_ containsObject:info])
288    return;
289
290  [[NSNotificationCenter defaultCenter]
291      removeObserver:self
292                name:NSWindowDidResizeNotification
293              object:parentWindow_];
294
295  [parentWindow_ removeChildWindow:[info overlayWindow]];
296  [[info sheet] closeSheetWithAnimation:withAnimation];
297  [[info overlayWindow] close];
298  [sheets_ removeObject:info];
299}
300
301@end
302