1/** <title>GSGormLoading</title>
2
3   <abstract>Contains all of the private classes used in .gorm files.</abstract>
4
5   Copyright (C) 2003 Free Software Foundation, Inc.
6
7   Author: Gregory John Casamento
8   Date: July 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/NSCoder.h>
30#import <Foundation/NSDictionary.h>
31#import <Foundation/NSDebug.h>
32#import <Foundation/NSException.h>
33#import <Foundation/NSString.h>
34#import <Foundation/NSKeyValueCoding.h>
35#import <Foundation/NSNotification.h>
36#import <Foundation/NSArchiver.h>
37#import <Foundation/NSSet.h>
38#import "AppKit/NSApplication.h"
39#import "AppKit/NSControl.h"
40#import "AppKit/NSMenu.h"
41#import "AppKit/NSNib.h"
42#import "AppKit/NSNibLoading.h"
43#import "AppKit/NSNibConnector.h"
44#import "AppKit/NSScreen.h"
45#import "AppKit/NSTextView.h"
46#import "AppKit/NSView.h"
47#import "AppKit/NSWindow.h"
48#import "GNUstepGUI/GSGormLoading.h"
49#import "NSDocumentFrameworkPrivate.h"
50
51static const int currentVersion = 1; // GSNibItem version number...
52
53@interface NSObject (GSNibPrivateMethods)
54- (BOOL) isInInterfaceBuilder;
55@end
56
57/*
58 * This private class is used to collect the nib items while the
59 * .gorm file is being unarchived.  This is done to allow only
60 * the top level items to be retained in a clean way.  The reason it's
61 * being done this way is because old .gorm files don't have any
62 * array within the nameTable which indicates the objects which are
63 * considered top level, so there is no clean and generic way to determine
64 * this.   Basically the top level items are any instances of or instances
65 * of subclasses of NSMenu, NSWindow, or any controller class.
66 * It's the last one that's hairy.  Controller classes are
67 * represented in .gorm files by the GSNibItem class, but once they transform
68 * into the actual class instance it's not easy to tell if it should be
69 * retained or not since there are a lot of other things stored in the nameTable
70 * as well.  GJC
71 */
72
73static NSString *GSInternalNibItemAddedNotification = @"_GSInternalNibItemAddedNotification";
74
75@interface GSNibItemCollector : NSObject
76{
77  NSMutableArray *items;
78}
79- (void) handleNotification: (NSNotification *)notification;
80- (NSMutableArray *)items;
81@end
82
83@implementation GSNibItemCollector
84- (void) handleNotification: (NSNotification *)notification;
85{
86  id obj = [notification object];
87  [items addObject: obj];
88}
89
90- init
91{
92  if ((self = [super init]) != nil)
93    {
94      NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
95
96      // add myself as an observer and initialize the items array.
97      [nc addObserver: self
98	  selector: @selector(handleNotification:)
99	  name: GSInternalNibItemAddedNotification
100	  object: nil];
101      items = [[NSMutableArray alloc] init];
102    }
103  return self;
104}
105
106- (void) dealloc
107{
108  [[NSNotificationCenter defaultCenter] removeObserver: self];
109  RELEASE(items);
110  [super dealloc];
111}
112
113- (NSMutableArray *)items
114{
115  return items;
116}
117@end
118
119/*
120 *	The GSNibContainer class manages the internals of a nib file.
121 */
122@implementation GSNibContainer
123
124+ (void) initialize
125{
126  if (self == [GSNibContainer class])
127    {
128      [self setVersion: GNUSTEP_NIB_VERSION];
129    }
130}
131
132- (void) awakeWithContext: (NSDictionary *)context
133{
134  if (isAwake == NO)
135    {
136      NSEnumerator	*enumerator;
137      NSNibConnector	*connection;
138      NSString		*key;
139      NSMenu		*menu;
140      NSMutableArray    *topObjects;
141      id                 obj;
142
143      // Add these objects with there old names as the code expects them
144      context = AUTORELEASE([context mutableCopyWithZone: [context zone]]);
145
146      isAwake = YES;
147      /*
148       *	Add local entries into name table.
149       */
150      if ([context count] > 0)
151	{
152	  [nameTable addEntriesFromDictionary: context];
153	}
154
155      /*
156       *	Now establish all connections by taking the names
157       *	stored in the connection objects, and replaciong them
158       *	with the corresponding values from the name table
159       *	before telling the connections to establish themselves.
160       */
161      enumerator = [connections objectEnumerator];
162      while ((connection = [enumerator nextObject]) != nil)
163	{
164	  id	val;
165
166	  val = [nameTable objectForKey: [connection source]];
167	  [connection setSource: val];
168	  val = [nameTable objectForKey: [connection destination]];
169	  [connection setDestination: val];
170	  [connection establishConnection];
171	}
172
173      /*
174       * See if there is a main menu to be set.  Report #4815, mainMenu
175       * should be initialized before awakeFromNib is called.
176       */
177      menu = [nameTable objectForKey: @"NSMenu"];
178      if (menu != nil && [menu isKindOfClass: [NSMenu class]] == YES)
179	{
180	  [NSApp setMainMenu: menu];
181	}
182
183      /*
184       * Set the Services menu.
185       * Report #5205, Services/Window menu does not behave correctly.
186       */
187      menu = [nameTable objectForKey: @"NSServicesMenu"];
188      if (menu != nil && [menu isKindOfClass: [NSMenu class]] == YES)
189	{
190	  [NSApp setServicesMenu: menu];
191	}
192
193      /*
194       * Set the Services menu.
195       * Report #5205, Services/Window menu does not behave correctly.
196       */
197      menu = [nameTable objectForKey: @"NSWindowsMenu"];
198      if (menu != nil && [menu isKindOfClass: [NSMenu class]] == YES)
199	{
200	  [NSApp setWindowsMenu: menu];
201	}
202
203      /*
204       * Set the Recent Documents menu.
205       */
206      menu = [nameTable objectForKey: @"NSRecentDocumentsMenu"];
207      if (menu != nil && [menu isKindOfClass: [NSMenu class]] == YES)
208	{
209	  [[NSDocumentController sharedDocumentController]
210	      _setRecentDocumentsMenu: menu];
211	}
212
213
214      /*
215       * See if the user has passed in the NSNibTopLevelObjects key.
216       * This is an implementation of a commonly used feature to give access to
217       * all top level objects of a nib file.
218       */
219      obj = [context objectForKey: NSNibTopLevelObjects];
220      if ([obj isKindOfClass: [NSMutableArray class]])
221	{
222	  topObjects = obj;
223	}
224      else
225	{
226	  topObjects = nil;
227	}
228
229
230      /*
231       * Now tell all the objects that they have been loaded from
232       * a nib.
233       */
234      enumerator = [nameTable keyEnumerator];
235      while ((key = [enumerator nextObject]) != nil)
236	{
237	  if ([context objectForKey: key] == nil ||
238	      [key isEqualToString: NSNibOwner]) // we want to send the message to the owner
239	    {
240	      // we don't want to send a message to these menus twice, if they're custom classes.
241	      if ([key isEqualToString: @"NSWindowsMenu"] == NO &&
242		  [key isEqualToString: @"NSServicesMenu"] == NO &&
243		  [key isEqualToString: NSNibTopLevelObjects] == NO)
244		{
245		  id o = [nameTable objectForKey: key];
246
247		  // send the awake message, if it responds...
248		  if ([o respondsToSelector: @selector(awakeFromNib)])
249		    {
250		      [o awakeFromNib];
251		    }
252
253		  /*
254		   * Retain all "top level" items so that, when the container
255		   * is released, they will remain. The GSNibItems instantiated in the gorm need
256		   * to be retained, since we are deallocating the container.
257		   * We don't want to retain the owner.
258		   *
259		   * Please note: It is encumbent upon the developer of an application to
260		   * release these objects. Instantiating a window manually or loading in a .gorm
261		   * file are equivalent processes. These objects need to be released in their
262		   * respective controllers. If the developer has used the NSNibTopLevelObjects feature,
263		   * then she will get the objects back in an array. She will will have to first release
264                   * all the objects in the array and then the array itself in order to release the
265                   * objects held within.
266		   */
267		  if ([key isEqualToString: NSNibOwner] == NO)
268		    {
269		      if ([topLevelObjects containsObject: o]) // anything already designated a top level item..
270			{
271			  [topObjects addObject: o];
272			  // All top level objects must be released by the
273			  // caller to avoid leaking, unless they are going
274			  // to be released by other nib objects on behalf
275			  // of the owner.
276			  RETAIN(o);
277			}
278		    }
279		}
280	    }
281	}
282
283      /*
284       * See if there are objects that should be made visible.
285       * This is the last thing we should do since changes might be made
286       * in the awakeFromNib methods which are called on all of the objects.
287       */
288      if (visibleWindows != nil)
289	{
290	  unsigned	pos = [visibleWindows count];
291	  while (pos-- > 0)
292	    {
293	      NSWindow *win = [visibleWindows objectAtIndex: pos];
294	      [win orderFront: self];
295	    }
296	}
297
298      /*
299       * Now remove any objects added from the context dictionary.
300       */
301      if ([context count] > 0)
302	{
303	  [nameTable removeObjectsForKeys: [context allKeys]];
304	}
305    }
306}
307
308- (void) dealloc
309{
310  RELEASE(nameTable);
311  RELEASE(connections);
312  RELEASE(topLevelObjects);
313  RELEASE(visibleWindows);
314  RELEASE(deferredWindows);
315  RELEASE(customClasses);
316  [super dealloc];
317}
318
319- (id) init
320{
321  if ((self = [super init]) != nil)
322    {
323      nameTable = [[NSMutableDictionary alloc] initWithCapacity: 8];
324      connections = [[NSMutableArray alloc] initWithCapacity: 8];
325      topLevelObjects = [[NSMutableSet alloc] initWithCapacity: 8];
326      customClasses = [[NSMutableDictionary alloc] initWithCapacity: 8];
327      deferredWindows = [[NSMutableArray alloc] initWithCapacity: 8];
328      visibleWindows = [[NSMutableArray alloc] initWithCapacity: 8];
329    }
330  return self;
331}
332
333- (void) encodeWithCoder: (NSCoder*)aCoder
334{
335  int version = [GSNibContainer version];
336  if (version == GNUSTEP_NIB_VERSION)
337    {
338      [aCoder encodeObject: topLevelObjects];
339      [aCoder encodeObject: visibleWindows];
340      [aCoder encodeObject: deferredWindows];
341      [aCoder encodeObject: nameTable];
342      [aCoder encodeObject: connections];
343      [aCoder encodeObject: customClasses];
344    }
345  else if (version == 1)
346    {
347      NSMutableDictionary *nt = [NSMutableDictionary dictionaryWithDictionary: nameTable];
348      [nt setObject: [NSMutableArray arrayWithArray: visibleWindows]
349	  forKey: @"NSVisible"];
350      [nt setObject: [NSMutableArray arrayWithArray: deferredWindows]
351	  forKey: @"NSDeferred"];
352      [nt setObject: [NSMutableDictionary dictionaryWithDictionary: customClasses]
353	  forKey: @"GSCustomClassMap"];
354      [aCoder encodeObject: nt];
355      [aCoder encodeObject: connections];
356      [aCoder encodeObject: topLevelObjects];
357    }
358  else if (version == 0)
359    {
360      NSMutableDictionary *nt = [NSMutableDictionary dictionaryWithDictionary: nameTable];
361      [nt setObject: [NSMutableArray arrayWithArray: visibleWindows]
362	  forKey: @"NSVisible"];
363      [nt setObject: [NSMutableArray arrayWithArray: deferredWindows]
364	  forKey: @"NSDeferred"];
365      [nt setObject: [NSMutableDictionary dictionaryWithDictionary: customClasses]
366	  forKey: @"GSCustomClassMap"];
367      [aCoder encodeObject: nt];
368      [aCoder encodeObject: connections];
369    }
370  else
371    {
372      [NSException raise: NSInternalInconsistencyException
373		   format: @"Unable to write GSNibContainer version #%d.  GSNibContainer version for the installed gui lib is %d.", version, GNUSTEP_NIB_VERSION];
374    }
375}
376
377- (id) initWithCoder: (NSCoder*)aCoder
378{
379  int version = [aCoder versionForClassName: @"GSNibContainer"];
380
381  // save the version to the ivar, we need it later.
382  if (version == GNUSTEP_NIB_VERSION)
383    {
384      [aCoder decodeValueOfObjCType: @encode(id) at: &topLevelObjects];
385      [aCoder decodeValueOfObjCType: @encode(id) at: &visibleWindows];
386      [aCoder decodeValueOfObjCType: @encode(id) at: &deferredWindows];
387      [aCoder decodeValueOfObjCType: @encode(id) at: &nameTable];
388      [aCoder decodeValueOfObjCType: @encode(id) at: &connections];
389      [aCoder decodeValueOfObjCType: @encode(id) at: &customClasses];
390    }
391  else if (version == 1)
392    {
393      [aCoder decodeValueOfObjCType: @encode(id) at: &nameTable];
394      [aCoder decodeValueOfObjCType: @encode(id) at: &connections];
395      [aCoder decodeValueOfObjCType: @encode(id) at: &topLevelObjects];
396
397      // initialize with special entries...
398      ASSIGN(visibleWindows, [NSMutableArray arrayWithArray:
399					       [nameTable objectForKey: @"NSVisible"]]);
400      ASSIGN(deferredWindows, [NSMutableArray arrayWithArray:
401						[nameTable objectForKey: @"NSDeferred"]]);
402      ASSIGN(customClasses, [NSMutableDictionary dictionaryWithDictionary:
403						   [nameTable objectForKey: @"GSCustomClassMap"]]);
404
405      // then remove them from the name table.
406      [nameTable removeObjectForKey: @"NSVisible"];
407      [nameTable removeObjectForKey: @"NSDeferred"];
408      [nameTable removeObjectForKey: @"GSCustomClassMap"];
409    }
410  else if (version == 0)
411    {
412      GSNibItemCollector *nibitems = [[GSNibItemCollector alloc] init];
413      NSEnumerator *en;
414      NSString *key;
415
416      // initialize the set of top level objects...
417      topLevelObjects = [[NSMutableSet alloc] initWithCapacity: 8];
418
419      // unarchive...
420      [aCoder decodeValueOfObjCType: @encode(id) at: &nameTable];
421      [aCoder decodeValueOfObjCType: @encode(id) at: &connections];
422      [topLevelObjects addObjectsFromArray: [nibitems items]]; // get the top level items here...
423      RELEASE(nibitems);
424
425      // iterate through the objects returned
426      en = [nameTable keyEnumerator];
427      while ((key = [en nextObject]) != nil)
428	{
429	  id o = [nameTable objectForKey: key];
430	  if (([o isKindOfClass: [NSMenu class]] && [key isEqual: @"NSMenu"]) ||
431	     [o isKindOfClass: [NSWindow class]])
432	    {
433	      [topLevelObjects addObject: o]; // if it's a top level object, add it.
434	    }
435	}
436
437      // initialize with special entries...
438      ASSIGN(visibleWindows, [NSMutableArray arrayWithArray:
439					       [nameTable objectForKey: @"NSVisible"]]);
440      ASSIGN(deferredWindows, [NSMutableArray arrayWithArray:
441						[nameTable objectForKey: @"NSDeferred"]]);
442      ASSIGN(customClasses, [NSMutableDictionary dictionaryWithDictionary:
443						   [nameTable objectForKey: @"GSCustomClassMap"]]);
444
445
446      // then remove them from the name table.
447      [nameTable removeObjectForKey: @"NSVisible"];
448      [nameTable removeObjectForKey: @"NSDeferred"];
449      [nameTable removeObjectForKey: @"GSCustomClassMap"];
450    }
451  else
452    {
453      [NSException raise: NSInternalInconsistencyException
454		   format: @"Unable to read GSNibContainer version #%d.  GSNibContainer version for the installed gui lib is %d.  Please upgrade to a more recent version of the gui library.", version, GNUSTEP_NIB_VERSION];
455    }
456
457  return self;
458}
459
460- (NSMutableDictionary*) nameTable
461{
462  return nameTable;
463}
464
465- (NSMutableSet*) topLevelObjects
466{
467  return topLevelObjects;
468}
469
470- (NSMutableArray*) connections
471{
472  return connections;
473}
474
475- (NSMutableArray*) visibleWindows
476{
477  return visibleWindows;
478}
479
480- (NSMutableArray*) deferredWindows
481{
482  return deferredWindows;
483}
484
485- (NSMutableDictionary *) customClasses
486{
487  return customClasses;
488}
489@end
490
491// The first standin objects here are for views and normal objects like controllers
492// or data sources.
493@implementation	GSNibItem
494+ (void) initialize
495{
496  if (self == [GSNibItem class])
497    {
498      [self setVersion: currentVersion];
499    }
500}
501
502- (void) dealloc
503{
504  RELEASE(theClass);
505  [super dealloc];
506}
507
508- (void) encodeWithCoder: (NSCoder*)aCoder
509{
510  [aCoder encodeObject: theClass];
511  [aCoder encodeRect: theFrame];
512  [aCoder encodeValueOfObjCType: @encode(unsigned int)
513	  at: &autoresizingMask];
514}
515
516- (id) initWithCoder: (NSCoder*)aCoder
517{
518  int version = [aCoder versionForClassName:
519			  NSStringFromClass([self class])];
520  id obj = nil;
521
522  if (version == 1)
523    {
524      Class		cls;
525      unsigned int      mask;
526
527      [aCoder decodeValueOfObjCType: @encode(id) at: &theClass];
528      theFrame = [aCoder decodeRect];
529      [aCoder decodeValueOfObjCType: @encode(unsigned int)
530	      at: &mask];
531
532      cls = NSClassFromString(theClass);
533      if (cls == nil)
534	{
535	  [NSException raise: NSInternalInconsistencyException
536		       format: @"Unable to find class '%@', it is not linked into the application.", theClass];
537	}
538
539      if (theFrame.size.height > 0 && theFrame.size.width > 0)
540	{
541	  obj = [[cls allocWithZone: [self zone]] initWithFrame: theFrame];
542	}
543      else
544	{
545	  if(GSObjCIsKindOf(cls, [NSApplication class]))
546	    {
547	      obj = RETAIN([cls sharedApplication]);
548	    }
549	  else
550	    {
551	      obj = [[cls allocWithZone: [self zone]] init];
552	    }
553	}
554
555      if ([obj respondsToSelector: @selector(setAutoresizingMask:)])
556	{
557	  [obj setAutoresizingMask: mask];
558	}
559    }
560  else if (version == 0)
561    {
562      Class		cls;
563
564      [aCoder decodeValueOfObjCType: @encode(id) at: &theClass];
565      theFrame = [aCoder decodeRect];
566
567      cls = NSClassFromString(theClass);
568      if (cls == nil)
569	{
570	  [NSException raise: NSInternalInconsistencyException
571		       format: @"Unable to find class '%@', it is not linked into the application.", theClass];
572	}
573
574      obj = [cls allocWithZone: [self zone]];
575      if (theFrame.size.height > 0 && theFrame.size.width > 0)
576	{
577	  obj = [obj initWithFrame: theFrame];
578	}
579      else
580	{
581	  obj = [obj init];
582	}
583    }
584  else
585    {
586      NSLog(@"no initWithCoder for this version");
587    }
588
589  // If this is a nib item and not a custom view, then we need to add it to
590  // the set of things to be retained.  Also, the initial version of the nib container
591  // needed this code, but subsequent versions don't, so don't send the notification,
592  // if the version isn't zero.
593  if (obj != nil && [aCoder versionForClassName: NSStringFromClass([GSNibContainer class])] == 0)
594    {
595      if ([self isKindOfClass: [GSNibItem class]] == YES &&
596	 [self isKindOfClass: [GSCustomView class]] == NO)
597	{
598	  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
599	  [nc postNotificationName: GSInternalNibItemAddedNotification
600	      object: obj];
601	}
602    }
603
604  // release self and return the object this represents...
605  RELEASE(self);
606  return obj;
607}
608
609@end
610
611@implementation	GSCustomView
612+ (void) initialize
613{
614  if (self == [GSCustomView class])
615    {
616      [self setVersion: currentVersion];
617    }
618}
619
620- (void) encodeWithCoder: (NSCoder*)aCoder
621{
622  [super encodeWithCoder: aCoder];
623}
624
625- (id) initWithCoder: (NSCoder*)aCoder
626{
627  return [super initWithCoder: aCoder];
628}
629@end
630
631/*
632  These stand-ins are here for use by GUI elements within Gorm.   Since each gui element
633  has it's own "designated initializer" it's important to provide a division between these
634  so that when they are loaded, the application will call the correct initializer.
635
636  Some "tricks" are employed in this code.   For instance the use of initWithCoder and
637  encodeWithCoder directly as opposed to using the encodeObjC..  methods is the obvious
638  standout.  To understand this it's necessary to explain a little about how encoding itself
639  works.
640
641  When the model is saved by the Interface Builder (whether Gorm or another
642  IB equivalent) these classes should be used to substitute for the actual classes.  The actual
643  classes are encoded as part of it, but since they are being replaced we can't use the normal
644  encode methods to do it and must encode it directly.
645
646  Also, the reason for encoding the superclass itself is that by doing so the unarchiver knows
647  what version is referred to by the encoded object.  This way we can replace the object with
648  a substitute class which will allow it to create itself as the custom class when read it by
649  the application, and using the encoding system to do it in a clean way.
650*/
651@implementation GSClassSwapper
652+ (void) initialize
653{
654  if (self == [GSClassSwapper class])
655    {
656      [self setVersion: GSSWAPPER_VERSION];
657    }
658}
659
660- (id) initWithObject: (id)object className: (NSString *)className superClassName: (NSString *)superClassName
661{
662  if ((self = [self init]) != nil)
663    {
664      NSDebugLog(@"Created template %@ -> %@",NSStringFromClass([self class]), className);
665      ASSIGN(_object, object);
666      ASSIGN(_className, className);
667      _superClass = NSClassFromString(superClassName);
668      if (_superClass == nil)
669	{
670	  [NSException raise: NSInternalInconsistencyException
671		       format: @"Unable to find class '%@', it is not linked into the application.", superClassName];
672	}
673    }
674  return self;
675}
676
677- init
678{
679  if ((self = [super init]) != nil)
680    {
681      _className = nil;
682      _superClass = nil;
683      _object = nil;
684    }
685  return self;
686}
687
688- (void) dealloc
689{
690  RELEASE(_object);
691  RELEASE(_className);
692  [super dealloc];
693}
694
695- (void) setClassName: (NSString *)name
696{
697  ASSIGN(_className, name);
698}
699
700- (NSString *)className
701{
702  return _className;
703}
704
705- (id) initWithCoder: (NSCoder *)coder
706{
707  id obj = nil;
708  int version = [coder versionForClassName: @"GSClassSwapper"];
709  if (version == 0)
710    {
711      if ((self = [super init]) != nil)
712	{
713	  NSUnarchiver *unarchiver = (NSUnarchiver *)coder;
714
715	  // decode class/superclass...
716	  [coder decodeValueOfObjCType: @encode(id) at: &_className];
717	  [coder decodeValueOfObjCType: @encode(Class) at: &_superClass];
718
719	  // if we are living within the interface builder app, then don't try to
720	  // morph into the subclass.
721	  if ([self shouldSwapClass])
722	    {
723	      Class aClass = NSClassFromString(_className);
724	      if (aClass == nil)
725		{
726		  [NSException raise: NSInternalInconsistencyException
727			       format: @"Unable to find class '%@', it is not linked into the application.", _className];
728		}
729
730	      // Initialize the object...  dont call decode, since this wont
731	      // allow us to instantiate the class we want.
732	      obj = [aClass alloc];
733	    }
734	  else
735	    {
736	      obj = [_superClass alloc];
737	    }
738
739	  // inform the coder that this object is to replace the template in all cases.
740	  [unarchiver replaceObject: self withObject: obj];
741	  obj = [obj initWithCoder: coder]; // unarchive the object...
742	}
743    }
744
745  // change the class of the instance to the one we want to see...
746  return obj;
747}
748
749- (void) encodeWithCoder: (NSCoder *)aCoder
750{
751  [aCoder encodeValueOfObjCType: @encode(id) at: &_className];
752  [aCoder encodeValueOfObjCType: @encode(Class) at: &_superClass];
753
754  if (_object != nil)
755    {
756      // Don't call encodeValue, the way templates are used will prevent
757      // it from being saved correctly.  Just call encodeWithCoder directly.
758      [_object encodeWithCoder: aCoder];
759    }
760}
761
762- (BOOL) shouldSwapClass
763{
764  BOOL result = YES;
765  if ([self respondsToSelector: @selector(isInInterfaceBuilder)])
766    {
767      result = !([self isInInterfaceBuilder]);
768    }
769  return result;
770}
771@end
772
773@implementation GSWindowTemplate
774+ (void) initialize
775{
776  if (self == [GSWindowTemplate class])
777    {
778      [self setVersion: GSWINDOWT_VERSION];
779    }
780}
781
782- (unsigned int) autoPositionMask
783{
784  return _autoPositionMask;
785}
786
787- (void) setAutoPositionMask: (unsigned int)flag
788{
789  _autoPositionMask = flag;
790}
791
792- (BOOL) deferFlag
793{
794  return _deferFlag;
795}
796
797- (void) setDeferFlag: (BOOL)flag
798{
799  _deferFlag = flag;
800}
801
802- (void) autoPositionWindow: (NSWindow *)window
803{
804  int		options = 0;
805  NSRect        currentScreenFrame = [[window screen] frame];
806  NSRect        windowFrame = [window frame];
807  NSPoint       origin  = windowFrame.origin;
808  NSSize	newSize = currentScreenFrame.size;
809  NSSize        oldSize = _screenRect.size;
810  BOOL		changedOrigin = NO;
811
812  // reposition the window on the screen.
813  if (_autoPositionMask == GSWindowAutoPositionNone)
814    return;
815
816  /*
817   * determine if and how the X axis can be resized
818   */
819  if (_autoPositionMask & GSWindowMinXMargin)
820    options++;
821
822  if (_autoPositionMask & GSWindowMaxXMargin)
823    options++;
824
825  /*
826   * adjust the X axis if any X options are set in the mask
827   */
828  if (options > 0)
829    {
830      float change = newSize.width - oldSize.width;
831      float changePerOption = change / options;
832
833      if (_autoPositionMask & GSWindowMinXMargin)
834	{
835	  origin.x += changePerOption;
836	  changedOrigin = YES;
837	}
838    }
839
840  /*
841   * determine if and how the Y axis can be resized
842   */
843  options = 0;
844  if (_autoPositionMask & GSWindowMinYMargin)
845    options++;
846
847  if (_autoPositionMask & GSWindowMaxYMargin)
848    options++;
849
850  /*
851   * adjust the Y axis if any Y options are set in the mask
852   */
853  if (options > 0)
854    {
855      float change = newSize.height - oldSize.height;
856      float changePerOption = change / options;
857
858      if (_autoPositionMask & (GSWindowMaxYMargin | GSWindowMinYMargin))
859	{
860	  if (_autoPositionMask & GSWindowMinYMargin)
861	    {
862	      origin.y += changePerOption;
863	      changedOrigin = YES;
864	    }
865	}
866    }
867
868  // change the origin of the window.
869  if (changedOrigin)
870    {
871      [window setFrameOrigin: origin];
872    }
873}
874
875// NSCoding...
876- (id) initWithCoder: (NSCoder *)coder
877{
878  id obj = [super initWithCoder: coder];
879  if (obj != nil)
880    {
881      int version = [coder versionForClassName: @"GSWindowTemplate"];
882
883      if (version == GSWINDOWT_VERSION)
884	{
885	  // decode the defer flag...
886	  [coder decodeValueOfObjCType: @encode(BOOL) at: &_deferFlag];
887	  [coder decodeValueOfObjCType: @encode(unsigned int) at: &_autoPositionMask];
888	  _screenRect = [coder decodeRect];
889	}
890      else if (version == 0)
891	{
892	  // decode the defer flag...
893	  [coder decodeValueOfObjCType: @encode(BOOL) at: &_deferFlag];
894	  _autoPositionMask = GSWindowAutoPositionNone;
895	  _screenRect = [[obj screen] frame];
896	}
897
898      // FIXME: The designated initializer logic for NSWindow is in the initWithCoder: method of
899      // NSWindow.   Unfortunately, this means that the "defer" flag for NSWindows and NSWindow
900      // subclasses in gorm files will be ignored.   This shouldn't have a great impact,
901      // but it is not the correct behavior.
902
903      //
904      // Set all of the attributes into the object, if it
905      // responds to any of these methods.
906      //
907      if ([obj respondsToSelector: @selector(setAutoPositionMask:)])
908	{
909	  [obj setAutoPositionMask: [self autoPositionMask]];
910	}
911
912      RELEASE(self);
913    }
914  return obj;
915}
916
917- (void) encodeWithCoder: (NSCoder *)coder
918{
919  int version = [[self class] version];
920
921  [super encodeWithCoder: coder];
922
923  if (version == GSWINDOWT_VERSION)
924    {
925      _screenRect = [[_object screen] frame];
926      [coder encodeValueOfObjCType: @encode(BOOL) at: &_deferFlag];
927      [coder encodeValueOfObjCType: @encode(unsigned int) at: &_autoPositionMask];
928      [coder encodeRect: _screenRect];
929    }
930  else if (version == 0)
931    {
932      [coder encodeValueOfObjCType: @encode(BOOL) at: &_deferFlag];
933    }
934}
935@end
936
937@implementation GSViewTemplate
938+ (void) initialize
939{
940  if (self == [GSViewTemplate class])
941    {
942      [self setVersion: GSVIEWT_VERSION];
943    }
944}
945
946- (id) initWithCoder: (NSCoder *)coder
947{
948  id obj = [super initWithCoder: coder];
949  if (obj != nil)
950    {
951      RELEASE(self);
952    }
953  return obj;
954}
955@end
956
957// Template for any classes which derive from NSText
958@implementation GSTextTemplate
959+ (void) initialize
960{
961  if (self == [GSTextTemplate class])
962    {
963      [self setVersion: GSTEXTT_VERSION];
964    }
965}
966
967- (id) initWithCoder: (NSCoder *)coder
968{
969  id     obj = [super initWithCoder: coder];
970  if (obj != nil)
971    {
972      RELEASE(self);
973    }
974  return obj;
975}
976@end
977
978// Template for any classes which derive from GSTextView
979@implementation GSTextViewTemplate
980+ (void) initialize
981{
982  if (self == [GSTextViewTemplate class])
983    {
984      [self setVersion: GSTEXTVIEWT_VERSION];
985    }
986}
987
988- (id) initWithCoder: (NSCoder *)coder
989{
990  id     obj = [super initWithCoder: coder];
991  if (obj != nil)
992    {
993      RELEASE(self);
994    }
995  return obj;
996}
997@end
998
999// Template for any classes which derive from NSMenu.
1000@implementation GSMenuTemplate
1001+ (void) initialize
1002{
1003  if (self == [GSMenuTemplate class])
1004    {
1005      [self setVersion: GSMENUT_VERSION];
1006    }
1007}
1008
1009- (id) initWithCoder: (NSCoder *)coder
1010{
1011  id     obj = [super initWithCoder: coder];
1012  if (obj != nil)
1013    {
1014      RELEASE(self);
1015    }
1016  return obj;
1017}
1018@end
1019
1020
1021// Template for any classes which derive from NSControl
1022@implementation GSControlTemplate
1023+ (void) initialize
1024{
1025  if (self == [GSControlTemplate class])
1026    {
1027      [self setVersion: GSCONTROLT_VERSION];
1028    }
1029}
1030
1031- (id) initWithCoder: (NSCoder *)coder
1032{
1033  id     obj = [super initWithCoder: coder];
1034  if (obj != nil)
1035    {
1036      RELEASE(self);
1037    }
1038  return obj;
1039}
1040@end
1041
1042@implementation GSObjectTemplate
1043+ (void) initialize
1044{
1045  if (self == [GSObjectTemplate class])
1046    {
1047      [self setVersion: GSOBJECTT_VERSION];
1048    }
1049}
1050
1051- (id) initWithCoder: (NSCoder *)coder
1052{
1053  id     obj = [super initWithCoder: coder];
1054  if (obj != nil)
1055    {
1056      RELEASE(self);
1057    }
1058  return obj;
1059}
1060@end
1061
1062// Order in this factory method is very important.
1063// Which template to create must be determined
1064// in sequence because of the class hierarchy.
1065@implementation GSTemplateFactory
1066+ (id) templateForObject: (id) object
1067	   withClassName: (NSString *)className
1068      withSuperClassName: (NSString *)superClassName
1069{
1070  id template = nil;
1071  if (object != nil)
1072    {
1073      if ([object isKindOfClass: [NSWindow class]])
1074	{
1075	  template = [[GSWindowTemplate alloc] initWithObject: object
1076					       className: className
1077					       superClassName: superClassName];
1078	}
1079      else if ([object isKindOfClass: [NSTextView class]])
1080	{
1081	  template = [[GSTextViewTemplate alloc] initWithObject: object
1082						 className: className
1083						 superClassName: superClassName];
1084	}
1085      else if ([object isKindOfClass: [NSText class]])
1086	{
1087	  template = [[GSTextTemplate alloc] initWithObject: object
1088					     className: className
1089					     superClassName: superClassName];
1090	}
1091      else if ([object isKindOfClass: [NSControl class]])
1092	{
1093	  template = [[GSControlTemplate alloc] initWithObject: object
1094						className: className
1095						superClassName: superClassName];
1096	}
1097      else if ([object isKindOfClass: [NSView class]])
1098	{
1099	  template = [[GSViewTemplate alloc] initWithObject: object
1100					     className: className
1101					     superClassName: superClassName];
1102	}
1103      else if ([object isKindOfClass: [NSMenu class]])
1104	{
1105	  template = [[GSMenuTemplate alloc] initWithObject: object
1106					     className: className
1107					     superClassName: superClassName];
1108	}
1109      else if ([object isKindOfClass: [NSObject class]])
1110	{
1111	  // for gui elements derived from NSObject
1112	  template = [[GSObjectTemplate alloc] initWithObject: object
1113					       className: className
1114					       superClassName: superClassName];
1115	}
1116    }
1117  return AUTORELEASE(template);
1118}
1119@end
1120