/* GSXibLoading Xib (Cocoa XML) loading helper classes Copyright (C) 2010, 2011 Free Software Foundation, Inc. Written by: Fred Kiefer Created: March 2010 Author: Gregory John Casamento Date: 2010, 2012 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 "AppKit/NSControl.h" #import "AppKit/NSNibLoading.h" #import "AppKit/NSWindow.h" #import "GNUstepGUI/GSNibLoading.h" #import "GNUstepGUI/GSXibLoading.h" @interface IBAccessibilityAttribute : NSObject @end @interface IBNSLayoutConstraint : NSObject @end @interface IBLayoutConstant : NSObject @end @implementation IBUserDefinedRuntimeAttribute - (void) encodeWithCoder: (NSCoder *)coder { if([coder allowsKeyedCoding]) { [coder encodeObject: typeIdentifier forKey: @"typeIdentifier"]; [coder encodeObject: keyPath forKey: @"keyPath"]; [coder encodeObject: value forKey: @"value"]; } } - (id) initWithCoder: (NSCoder *)coder { if([coder allowsKeyedCoding]) { [self setTypeIdentifier: [coder decodeObjectForKey: @"typeIdentifier"]]; [self setKeyPath: [coder decodeObjectForKey: @"keyPath"]]; [self setValue: [coder decodeObjectForKey: @"value"]]; } return self; } - (void) setTypeIdentifier: (NSString *)type { ASSIGN(typeIdentifier, type); } - (NSString *) typeIdentifier { return typeIdentifier; } - (void) setKeyPath: (NSString *)kpath { ASSIGN(keyPath, kpath); } - (NSString *) keyPath { return keyPath; } - (void) setValue: (id)val { ASSIGN(value, val); } - (id) value { return value; } - (NSString*) description { NSMutableString *description = [[super description] mutableCopy]; [description appendString: @" <"]; [description appendFormat: @" type: %@", typeIdentifier]; [description appendFormat: @" keyPath: %@", keyPath]; [description appendFormat: @" value: %@", value]; [description appendString: @">"]; return AUTORELEASE(description); } @end @implementation IBUserDefinedRuntimeAttributesPlaceholder - (void) encodeWithCoder: (NSCoder *)coder { if([coder allowsKeyedCoding]) { [coder encodeObject: name forKey: @"IBUserDefinedRuntimeAttributesPlaceholderName"]; [coder encodeObject: runtimeAttributes forKey: @"userDefinedRuntimeAttributes"]; } } - (id) initWithCoder: (NSCoder *)coder { if([coder allowsKeyedCoding]) { [self setName: [coder decodeObjectForKey: @"IBUserDefinedRuntimeAttributesPlaceholderName"]]; [self setRuntimeAttributes: [coder decodeObjectForKey: @"userDefinedRuntimeAttributes"]]; } return self; } - (void) setName: (NSString *)value { ASSIGN(name, value); } - (NSString *) name { return name; } - (void) setRuntimeAttributes: (NSArray *)attrbutes { ASSIGN(runtimeAttributes, attrbutes); } - (NSArray *) runtimeAttributes { return runtimeAttributes; } @end @implementation IBAccessibilityAttribute - (void) encodeWithCoder: (NSCoder *)coder { } - (id) initWithCoder: (NSCoder *)coder { return self; } @end @implementation IBNSLayoutConstraint - (void) encodeWithCoder: (NSCoder *)coder { // Do nothing... } - (id) initWithCoder: (NSCoder *)coder { return self; } @end @implementation IBLayoutConstant - (void) encodeWithCoder: (NSCoder *)coder { // Do nothing... } - (id) initWithCoder: (NSCoder *)coder { return self; } @end @implementation FirstResponder + (id) allocWithZone: (NSZone*)zone { return nil; } @end @implementation IBClassDescriptionSource - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"majorKey"]) { ASSIGN(majorKey, [coder decodeObjectForKey: @"majorKey"]); } if ([coder containsValueForKey: @"minorKey"]) { ASSIGN(minorKey, [coder decodeObjectForKey: @"minorKey"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(majorKey); DESTROY(minorKey); [super dealloc]; } @end @implementation IBPartialClassDescription - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"className"]) { ASSIGN(className, [coder decodeObjectForKey: @"className"]); } if ([coder containsValueForKey: @"superclassName"]) { ASSIGN(superclassName, [coder decodeObjectForKey: @"superclassName"]); } if ([coder containsValueForKey: @"actions"]) { ASSIGN(actions, [coder decodeObjectForKey: @"actions"]); } if ([coder containsValueForKey: @"outlets"]) { ASSIGN(outlets, [coder decodeObjectForKey: @"outlets"]); } if ([coder containsValueForKey: @"sourceIdentifier"]) { ASSIGN(sourceIdentifier, [coder decodeObjectForKey: @"sourceIdentifier"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(className); DESTROY(superclassName); DESTROY(actions); DESTROY(outlets); DESTROY(sourceIdentifier); [super dealloc]; } @end @implementation IBClassDescriber - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"referencedPartialClassDescriptions"]) { ASSIGN(referencedPartialClassDescriptions, [coder decodeObjectForKey: @"referencedPartialClassDescriptions"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(referencedPartialClassDescriptions); [super dealloc]; } @end @implementation IBConnection - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"label"]) { ASSIGN(label, [coder decodeObjectForKey: @"label"]); } if ([coder containsValueForKey: @"source"]) { ASSIGN(source, [coder decodeObjectForKey: @"source"]); } if ([coder containsValueForKey: @"destination"]) { ASSIGN(destination, [coder decodeObjectForKey: @"destination"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) encodeWithCoder: (NSCoder*)aCoder { // FIXME } - (void) dealloc { DESTROY(label); DESTROY(source); DESTROY(destination); [super dealloc]; } - (NSString*) label { return label; } - (id) source { return source; } - (id) destination { return destination; } - (NSNibConnector*) nibConnector { NSString *tag = [self label]; NSRange colonRange = [tag rangeOfString: @":"]; NSUInteger location = colonRange.location; NSNibConnector *result = nil; if (location == NSNotFound) { result = [[NSNibOutletConnector alloc] init]; } else { result = [[NSNibControlConnector alloc] init]; } [result setDestination: [self destination]]; [result setSource: [self source]]; [result setLabel: [self label]]; return result; } - (NSString*) description { return [NSString stringWithFormat: @"%@ - label: %@, source: %@, destination: %@", [super description], label, NSStringFromClass([source class]), NSStringFromClass([destination class])]; } - (id) nibInstantiate { if ([source respondsToSelector: @selector(nibInstantiate)]) { ASSIGN(source, [source nibInstantiate]); } if ([destination respondsToSelector: @selector(nibInstantiate)]) { ASSIGN(destination, [destination nibInstantiate]); } return self; } - (void) establishConnection { } @end @implementation IBActionConnection - (void) dealloc { DESTROY(trigger); [super dealloc]; } - (instancetype) initWithCoder: (NSCoder *)coder { self = [super initWithCoder: coder]; if (self) { if ([coder allowsKeyedCoding]) { // label and source string tags have changed for XIB5... if ([coder containsValueForKey: @"selector"]) { ASSIGN(label, [coder decodeObjectForKey: @"selector"]); } if ([coder containsValueForKey: @"target"]) { ASSIGN(source, [coder decodeObjectForKey: @"target"]); } // Looks like the 'trigger' attribute should be used to override the // action setup method... if ([coder containsValueForKey: @"trigger"]) { ASSIGN(trigger, [coder decodeObjectForKey: @"trigger"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.", NSStringFromClass([self class]), NSStringFromClass([coder class])]; } } return self; } - (NSString*) trigger { return trigger; } - (void) establishConnection { SEL sel = NSSelectorFromString(label); [destination setTarget: source]; if (trigger && [trigger length]) { NSString *selName = [NSString stringWithFormat: @"set%@%@:", [[trigger substringToIndex: 1] uppercaseString], [trigger substringFromIndex: 1]]; SEL trigsel = NSSelectorFromString(selName); if (sel && trigsel && [destination respondsToSelector: trigsel]) { NSWarnMLog(@"setting trigger %@ to selector %@", selName, label); [destination performSelector: trigsel withObject: (id)sel]; } else if (!sel) { NSWarnMLog(@"label %@ does not correspond to any selector", label); } else if (!trigsel) { NSWarnMLog(@"trigger %@ does not correspond to any selector", trigger); } else { NSWarnMLog(@"destination class (%@) does not respond to trigger selector %@", NSStringFromClass([destination class]), selName); } } else { // Otherwise invoke the normal method... [destination setAction: sel]; } } @end @implementation IBOutletConnection - (instancetype) initWithCoder: (NSCoder *)coder { self = [super initWithCoder: coder]; if (self) { if ([coder allowsKeyedCoding]) { // label string tag has changed for XIB5... if ([coder containsValueForKey: @"property"]) { ASSIGN(label, [coder decodeObjectForKey: @"property"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.", NSStringFromClass([self class]), NSStringFromClass([coder class])]; } } return self; } - (void) establishConnection { NS_DURING { if (source != nil) { NSString *selName; SEL sel; selName = [NSString stringWithFormat: @"set%@%@:", [[label substringToIndex: 1] uppercaseString], [label substringFromIndex: 1]]; sel = NSSelectorFromString(selName); if (sel && [source respondsToSelector: sel]) { [source performSelector: sel withObject: destination]; } else { /* * We cannot use the KVC mechanism here, as this would always retain _dst * and it could also affect _setXXX methods and _XXX ivars that aren't * affected by the Cocoa code. */ const char *name = [label cString]; Class class = object_getClass(source); Ivar ivar = class_getInstanceVariable(class, name); if (ivar != 0) { object_setIvar(source, ivar, destination); } else { NSWarnMLog(@"class '%@' has no instance var named: %@", NSStringFromClass(class), label); } } } } NS_HANDLER { NSLog(@"Error while establishing connection %@: %@",self,[localException reason]); } NS_ENDHANDLER; } @end @implementation IBBindingConnection - (void) dealloc { DESTROY(connector); [super dealloc]; } - (id) initWithCoder: (NSCoder*)coder { self = [super initWithCoder: coder]; if (self == nil) return nil; if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"connector"]) { ASSIGN(connector, [coder decodeObjectForKey: @"connector"]); } } return self; } - (void) establishConnection { [connector establishConnection]; } @end @implementation IBConnectionRecord - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"connection"]) { ASSIGN(connection, [coder decodeObjectForKey: @"connection"]); } else { NSString *format = [NSString stringWithFormat:@"%s:Can't decode %@ without a connection ID", __PRETTY_FUNCTION__, NSStringFromClass([self class])]; [NSException raise: NSInvalidArgumentException format: @"%@", format]; } // Load the connection ID.... if ([coder containsValueForKey: @"connectionID"]) { // PRE-4.6 XIBs.... connectionID = [coder decodeIntForKey: @"connectionID"]; } else if ([coder containsValueForKey: @"id"]) { // 4.6+ XIBs.... NSString *string = [coder decodeObjectForKey: @"id"]; if (string && [string isKindOfClass:[NSString class]] && [string length]) { connectionID = [string intValue]; } else { NSString *format = [NSString stringWithFormat:@"%s:class: %@ - connection ID is missing or zero!", __PRETTY_FUNCTION__, NSStringFromClass([self class])]; [NSException raise: NSInvalidArgumentException format: @"%@", format]; } } else { NSString *format = [NSString stringWithFormat:@"%s:Can't decode %@ without a connection ID", __PRETTY_FUNCTION__, NSStringFromClass([self class])]; [NSException raise: NSInvalidArgumentException format: @"%@", format]; } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(connection); [super dealloc]; } - (IBConnection*) connection { return connection; } - (id) nibInstantiate { ASSIGN(connection, [connection nibInstantiate]); return self; } - (void) establishConnection { [connection establishConnection]; } - (NSString *) description { return [NSString stringWithFormat: @"<%@ - %d, %@>", [super description], connectionID, connection]; } @end @implementation IBToolTipAttribute - (NSString*) toolTip { return toolTip; } - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"name"]) { ASSIGN(name, [coder decodeObjectForKey: @"name"]); } if ([coder containsValueForKey: @"object"]) { ASSIGN(object, [coder decodeObjectForKey: @"object"]); } if ([coder containsValueForKey: @"toolTip"]) { ASSIGN(toolTip, [coder decodeObjectForKey: @"toolTip"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(name); DESTROY(object); DESTROY(toolTip); [super dealloc]; } @end @implementation IBInitialTabViewItemAttribute - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"name"]) { ASSIGN(name, [coder decodeObjectForKey: @"name"]); } if ([coder containsValueForKey: @"object"]) { ASSIGN(object, [coder decodeObjectForKey: @"object"]); } if ([coder containsValueForKey: @"initialTabViewItem"]) { ASSIGN(initialTabViewItem, [coder decodeObjectForKey: @"initialTabViewItem"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(name); DESTROY(object); DESTROY(initialTabViewItem); [super dealloc]; } @end @implementation IBObjectRecord - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"objectID"]) { // PRE-4.6 XIBs.... objectID = [coder decodeObjectForKey: @"objectID"]; } else if ([coder containsValueForKey: @"id"]) { // 4.6+ XIBs.... objectID = [coder decodeObjectForKey: @"id"]; } else { // Cannot process without object ID... NSString *format = [NSString stringWithFormat:@"%s:Can't decode %@ without an object ID", __PRETTY_FUNCTION__, NSStringFromClass([self class])]; [NSException raise: NSInvalidArgumentException format: @"%@", format]; } if ([coder containsValueForKey: @"object"]) { ASSIGN(object, [coder decodeObjectForKey: @"object"]); } if ([coder containsValueForKey: @"children"]) { ASSIGN(children, [coder decodeObjectForKey: @"children"]); } if ([coder containsValueForKey: @"parent"]) { ASSIGN(parent, [coder decodeObjectForKey: @"parent"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(object); DESTROY(children); DESTROY(parent); [super dealloc]; } - (id) object { return object; } - (id) parent { return parent; } - (id) objectID { return objectID; } - (NSString *) description { return [NSString stringWithFormat: @"<%@, %@, %@, %p>", [self className], object, parent, objectID]; } @end @implementation IBMutableOrderedSet - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"orderedObjects"]) { ASSIGN(orderedObjects, [coder decodeObjectForKey: @"orderedObjects"]); } } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) dealloc { DESTROY(orderedObjects); [super dealloc]; } - (NSArray*) orderedObjects { return orderedObjects; } - (id) objectWithObjectID: (id)objID { NSEnumerator *en; IBObjectRecord *obj; en = [orderedObjects objectEnumerator]; while ((obj = [en nextObject]) != nil) { if ([[obj objectID] isEqual:objID]) { return [obj object]; } } return nil; } @end @implementation IBObjectContainer - (id) initWithCoder: (NSCoder*)coder { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"sourceID"]) { ASSIGN(sourceID, [coder decodeObjectForKey: @"sourceID"]); } if ([coder containsValueForKey: @"maxID"]) { maxID = [coder decodeIntForKey: @"maxID"]; } if ([coder containsValueForKey: @"flattenedProperties"]) { ASSIGN(flattenedProperties, [coder decodeObjectForKey: @"flattenedProperties"]); } if ([coder containsValueForKey: @"objectRecords"]) { ASSIGN(objectRecords, [coder decodeObjectForKey: @"objectRecords"]); } if ([coder containsValueForKey: @"connectionRecords"]) { ASSIGN(connectionRecords, [coder decodeObjectForKey: @"connectionRecords"]); } // We could load more data here, but we currently don't need it. } else { [NSException raise: NSInvalidArgumentException format: @"Can't decode %@ with %@.",NSStringFromClass([self class]), NSStringFromClass([coder class])]; } return self; } - (void) encodeWithCoder: (NSCoder*)aCoder { // FIXME } - (void) dealloc { DESTROY(connectionRecords); DESTROY(objectRecords); DESTROY(flattenedProperties); DESTROY(unlocalizedProperties); DESTROY(activeLocalization); DESTROY(localizations); DESTROY(sourceID); [super dealloc]; } - (NSString*) description { return [NSString stringWithFormat: @"%@ - sourceID: %@: maxID: %d:" @" objectRecords: %@: flattenedProperties: %@: connectionRecords: %@: ", [super description], sourceID, maxID, objectRecords, flattenedProperties, connectionRecords]; } - (NSEnumerator*) connectionRecordEnumerator { return [connectionRecords objectEnumerator]; } - (NSEnumerator*) objectRecordEnumerator { return [[objectRecords orderedObjects] objectEnumerator]; } - (NSDictionary*) propertiesForObjectID: (id)objectID { NSEnumerator *en; NSString *idString; NSString *key; NSMutableDictionary *properties; int idLength; idString = [NSString stringWithFormat: @"%@.", objectID]; idLength = [idString length]; properties = [[NSMutableDictionary alloc] init]; en = [flattenedProperties keyEnumerator]; while ((key = [en nextObject]) != nil) { if ([key hasPrefix: idString]) { id value = [flattenedProperties objectForKey: key]; [properties setObject: value forKey: [key substringFromIndex: idLength]]; } } return AUTORELEASE(properties); } /* Returns a dictionary of the custom class names keyed on the objectIDs. */ - (NSDictionary*) customClassNames { NSMutableDictionary *properties; int i; properties = [[NSMutableDictionary alloc] init]; // We have special objects at -3, -2, -1 and 0 for (i = -3; i < maxID; i++) { NSString *idString; id value; idString = [NSString stringWithFormat: @"%d.CustomClassName", i]; value = [flattenedProperties objectForKey: idString]; if (value) { NSString *key; key = [NSString stringWithFormat: @"%d", i]; [properties setObject: value forKey: key]; } } return properties; } - (id) nibInstantiate { NSEnumerator *en; id obj; // If we are currently in IB, then don't do anything. if ([NSClassSwapper isInInterfaceBuilder]) { // Prepare objects for display in Gorm/IB. en = [[objectRecords orderedObjects] objectEnumerator]; while ((obj = [en nextObject]) != nil) { id realObj = [obj object]; // All objects should respond to this as it is defined on // NSObject(NSNibAwaking) [realObj prepareForInterfaceBuilder]; } return self; } // iterate over connections, instantiate, and then establish them. en = [connectionRecords objectEnumerator]; while ((obj = [en nextObject]) != nil) { [obj nibInstantiate]; [obj establishConnection]; } // awaken all objects. en = [[objectRecords orderedObjects] objectEnumerator]; while ((obj = [en nextObject]) != nil) { id realObj; NSDictionary *properties; id value; realObj = [obj object]; if ([realObj respondsToSelector: @selector(nibInstantiate)]) { realObj = [realObj nibInstantiate]; } properties = [self propertiesForObjectID: [obj objectID]]; NSDebugLLog(@"XIB", @"object %@ props %@", [obj objectID], properties); //value = [properties objectForKey: @"windowTemplate.maxSize"]; //value = [properties objectForKey: @"CustomClassName"]; // Activate windows value = [properties objectForKey: @"NSWindowTemplate.visibleAtLaunch"]; if (value != nil) { if ([value boolValue] == YES) { if ([realObj isKindOfClass: [NSWindow class]]) { // bring visible windows to front... [(NSWindow *)realObj orderFront: self]; } } } // Tool tips value = [properties objectForKey: @"IBAttributePlaceholdersKey"]; if (value != nil) { NSDictionary *infodict = (NSDictionary*)value; // Process tooltips... IBToolTipAttribute *tooltip = [infodict objectForKey: @"ToolTip"]; if (tooltip && [realObj respondsToSelector: @selector(setToolTip:)]) { [realObj setToolTip: [tooltip toolTip]]; } // Process XIB runtime attributes... if ([infodict objectForKey:@"IBUserDefinedRuntimeAttributesPlaceholderName"]) { IBUserDefinedRuntimeAttributesPlaceholder *placeholder = [infodict objectForKey:@"IBUserDefinedRuntimeAttributesPlaceholderName"]; NSArray *attributes = [placeholder runtimeAttributes]; NSEnumerator *objectIter = [attributes objectEnumerator]; IBUserDefinedRuntimeAttribute *attribute; while ((attribute = [objectIter nextObject]) != nil) { [realObj setValue: [attribute value] forKeyPath: [attribute keyPath]]; } } } [realObj awakeFromNib]; } return self; } @end