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