1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6#include "nsWindowMap.h"
7#include "nsObjCExceptions.h"
8#include "nsChildView.h"
9#include "nsCocoaWindow.h"
10
11@interface WindowDataMap (Private)
12
13- (NSString*)keyForWindow:(NSWindow*)inWindow;
14
15@end
16
17@interface TopLevelWindowData (Private)
18
19- (void)windowResignedKey:(NSNotification*)inNotification;
20- (void)windowBecameKey:(NSNotification*)inNotification;
21- (void)windowWillClose:(NSNotification*)inNotification;
22
23@end
24
25#pragma mark -
26
27@implementation WindowDataMap
28
29+ (WindowDataMap*)sharedWindowDataMap {
30  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
31
32  static WindowDataMap* sWindowMap = nil;
33  if (!sWindowMap) sWindowMap = [[WindowDataMap alloc] init];
34
35  return sWindowMap;
36
37  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
38}
39
40- (id)init {
41  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
42
43  if ((self = [super init])) {
44    mWindowMap = [[NSMutableDictionary alloc] initWithCapacity:10];
45  }
46  return self;
47
48  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
49}
50
51- (void)dealloc {
52  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
53
54  [mWindowMap release];
55  [super dealloc];
56
57  NS_OBJC_END_TRY_IGNORE_BLOCK;
58}
59
60- (void)ensureDataForWindow:(NSWindow*)inWindow {
61  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
62
63  if (!inWindow || [self dataForWindow:inWindow]) return;
64
65  TopLevelWindowData* windowData = [[TopLevelWindowData alloc] initWithWindow:inWindow];
66  [self setData:windowData forWindow:inWindow];  // takes ownership
67  [windowData release];
68
69  NS_OBJC_END_TRY_IGNORE_BLOCK;
70}
71
72- (id)dataForWindow:(NSWindow*)inWindow {
73  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
74
75  return [mWindowMap objectForKey:[self keyForWindow:inWindow]];
76
77  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
78}
79
80- (void)setData:(id)inData forWindow:(NSWindow*)inWindow {
81  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
82
83  [mWindowMap setObject:inData forKey:[self keyForWindow:inWindow]];
84
85  NS_OBJC_END_TRY_IGNORE_BLOCK;
86}
87
88- (void)removeDataForWindow:(NSWindow*)inWindow {
89  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
90
91  [mWindowMap removeObjectForKey:[self keyForWindow:inWindow]];
92
93  NS_OBJC_END_TRY_IGNORE_BLOCK;
94}
95
96- (NSString*)keyForWindow:(NSWindow*)inWindow {
97  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
98
99  return [NSString stringWithFormat:@"%p", inWindow];
100
101  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
102}
103
104@end
105
106//  TopLevelWindowData
107//
108//  This class holds data about top-level windows. We can't use a window
109//  delegate, because an embedder may already have one.
110
111@implementation TopLevelWindowData
112
113- (id)initWithWindow:(NSWindow*)inWindow {
114  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
115
116  if ((self = [super init])) {
117    [[NSNotificationCenter defaultCenter] addObserver:self
118                                             selector:@selector(windowBecameKey:)
119                                                 name:NSWindowDidBecomeKeyNotification
120                                               object:inWindow];
121
122    [[NSNotificationCenter defaultCenter] addObserver:self
123                                             selector:@selector(windowResignedKey:)
124                                                 name:NSWindowDidResignKeyNotification
125                                               object:inWindow];
126
127    [[NSNotificationCenter defaultCenter] addObserver:self
128                                             selector:@selector(windowBecameMain:)
129                                                 name:NSWindowDidBecomeMainNotification
130                                               object:inWindow];
131
132    [[NSNotificationCenter defaultCenter] addObserver:self
133                                             selector:@selector(windowResignedMain:)
134                                                 name:NSWindowDidResignMainNotification
135                                               object:inWindow];
136
137    [[NSNotificationCenter defaultCenter] addObserver:self
138                                             selector:@selector(windowWillClose:)
139                                                 name:NSWindowWillCloseNotification
140                                               object:inWindow];
141  }
142  return self;
143
144  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
145}
146
147- (void)dealloc {
148  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
149
150  [[NSNotificationCenter defaultCenter] removeObserver:self];
151  [super dealloc];
152
153  NS_OBJC_END_TRY_IGNORE_BLOCK;
154}
155
156// As best I can tell, if the notification's object has a corresponding
157// top-level widget (an nsCocoaWindow object), it has a delegate (set in
158// nsCocoaWindow::StandardCreate()) of class WindowDelegate, and otherwise
159// not (Camino didn't use top-level widgets (nsCocoaWindow objects) --
160// only child widgets (nsChildView objects)).  (The notification is sent
161// to windowBecameKey: or windowBecameMain: below.)
162//
163// For use with clients that (like Firefox) do use top-level widgets (and
164// have NSWindow delegates of class WindowDelegate).
165+ (void)activateInWindow:(NSWindow*)aWindow {
166  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
167
168  WindowDelegate* delegate = (WindowDelegate*)[aWindow delegate];
169  if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) return;
170
171  if ([delegate toplevelActiveState]) return;
172  [delegate sendToplevelActivateEvents];
173
174  NS_OBJC_END_TRY_IGNORE_BLOCK;
175}
176
177// See comments above activateInWindow:
178//
179// If we're using top-level widgets (nsCocoaWindow objects), we send them
180// NS_DEACTIVATE events (which propagate to child widgets (nsChildView
181// objects) via nsWebShellWindow::HandleEvent()).
182//
183// For use with clients that (like Firefox) do use top-level widgets (and
184// have NSWindow delegates of class WindowDelegate).
185+ (void)deactivateInWindow:(NSWindow*)aWindow {
186  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
187
188  WindowDelegate* delegate = (WindowDelegate*)[aWindow delegate];
189  if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) return;
190
191  if (![delegate toplevelActiveState]) return;
192  [delegate sendToplevelDeactivateEvents];
193
194  NS_OBJC_END_TRY_IGNORE_BLOCK;
195}
196
197// For use with clients that (like Camino) don't use top-level widgets (and
198// don't have NSWindow delegates of class WindowDelegate).
199+ (void)activateInWindowViews:(NSWindow*)aWindow {
200  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
201
202  id firstResponder = [aWindow firstResponder];
203  if ([firstResponder isKindOfClass:[ChildView class]]) [firstResponder viewsWindowDidBecomeKey];
204
205  NS_OBJC_END_TRY_IGNORE_BLOCK;
206}
207
208// For use with clients that (like Camino) don't use top-level widgets (and
209// don't have NSWindow delegates of class WindowDelegate).
210+ (void)deactivateInWindowViews:(NSWindow*)aWindow {
211  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
212
213  id firstResponder = [aWindow firstResponder];
214  if ([firstResponder isKindOfClass:[ChildView class]]) [firstResponder viewsWindowDidResignKey];
215
216  NS_OBJC_END_TRY_IGNORE_BLOCK;
217}
218
219// We make certain exceptions for top-level windows in non-embedders (see
220// comment above windowBecameMain below).  And we need (elsewhere) to guard
221// against sending duplicate events.  But in general the NS_ACTIVATE event
222// should be sent when a native window becomes key, and the NS_DEACTIVATE
223// event should be sent when it resignes key.
224- (void)windowBecameKey:(NSNotification*)inNotification {
225  NSWindow* window = (NSWindow*)[inNotification object];
226
227  id delegate = [window delegate];
228  if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
229    [TopLevelWindowData activateInWindowViews:window];
230  } else if ([window isSheet]) {
231    [TopLevelWindowData activateInWindow:window];
232  }
233}
234
235- (void)windowResignedKey:(NSNotification*)inNotification {
236  NSWindow* window = (NSWindow*)[inNotification object];
237
238  id delegate = [window delegate];
239  if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
240    [TopLevelWindowData deactivateInWindowViews:window];
241  } else if ([window isSheet]) {
242    [TopLevelWindowData deactivateInWindow:window];
243  }
244}
245
246// The appearance of a top-level window depends on its main state (not its key
247// state).  So (for non-embedders) we need to ensure that a top-level window
248// is main when an NS_ACTIVATE event is sent to Gecko for it.
249- (void)windowBecameMain:(NSNotification*)inNotification {
250  NSWindow* window = (NSWindow*)[inNotification object];
251
252  id delegate = [window delegate];
253  // Don't send events to a top-level window that has a sheet open above it --
254  // as far as Gecko is concerned, it's inactive, and stays so until the sheet
255  // closes.
256  if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
257    [TopLevelWindowData activateInWindow:window];
258}
259
260- (void)windowResignedMain:(NSNotification*)inNotification {
261  NSWindow* window = (NSWindow*)[inNotification object];
262
263  id delegate = [window delegate];
264  if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
265    [TopLevelWindowData deactivateInWindow:window];
266}
267
268- (void)windowWillClose:(NSNotification*)inNotification {
269  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
270
271  // postpone our destruction
272  [[self retain] autorelease];
273
274  // remove ourselves from the window map (which owns us)
275  [[WindowDataMap sharedWindowDataMap] removeDataForWindow:[inNotification object]];
276
277  NS_OBJC_END_TRY_IGNORE_BLOCK;
278}
279
280@end
281