1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License
6 * Version 1.1 (the "License"); you may not use this file except in
7 * compliance with the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Simon Fraser <smfr@smfr.org>
24 * Josh Aas <josh@mozilla.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsWindowMap.h"
41 #include "nsObjCExceptions.h"
42 #include "nsChildView.h"
43 #include "nsCocoaWindow.h"
45 @interface WindowDataMap(Private)
47 - (NSString*)keyForWindow:(NSWindow*)inWindow;
51 @interface TopLevelWindowData(Private)
53 - (void)windowResignedKey:(NSNotification*)inNotification;
54 - (void)windowBecameKey:(NSNotification*)inNotification;
55 - (void)windowWillClose:(NSNotification*)inNotification;
61 @implementation WindowDataMap
63 + (WindowDataMap*)sharedWindowDataMap
65 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
67 static WindowDataMap* sWindowMap = nil;
69 sWindowMap = [[WindowDataMap alloc] init];
73 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
78 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
80 if ((self = [super init])) {
81 mWindowMap = [[NSMutableDictionary alloc] initWithCapacity:10];
85 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
90 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
95 NS_OBJC_END_TRY_ABORT_BLOCK;
98 - (id)dataForWindow:(NSWindow*)inWindow
100 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
102 return [mWindowMap objectForKey:[self keyForWindow:inWindow]];
104 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
107 - (void)setData:(id)inData forWindow:(NSWindow*)inWindow
109 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
111 [mWindowMap setObject:inData forKey:[self keyForWindow:inWindow]];
113 NS_OBJC_END_TRY_ABORT_BLOCK;
116 - (void)removeDataForWindow:(NSWindow*)inWindow
118 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
120 [mWindowMap removeObjectForKey:[self keyForWindow:inWindow]];
122 NS_OBJC_END_TRY_ABORT_BLOCK;
125 - (NSString*)keyForWindow:(NSWindow*)inWindow
127 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
129 return [NSString stringWithFormat:@"%p", inWindow];
131 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
137 // TopLevelWindowData
139 // This class holds data about top-level windows. We can't use a window
140 // delegate, because an embedder may already have one.
142 @implementation TopLevelWindowData
144 - (id)initWithWindow:(NSWindow*)inWindow
146 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
148 if ((self = [super init])) {
149 mShouldFocusView = nil;
150 [[NSNotificationCenter defaultCenter] addObserver:self
151 selector:@selector(windowBecameKey:)
152 name:NSWindowDidBecomeKeyNotification
155 [[NSNotificationCenter defaultCenter] addObserver:self
156 selector:@selector(windowResignedKey:)
157 name:NSWindowDidResignKeyNotification
160 [[NSNotificationCenter defaultCenter] addObserver:self
161 selector:@selector(windowBecameMain:)
162 name:NSWindowDidBecomeMainNotification
165 [[NSNotificationCenter defaultCenter] addObserver:self
166 selector:@selector(windowResignedMain:)
167 name:NSWindowDidResignMainNotification
170 [[NSNotificationCenter defaultCenter] addObserver:self
171 selector:@selector(windowWillClose:)
172 name:NSWindowWillCloseNotification
177 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
182 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
184 if (mShouldFocusView)
185 [mShouldFocusView release];
187 [[NSNotificationCenter defaultCenter] removeObserver:self];
190 NS_OBJC_END_TRY_ABORT_BLOCK;
193 // mShouldFocusView (if non-nil) is the ChildView object that *should* be
194 // focused in our NSWindow -- even if it isn't the one that's actually
195 // currently focused. (By "focused" I mean "is our NSWindow's first
196 // responder, which takes keyboard input".) We assume that [ChildView
197 // sendFocusEvent:] is sent (to a ChildView object) if and only if that
198 // ChildView object is about to be (or has just been) appropriately focused
199 // or unfocused. And mShouldFocusView keeps track of the results of calls
200 // to [ChildView sendFocusEvent:] on ChildView objects in our NSWindow.
201 - (ChildView *)getShouldFocusView
203 // A ChildView that's been detached from its window should never be made
204 // first responder (it should have been unfocused, but wasn't).
205 if (![mShouldFocusView window]) {
206 [mShouldFocusView release];
207 mShouldFocusView = nil;
209 return mShouldFocusView;
212 - (void)markShouldFocus:(ChildView *)aView
214 if (aView == mShouldFocusView)
216 if (mShouldFocusView)
217 [mShouldFocusView release];
218 mShouldFocusView = [aView retain];
221 - (void)markShouldUnfocus:(ChildView *)aView
223 if (aView == mShouldFocusView) {
224 [mShouldFocusView release];
225 mShouldFocusView = nil;
229 // As best I can tell, if the notification's object has a corresponding
230 // top-level widget (an nsCocoaWindow object), it has a delegate (set in
231 // nsCocoaWindow::StandardCreate()) of class WindowDelegate, and otherwise
232 // not (Camino doesn't use top-level widgets (nsCocoaWindow objects) --
233 // only child widgets (nsChildView objects)). (The notification is sent
234 // to windowBecameKey: or windowBecameMain: below.)
236 // If we're using top-level widgets, we need to send them both kinds of
237 // focus event (NS_GOTFOCUS and NS_ACTIVATE, which by convention are sent in
238 // that order) -- otherwise text input can (under unusual circumstances) stop
239 // working in the currently focused child widget (see bmo bug 354768).
241 // When we send focus events to a top-level widget, they get propagated
242 // (via nsWebShellWindow::HandleEvent(), indirectly) to a child widget (an
243 // nsChildView object) -- so in principle we shouldn't have to send them to
244 // child widgets here. But I've found that, unless I also send at least an
245 // NS_GOTFOCUS event directly to the currently focused child widget, it's
246 // easy to get blinking I-bar cursors in multiple text input fields
247 // (particularly if one of them is the Google search box). On other platforms
248 // (e.g. Windows and GTK2), NS_ACTIVATE events are only sent (directly) to
249 // top-level widgets -- so we do the same here. Not sending them directly
250 // to child widgets also avoids "win is null" assertions on debug builds
251 // (see bug 354768 comments 55 and 58).
253 // For use with clients that (like Firefox) do use top-level widgets (and
254 // have NSWindow delegates of class WindowDelegate).
255 + (void)activateInWindow:(NSWindow*)aWindow
257 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
259 WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
260 if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
263 if ([delegate toplevelActiveState])
265 [delegate sendToplevelActivateEvents];
267 id firstResponder = [aWindow firstResponder];
268 if ([firstResponder isKindOfClass:[ChildView class]]) {
269 BOOL isMozWindow = [aWindow respondsToSelector:@selector(setSuppressMakeKeyFront:)];
271 [aWindow setSuppressMakeKeyFront:YES];
272 [firstResponder sendFocusEvent:NS_GOTFOCUS];
274 [aWindow setSuppressMakeKeyFront:NO];
277 NS_OBJC_END_TRY_ABORT_BLOCK;
280 // See comments above activateInWindow:
282 // If we're using top-level widgets (nsCocoaWindow objects), we send them
283 // NS_DEACTIVATE events (which propagate to child widgets (nsChildView
284 // objects) via nsWebShellWindow::HandleEvent()). Sending NS_LOSTFOCUS
285 // events to top-level widgets currently has no effect (nsWebShellWindow::
286 // HandleEvent(), which processes focus events sent to top-level widgets,
287 // doesn't have a section for NS_LOSTFOCUS). But on general principles we
290 // On other platforms (e.g. Windows and GTK2), NS_DEACTIVATE events are only
291 // sent (directly) to top-level widgets. And (as noted above) these events
292 // propagate to child widgets when they're sent to top-level widgets. But if
293 // we don't send them again, blinking I-bar cursors can appear in multiple
294 // text input fields. Since we also need to send NS_LOSTFOCUS events and
295 // call nsTSMManager::CommitIME(), we just always call through to ChildView
296 // viewsWindowDidResignKey (whether or not we're using top-level widgets).
298 // For use with clients that (like Firefox) do use top-level widgets (and
299 // have NSWindow delegates of class WindowDelegate).
300 + (void)deactivateInWindow:(NSWindow*)aWindow
302 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
304 WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
305 if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
308 if (![delegate toplevelActiveState])
310 [delegate sendToplevelDeactivateEvents];
312 id firstResponder = [aWindow firstResponder];
313 if ([firstResponder isKindOfClass:[ChildView class]])
314 [firstResponder viewsWindowDidResignKey];
316 NS_OBJC_END_TRY_ABORT_BLOCK;
319 // For use with clients that (like Camino) don't use top-level widgets (and
320 // don't have NSWindow delegates of class WindowDelegate).
321 + (void)activateInWindowViews:(NSWindow*)aWindow
323 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
325 id firstResponder = [aWindow firstResponder];
326 if ([firstResponder isKindOfClass:[ChildView class]])
327 [firstResponder viewsWindowDidBecomeKey];
329 NS_OBJC_END_TRY_ABORT_BLOCK;
332 // For use with clients that (like Camino) don't use top-level widgets (and
333 // don't have NSWindow delegates of class WindowDelegate).
334 + (void)deactivateInWindowViews:(NSWindow*)aWindow
336 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
338 id firstResponder = [aWindow firstResponder];
339 if ([firstResponder isKindOfClass:[ChildView class]])
340 [firstResponder viewsWindowDidResignKey];
342 NS_OBJC_END_TRY_ABORT_BLOCK;
345 // We make certain exceptions for top-level windows in non-embedders (see
346 // comment above windowBecameMain below). And we need (elsewhere) to guard
347 // against sending duplicate events. But in general NS_ACTIVATE and
348 // NS_GOTFOCUS events should be sent when a native window becomes key, and
349 // NS_LOSTFOCUS and NS_DEACTIVATE events should be sent when it resignes key.
350 - (void)windowBecameKey:(NSNotification*)inNotification
352 NSWindow* window = (NSWindow*)[inNotification object];
354 id delegate = [window delegate];
355 if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
356 [TopLevelWindowData activateInWindowViews:window];
357 } else if ([window isSheet]) {
358 [TopLevelWindowData activateInWindow:window];
361 [[window contentView] setNeedsDisplay:YES];
364 - (void)windowResignedKey:(NSNotification*)inNotification
366 NSWindow* window = (NSWindow*)[inNotification object];
368 id delegate = [window delegate];
369 if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
370 [TopLevelWindowData deactivateInWindowViews:window];
371 } else if ([window isSheet]) {
372 [TopLevelWindowData deactivateInWindow:window];
375 [[window contentView] setNeedsDisplay:YES];
378 // The appearance of a top-level window depends on its main state (not its key
379 // state). So (for non-embedders) we need to ensure that a top-level window
380 // is main when an NS_ACTIVATE event is sent to Gecko for it.
381 - (void)windowBecameMain:(NSNotification*)inNotification
383 NSWindow* window = (NSWindow*)[inNotification object];
385 id delegate = [window delegate];
386 // Don't send events to a top-level window that has a sheet open above it --
387 // as far as Gecko is concerned, it's inactive, and stays so until the sheet
389 if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
390 [TopLevelWindowData activateInWindow:window];
393 - (void)windowResignedMain:(NSNotification*)inNotification
395 NSWindow* window = (NSWindow*)[inNotification object];
397 id delegate = [window delegate];
398 if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
399 [TopLevelWindowData deactivateInWindow:window];
402 - (void)windowWillClose:(NSNotification*)inNotification
404 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
406 // postpone our destruction
407 [[self retain] autorelease];
409 // remove ourselves from the window map (which owns us)
410 [[WindowDataMap sharedWindowDataMap] removeDataForWindow:[inNotification object]];
412 NS_OBJC_END_TRY_ABORT_BLOCK;