1/** <title>NSWindowController</title> 2 3 Copyright (C) 2000 Free Software Foundation, Inc. 4 5 Author: Carl Lindberg <Carl.Lindberg@hbo.com> 6 Date: 1999 7 Author: Fred Kiefer <FredKiefer@gmx.de> 8 Date: Aug 2003 9 10 This file is part of the GNUstep GUI Library. 11 12 This library is free software; you can redistribute it and/or 13 modify it under the terms of the GNU Lesser General Public 14 License as published by the Free Software Foundation; either 15 version 2 of the License, or (at your option) any later version. 16 17 This library is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public 23 License along with this library; see the file COPYING.LIB. 24 If not, see <http://www.gnu.org/licenses/> or write to the 25 Free Software Foundation, 51 Franklin Street, Fifth Floor, 26 Boston, MA 02110-1301, USA. 27*/ 28 29#import <Foundation/NSArray.h> 30#import <Foundation/NSBundle.h> 31#import <Foundation/NSDictionary.h> 32#import <Foundation/NSEnumerator.h> 33#import <Foundation/NSException.h> 34#import <Foundation/NSNotification.h> 35#import <Foundation/NSString.h> 36 37#import "AppKit/NSNib.h" 38#import "AppKit/NSNibLoading.h" 39#import "AppKit/NSPanel.h" 40#import "AppKit/NSWindowController.h" 41#import "NSDocumentFrameworkPrivate.h" 42 43@implementation NSWindowController 44 45+ (void) initialize 46{ 47 if (self == [NSWindowController class]) 48 { 49 [self setVersion: 1]; 50 } 51} 52 53- (id) initWithWindowNibName: (NSString *)windowNibName 54{ 55 return [self initWithWindowNibName: windowNibName owner: self]; 56} 57 58- (id) initWithWindowNibName: (NSString *)windowNibName owner: (id)owner 59{ 60 if (windowNibName == nil) 61 { 62 [NSException raise: NSInvalidArgumentException 63 format: @"attempt to init NSWindowController with nil windowNibName"]; 64 } 65 66 if (owner == nil) 67 { 68 [NSException raise: NSInvalidArgumentException 69 format: @"attempt to init NSWindowController with nil owner"]; 70 } 71 72 self = [self initWithWindow: nil]; 73 if (!self) 74 return nil; 75 76 ASSIGN(_window_nib_name, windowNibName); 77 _owner = owner; 78 return self; 79} 80 81- (id) initWithWindowNibPath: (NSString *)windowNibPath 82 owner: (id)owner 83{ 84 if (windowNibPath == nil) 85 { 86 [NSException raise: NSInvalidArgumentException 87 format: @"attempt to init NSWindowController with nil windowNibPath"]; 88 } 89 90 if (owner == nil) 91 { 92 [NSException raise: NSInvalidArgumentException 93 format: @"attempt to init NSWindowController with nil owner"]; 94 } 95 96 self = [self initWithWindow: nil]; 97 if (!self) 98 return nil; 99 100 ASSIGN(_window_nib_path, windowNibPath); 101 _owner = owner; 102 return self; 103} 104 105- (id) initWithWindow: (NSWindow *)window 106{ 107 self = [super init]; 108 if (!self) 109 return nil; 110 111 ASSIGN(_window_frame_autosave_name, @""); 112 _wcFlags.should_cascade = YES; 113 //_wcFlags.should_close_document = NO; 114 _owner = self; 115 116 [self setWindow: window]; 117 if (_window != nil) 118 { 119 [self _windowDidLoad]; 120 [self setDocument: nil]; 121 } 122 123 return self; 124} 125 126- (id) init 127{ 128 return [self initWithWindow: nil]; 129} 130 131- (void) dealloc 132{ 133 // Window Controllers are expect to release their own top-level objects 134 [_top_level_objects makeObjectsPerformSelector: @selector(release)]; 135 [self setWindow: nil]; 136 RELEASE(_window_nib_name); 137 RELEASE(_window_nib_path); 138 RELEASE(_window_frame_autosave_name); 139 RELEASE(_top_level_objects); 140 [super dealloc]; 141} 142 143- (NSString *) windowNibName 144{ 145 if ((_window_nib_name == nil) && (_window_nib_path != nil)) 146 { 147 return [[_window_nib_path lastPathComponent] 148 stringByDeletingPathExtension]; 149 } 150 151 return _window_nib_name; 152} 153 154- (NSString *)windowNibPath 155{ 156 if ((_window_nib_name != nil) && (_window_nib_path == nil)) 157 { 158 NSString *path; 159 160 path = [[NSBundle bundleForClass: [_owner class]] 161 pathForNibResource: _window_nib_name]; 162 if (path == nil) 163 path = [[NSBundle mainBundle] 164 pathForNibResource: _window_nib_name]; 165 166 return path; 167 } 168 169 return _window_nib_path; 170} 171 172- (id) owner 173{ 174 return _owner; 175} 176 177/** Sets the document associated with this controller. A document 178 automatically calls this method when adding a window controller to 179 its list of window controllers. You should not call this method 180 directly when using NSWindowController with an NSDocument 181 or subclass. */ 182- (void) setDocument: (NSDocument *)document 183{ 184 // As the document retains us, we only keep a week reference. 185 _document = document; 186 [self synchronizeWindowTitleWithDocumentName]; 187 188 if (_document == nil) 189 { 190 /* If you want the window to be deallocated when closed, you 191 need to observe the NSWindowWillCloseNotification (or 192 implement the window's delegate windowWillClose: method) and 193 autorelease the window controller in that method. That will 194 then release the window when the window controller is 195 released. */ 196 [_window setReleasedWhenClosed: NO]; 197 } 198 else 199 { 200 /* When a window owned by a document is closed, it is released 201 and the window controller is removed from the documents 202 list of controllers. 203 */ 204 [_window setReleasedWhenClosed: YES]; 205 } 206} 207 208- (id) document 209{ 210 return _document; 211} 212 213- (void) setDocumentEdited: (BOOL)flag 214{ 215 if ([self isWindowLoaded]) 216 [[self window] setDocumentEdited: flag]; 217} 218 219- (void) setWindowFrameAutosaveName:(NSString *)name 220{ 221 ASSIGN(_window_frame_autosave_name, name); 222 223 if ([self isWindowLoaded]) 224 { 225 [[self window] setFrameAutosaveName: name ? (id)name : (id)@""]; 226 } 227} 228 229- (NSString *) windowFrameAutosaveName 230{ 231 return _window_frame_autosave_name; 232} 233 234- (void) setShouldCloseDocument: (BOOL)flag 235{ 236 _wcFlags.should_close_document = flag; 237} 238 239- (BOOL) shouldCloseDocument 240{ 241 return _wcFlags.should_close_document; 242} 243 244- (void) setShouldCascadeWindows: (BOOL)flag 245{ 246 _wcFlags.should_cascade = flag; 247} 248 249- (BOOL) shouldCascadeWindows 250{ 251 return _wcFlags.should_cascade; 252} 253 254- (void) close 255{ 256 [_window close]; 257} 258 259- (void) _windowWillClose: (NSNotification *)notification 260{ 261 if ([notification object] == _window) 262 { 263 /* We only need to do something if the window is set to be 264 released when closed (which should only happen if _document 265 != nil). In this case, we release everything; otherwise, 266 well the window is closed but nothing is released so there's 267 nothing to do here. */ 268 if ([_window isReleasedWhenClosed]) 269 { 270 RETAIN(self); 271 272 /* 273 * If the window is set to isReleasedWhenClosed, it will release 274 * itself, so we have to retain it once more. 275 * 276 * Apple's implementation doesn't seem to deal with this case, and 277 * crashes if isReleaseWhenClosed is set. 278 */ 279 RETAIN(_window); 280 [self setWindow: nil]; 281 282 [_document _removeWindowController: self]; 283 AUTORELEASE(self); 284 } 285 } 286} 287 288- (NSWindow *) window 289{ 290 if (_window == nil && ![self isWindowLoaded]) 291 { 292 // Do all the notifications. Yes, the docs say this should 293 // be implemented here instead of in -loadWindow itself. 294 295 // Note: The docs say that windowController{Will,Did}LoadNib: are sent 296 // to the window controller's document, but Apple's implementation 297 // really sends them to the owner of the nib. Since this behavior is 298 // more useful, in particular when non-document classes use a window 299 // controller, we implement it here too. 300 [self windowWillLoad]; 301 if (_owner != self && 302 [_owner respondsToSelector: @selector(windowControllerWillLoadNib:)]) 303 { 304 [_owner windowControllerWillLoadNib: self]; 305 } 306 307 [self loadWindow]; 308 if ([self isWindowLoaded]) 309 { 310 [self _windowDidLoad]; 311 if (_owner != self && 312 [_owner respondsToSelector: @selector(windowControllerDidLoadNib:)]) 313 { 314 [_owner windowControllerDidLoadNib: self]; 315 } 316 } 317 } 318 319 return _window; 320} 321 322/** Sets the window that this controller managers to aWindow. The old 323 window is released. */ 324- (void) setWindow: (NSWindow *)aWindow 325{ 326 NSNotificationCenter *nc; 327 328 if (_window == aWindow) 329 { 330 return; 331 } 332 333 nc = [NSNotificationCenter defaultCenter]; 334 335 if (_window != nil) 336 { 337 NSResponder *responder; 338 339 [nc removeObserver: self 340 name: NSWindowWillCloseNotification 341 object: _window]; 342 // Remove self from the responder chain 343 responder = _window; 344 while (responder && [responder nextResponder] != self) 345 { 346 responder = [responder nextResponder]; 347 } 348 [responder setNextResponder: [self nextResponder]]; 349 [_window setWindowController: nil]; 350 351 // Remove the delegate as well if set to the owner in the NIB file 352 if ([_window delegate] == _owner) 353 { 354 [_window setDelegate: nil]; 355 } 356 } 357 358 ASSIGN(_window, aWindow); 359 360 if (_window != nil) 361 { 362 [_window setWindowController: self]; 363 // Put self into the responder chain 364 [self setNextResponder: [_window nextResponder]]; 365 [_window setNextResponder: self]; 366 [nc addObserver: self 367 selector: @selector(_windowWillClose:) 368 name: NSWindowWillCloseNotification 369 object: _window]; 370 371 /* For information on the following, see the description in 372 -setDocument: */ 373 if (_document == nil) 374 { 375 [_window setReleasedWhenClosed: NO]; 376 } 377 else 378 { 379 [_window setReleasedWhenClosed: YES]; 380 [_window setDocumentEdited: [_document isDocumentEdited]]; 381 } 382 383 /* Make sure window sizes itself right */ 384 if ([_window_frame_autosave_name length] > 0) 385 { 386 [_window setFrameAutosaveName: _window_frame_autosave_name]; 387 } 388 } 389} 390 391/** Orders the receiver's window front, also making it the key window 392 if appropriate. */ 393- (IBAction) showWindow: (id)sender 394{ 395 NSWindow *window = [self window]; 396 397 if ([window isKindOfClass: [NSPanel class]] 398 && [(NSPanel*)window becomesKeyOnlyIfNeeded]) 399 { 400 [window orderFront: sender]; 401 } 402 else 403 { 404 [window makeKeyAndOrderFront: sender]; 405 } 406} 407 408- (NSString *) windowTitleForDocumentDisplayName: (NSString *)displayName 409{ 410 return displayName; 411} 412 413- (void) synchronizeWindowTitleWithDocumentName 414{ 415 if ((_document != nil) && [self isWindowLoaded]) 416 { 417 NSString *filename = [_document fileName]; 418 NSString *displayName = [_document displayName]; 419 NSString *title = [self windowTitleForDocumentDisplayName: displayName]; 420 421 /* If they just want to display the filename, use the fancy method */ 422 /* NB For compatibility with Mac OS X, a document's display name is equal 423 to its last path component, so we check for that here too */ 424 if (filename != nil && 425 ([title isEqualToString: filename] || 426 [title isEqualToString: [filename lastPathComponent]])) 427 { 428 [_window setTitleWithRepresentedFilename: filename]; 429 } 430 else 431 { 432 if (filename) 433 [_window setRepresentedFilename: filename]; 434 [_window setTitle: title]; 435 } 436 } 437} 438 439/** Returns YES if the receiver's window has loaded. */ 440- (BOOL) isWindowLoaded 441{ 442 return _wcFlags.nib_is_loaded; 443} 444 445/** Subclasses can override this method to perform any customisation 446 needed after the receiver has loaded its window. */ 447- (void) windowDidLoad 448{ 449} 450 451/** Subclasses can override this method to perform any customisation 452 needed before the receiver loads its window. */ 453- (void) windowWillLoad 454{ 455} 456 457- (void) _windowDidLoad 458{ 459 _wcFlags.nib_is_loaded = YES; 460 461 [self synchronizeWindowTitleWithDocumentName]; 462 463 if ([self shouldCascadeWindows]) 464 { 465 static NSPoint nextWindowLocation = { 0.0, 0.0 }; 466 /* 467 * cascadeTopLeftFromPoint will "wrap" the point back to the 468 * top left if the normal cascading will cause the window to go 469 * off the screen. In Apple's implementation, this wraps to the 470 * extreme top of the screen, and offset only a small amount 471 * from the left. 472 */ 473 nextWindowLocation 474 = [_window cascadeTopLeftFromPoint: nextWindowLocation]; 475 } 476 477 [self windowDidLoad]; 478} 479 480/** Loads the receiver's window. You can override this method if the 481 way that the window is loaded is not appropriate. You should not 482 normally need to call this method directly; it will be called when 483 the window controller needs to access the window. 484 */ 485- (void) loadWindow 486{ 487 NSDictionary *table; 488 489 if ([self isWindowLoaded]) 490 { 491 return; 492 } 493 494 table = [NSDictionary dictionaryWithObject: _owner forKey: NSNibOwner]; 495 if ([NSBundle loadNibFile: [self windowNibPath] 496 externalNameTable: table 497 withZone: [_owner zone]]) 498 { 499 _wcFlags.nib_is_loaded = YES; 500 501 if (_window == nil && _document != nil && _owner == _document) 502 { 503 [self setWindow: [_document _transferWindowOwnership]]; 504 } 505 else 506 { 507 // The window was already retained by the NIB loading. 508 RELEASE(_window); 509 } 510 } 511 else 512 { 513 if (_window_nib_name != nil) 514 { 515 NSLog (@"%@: could not load nib named %@.nib", 516 [self class], _window_nib_name); 517 } 518 } 519} 520 521- (id) initWithCoder: (NSCoder *)coder 522{ 523 if ([coder allowsKeyedCoding] 524 || [coder versionForClassName: @"NSWindowController"] >= 1) 525 { 526 self = [super initWithCoder: coder]; 527 if (!self) 528 return nil; 529 530 ASSIGN(_window_frame_autosave_name, @""); 531 _wcFlags.should_cascade = YES; 532 //_wcFlags.should_close_document = NO; 533 534 return self; 535 } 536 else 537 { 538 /* backward compatibility: old NSWindowController instances are not 539 subclasses of NSResponder, but of NSObject */ 540 return [self init]; 541 } 542} 543 544- (void) encodeWithCoder: (NSCoder *)coder 545{ 546 // What are we supposed to encode? Window nib name? Or should these 547 // be empty, just to conform to NSCoding, so we do an -init on 548 // unarchival. ? 549 550 [super encodeWithCoder: coder]; 551} 552 553@end 554