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