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