/* GSXibLoader Xib (Cocoa XML) model loader Copyright (C) 2010, 2011 Free Software Foundation, Inc. Written by: Fred Kiefer Created: March 2010 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110 USA. */ #import #import #import #import #import #import #import #import #import #import #import #import #import "AppKit/NSApplication.h" #import "AppKit/NSMenu.h" #import "AppKit/NSNib.h" #import "GNUstepGUI/GSModelLoaderFactory.h" #import "GNUstepGUI/GSNibLoading.h" #import "GNUstepGUI/GSXibLoading.h" #import "GNUstepGUI/GSXibKeyedUnarchiver.h" #import "GNUstepGUI/GSXib5KeyedUnarchiver.h" @interface NSApplication (NibCompatibility) - (void) _setMainMenu: (NSMenu*)aMenu; @end @interface NSMenu (XibCompatibility) - (BOOL) _isMainMenu; @end @implementation NSMenu (XibCompatibility) - (BOOL) _isMainMenu { if (_name) return [_name isEqualToString:@"_NSMainMenu"]; return NO; } @end @interface GSXibLoader: GSModelLoader { } @end @implementation GSXibLoader + (NSString*) type { return @"xib"; } + (float) priority { return 4.0; } - (void) awake: (NSArray *)rootObjects withContext: (NSDictionary *)context { NSEnumerator *en; id obj; NSMutableArray *topLevelObjects = [context objectForKey: NSNibTopLevelObjects]; id owner = [context objectForKey: NSNibOwner]; id first = nil; id app = nil; NSCustomObject *object; NSString *className; if ([rootObjects count] == 0) { NSWarnMLog(@"No root objects in XIB!"); return; } // Get the file's owner and NSApplication object references... object = (NSCustomObject*)[rootObjects objectAtIndex: 1]; if ([[object className] isEqualToString: @"FirstResponder"]) { first = [object realObject]; } else { NSLog(@"%s:first responder missing\n", __PRETTY_FUNCTION__); } object = (NSCustomObject*)[rootObjects objectAtIndex: 2]; className = [object className]; if ([className isEqualToString: @"NSApplication"] || [NSClassFromString(className) isSubclassOfClass:[NSApplication class]]) { app = [object realObject]; } else { NSLog(@"%s:NSApplication missing '%@'\n", __PRETTY_FUNCTION__, className); } // Use the owner as first root object [(NSCustomObject*)[rootObjects objectAtIndex: 0] setRealObject: owner]; en = [rootObjects objectEnumerator]; while ((obj = [en nextObject]) != nil) { if ([obj respondsToSelector: @selector(nibInstantiate)]) { obj = [obj nibInstantiate]; } // IGNORE file's owner, first responder and NSApplication instances... if ((obj != nil) && (obj != owner) && (obj != first) && (obj != app)) { [topLevelObjects addObject: obj]; // All top level objects must be released by the caller to avoid // leaking, unless they are going to be released by other nib // objects on behalf of the owner. RETAIN(obj); } if (([obj isKindOfClass: [NSMenu class]]) && ([obj _isMainMenu])) { // add the menu... [NSApp _setMainMenu: obj]; } } } - (void) awake: (NSArray *)rootObjects inContainer: (id)objects withContext: (NSDictionary *)context { [self awake: rootObjects withContext: context]; // Load connections and awaken objects if ([objects respondsToSelector: @selector(nibInstantiate)]) { [objects nibInstantiate]; } } - (BOOL) checkXib5: (NSData *)data { #if GNUSTEP_BASE_HAVE_LIBXML // Ensure we have a XIB 5 version...first see if we can parse the XML... NSXMLDocument *document = [[NSXMLDocument alloc] initWithData: data options: 0 error: NULL]; if (document == nil) { return NO; } else { // Test to see if this is an Xcode 5 XIB... NSArray *documentNodes = [document nodesForXPath: @"/document" error: NULL]; // Need at LEAST ONE document node...we should find something a bit more // specific to check here... return [documentNodes count] != 0; } #else // We now default to checking XIB 5 versions return YES; #endif } - (BOOL) loadModelData: (NSData *)data externalNameTable: (NSDictionary *)context withZone: (NSZone *)zone; { BOOL loaded = NO; NSKeyedUnarchiver *unarchiver = nil; NS_DURING { if (data != nil) { if ([self checkXib5: data]) { unarchiver = [[GSXib5KeyedUnarchiver alloc] initForReadingWithData: data]; } else { unarchiver = [[GSXibKeyedUnarchiver alloc] initForReadingWithData: data]; } if (unarchiver != nil) { NSArray *rootObjects; IBObjectContainer *objects; NSDebugLLog(@"XIB", @"Invoking unarchiver"); [unarchiver setObjectZone: zone]; rootObjects = [unarchiver decodeObjectForKey: @"IBDocument.RootObjects"]; objects = [unarchiver decodeObjectForKey: @"IBDocument.Objects"]; NSDebugLLog(@"XIB", @"rootObjects %@", rootObjects); [self awake: rootObjects inContainer: objects withContext: context]; loaded = YES; RELEASE(unarchiver); } else { NSLog(@"Could not instantiate Xib unarchiver/Unable to parse Xib."); } } else { NSLog(@"Data passed to Xib loading method is nil."); } } NS_HANDLER { NSLog(@"Exception occurred while loading model: %@",[localException reason]); // TEST_RELEASE(unarchiver); } NS_ENDHANDLER if (loaded == NO) { NSLog(@"Failed to load Xib\n"); } return loaded; } - (NSData*) dataForFile: (NSString*)fileName { NSFileManager *mgr = [NSFileManager defaultManager]; BOOL isDir = NO; NSDebugLLog(@"XIB", @"Loading Xib `%@'...\n", fileName); if ([mgr fileExistsAtPath: fileName isDirectory: &isDir]) { if (isDir == NO) { return [NSData dataWithContentsOfFile: fileName]; } else { NSLog(@"Xib file specified %@, is directory.", fileName); } } else { NSLog(@"Xib file specified %@, could not be found.", fileName); } return nil; } @end