From d9117bd9a01db1920b513777888b0f84ded1adeb Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Mon, 8 Apr 2013 15:48:13 -0400 Subject: [PATCH] Fixed: Converter was copying too much state from Nib2Cib, framework resources could not be referenced in Xcode. * State * Previously the Converter class needlessly copied a whole bunch of state from the Nib2Cib class, which was not only poor factoring, but made for easy omissions of state when instantiating a new Converter, such as was done in NSNib.j. This commit pushes all state not specific to Converter up to Nib2Cib. This allowed removal of redundant code in NSNib -NS_initWithCoder. * Resources * Previously it was not possible to use custom images that were located in a framework, because only the app's Resources directory was searched. This commit automatically searches all frameworks for Resources directories. When a custom image is specified in Xcode, first the app's Resources directory is searched, then all framework Resource directories. Alternately, the specific framework to use can be specified in Xcode by using the form framework@image. At runtime, the framework is loaded by its identifier (as specified in Info.plist), and if there is no identifier, by searching next to the current AppKit framework. All of this renders the nib2cib -R option completely obsolete, so it was removed from the NibApplication/Jakefile template. --- AppKit/Cib/_CPCibCustomResource.j | 84 ++++-- .../Templates/NibApplication/Jakefile | 1 - Tools/nib2cib/Converter+Mac.j | 9 +- Tools/nib2cib/Converter.j | 62 ++-- Tools/nib2cib/NSButton.j | 4 +- Tools/nib2cib/NSClassSwapper.j | 4 +- Tools/nib2cib/NSComboBox.j | 7 +- Tools/nib2cib/NSCustomResource.j | 45 ++- Tools/nib2cib/NSDatePicker.j | 6 +- Tools/nib2cib/NSNib.j | 16 +- Tools/nib2cib/NSProgressIndicator.j | 4 +- Tools/nib2cib/NSSearchField.j | 4 +- Tools/nib2cib/NSSegmentedControl.j | 4 +- Tools/nib2cib/NSTableColumn.j | 5 +- Tools/nib2cib/NSTableHeaderView.j | 4 +- Tools/nib2cib/NSTableView.j | 4 +- Tools/nib2cib/Nib2Cib.j | 275 ++++++++++-------- Tools/nib2cib/Nib2CibKeyedUnarchiver.j | 62 +++- 18 files changed, 353 insertions(+), 247 deletions(-) diff --git a/AppKit/Cib/_CPCibCustomResource.j b/AppKit/Cib/_CPCibCustomResource.j index aefe07e67..d929cd0c7 100644 --- a/AppKit/Cib/_CPCibCustomResource.j +++ b/AppKit/Cib/_CPCibCustomResource.j @@ -31,7 +31,7 @@ @import "CPTheme.j" @class CPButtonBar - +@class CPView var _CPCibCustomResourceClassNameKey = @"_CPCibCustomResourceClassNameKey", _CPCibCustomResourceResourceNameKey = @"_CPCibCustomResourceResourceNameKey", @@ -42,6 +42,7 @@ var _CPCibCustomResourceClassNameKey = @"_CPCibCustomResourceClassNameKey", CPString _className; CPString _resourceName; CPDictionary _properties; + CPBundle _bundle; } + (id)imageResourceWithName:(CPString)aResourceName size:(CGSize)aSize @@ -63,6 +64,7 @@ var _CPCibCustomResourceClassNameKey = @"_CPCibCustomResourceClassNameKey", _className = aClassName; _resourceName = aResourceName; _properties = properties; + _bundle = nil; } return self; @@ -77,6 +79,7 @@ var _CPCibCustomResourceClassNameKey = @"_CPCibCustomResourceClassNameKey", _className = [aCoder decodeObjectForKey:_CPCibCustomResourceClassNameKey]; _resourceName = [aCoder decodeObjectForKey:_CPCibCustomResourceResourceNameKey]; _properties = [aCoder decodeObjectForKey:_CPCibCustomResourcePropertiesKey]; + _bundle = nil; } return self; @@ -100,39 +103,72 @@ var _CPCibCustomResourceClassNameKey = @"_CPCibCustomResourceClassNameKey", else if (_resourceName == "CPRemoveTemplate") return [[CPTheme defaultTheme] valueForAttributeWithName:@"button-image-minus" forClass:[CPButtonBar class]]; - return [self imageFromBundle:[aCoder bundle]]; + return [self imageFromCoder:aCoder]; } return self; } -- (CPImage)imageFromBundle:(CPBundle)aBundle -{ - if (!aBundle) - { - var bundleClass = _properties.valueForKey(@"bundleClass"); - - if (bundleClass) - { - bundleClass = CPClassFromString(bundleClass); - - if (bundleClass) - aBundle = [CPBundle bundleForClass:bundleClass]; - } - else - aBundle = [CPBundle mainBundle]; - } - - return [[CPImage alloc] initWithContentsOfFile:[aBundle pathForResource:_resourceName] size:_properties.valueForKey(@"size")]; -} - @end @implementation _CPCibCustomResource (CPImage) +- (CPBundle)imageBundleWithCoder:(CPCoder)aCoder +{ + if (_bundle) + return _bundle; + + var bundleIdentifier = [_properties valueForKey:@"bundleIdentifier"]; + + if (bundleIdentifier) + _bundle = [CPBundle bundleWithIdentifier:bundleIdentifier]; + else + { + var bundleClass = [_properties valueForKey:@"bundleClass"]; + + if (bundleClass) + { + bundleClass = CPClassFromString(bundleClass); + + if (bundleClass) + _bundle = [CPBundle bundleForClass:bundleClass]; + } + } + + if (!_bundle) + { + var framework = [_properties valueForKey:@"framework"]; + + if (framework) + { + // Get AppKit and hope the framework is in the same directory + var appKit = [CPBundle bundleForClass:[CPView class]], + url = [[appKit bundleURL] URLByDeletingLastPathComponent]; + + url = [CPURL URLWithString:framework relativeToURL:url]; + _bundle = [CPBundle bundleWithURL:url]; + } + } + + if (!_bundle) + { + if (aCoder) + _bundle = [aCoder bundle]; + else + _bundle = [CPBundle mainBundle]; + } + + return _bundle; +} + +- (CPImage)imageFromCoder:(CPCoder)aCoder +{ + return [[CPImage alloc] initWithContentsOfFile:[[self imageBundleWithCoder:aCoder] pathForResource:_resourceName] size:[_properties valueForKey:@"size"]]; +} + - (CPString)filename { - return [[CPBundle mainBundle] pathForResource:_resourceName]; + return [[self imageBundleWithCoder:nil] pathForResource:_resourceName]; } - (CGSize)size @@ -162,7 +198,7 @@ var _CPCibCustomResourceClassNameKey = @"_CPCibCustomResourceClassNameKey", - (CPString)description { - var image = [self imageFromBundle:nil]; + var image = [self imageFromCoder:nil]; return [image description]; } diff --git a/Tools/capp/Resources/Templates/NibApplication/Jakefile b/Tools/capp/Resources/Templates/NibApplication/Jakefile index 9e688f19f..81068711d 100644 --- a/Tools/capp/Resources/Templates/NibApplication/Jakefile +++ b/Tools/capp/Resources/Templates/NibApplication/Jakefile @@ -30,7 +30,6 @@ app ("__project.nameasidentifier__", function(task) task.setResources(new FileList("Resources/**")); task.setIndexFilePath("index.html"); task.setInfoPlistPath("Info.plist"); - task.setNib2CibFlags("-R Resources/"); if (configuration === "Debug") task.setCompilerFlags("-DDEBUG -g"); diff --git a/Tools/nib2cib/Converter+Mac.j b/Tools/nib2cib/Converter+Mac.j index 606717d5e..e6145cfdf 100644 --- a/Tools/nib2cib/Converter+Mac.j +++ b/Tools/nib2cib/Converter+Mac.j @@ -24,12 +24,14 @@ @import "Nib2CibKeyedUnarchiver.j" @import "NSFont.j" +@class Nib2Cib + @implementation Converter (Mac) -- (void)convertedDataFromMacData:(CPData)data resourcesPath:(CPString)aResourcesPath +- (void)convertedDataFromMacData:(CPData)data { // Unarchive the NS data - var unarchiver = [[Nib2CibKeyedUnarchiver alloc] initForReadingWithData:data resourcesPath:aResourcesPath], + var unarchiver = [[Nib2CibKeyedUnarchiver alloc] initForReadingWithData:data], objectData = [unarchiver decodeObjectForKey:@"IB.objectdata"], objects = [unarchiver allObjects], count = [objects count]; @@ -106,7 +108,8 @@ // nil cibFont means try to use theme font if (!cibFont) { - var bold = [nibFont isBold]; + var bold = [nibFont isBold], + themes = [[Nib2Cib sharedNib2Cib] themes]; for (var i = 0; i < themes.length; ++i) { diff --git a/Tools/nib2cib/Converter.j b/Tools/nib2cib/Converter.j index a8c6d65ff..ed9857fa1 100644 --- a/Tools/nib2cib/Converter.j +++ b/Tools/nib2cib/Converter.j @@ -30,6 +30,8 @@ @import "NSFoundation.j" @import "NSAppKit.j" +@class Nib2Cib + @global java var FILE = require("file"), @@ -48,13 +50,8 @@ ConverterConversionException = @"ConverterConversionException"; @implementation Converter : CPObject { - CPString inputPath @accessors(readonly); - CPString outputPath @accessors; - CPString resourcesPath @accessors; - NibFormat format @accessors(readonly); - CPArray themes @accessors(readonly); - CPArray userNSClasses @accessors; - BOOL compileNib @accessors; + CPString inputPath @accessors(readonly); + CPString outputPath @accessors; } + (Converter)sharedConverter @@ -62,7 +59,7 @@ ConverterConversionException = @"ConverterConversionException"; return SharedConverter; } -- (id)initWithInputPath:(CPString)aPath format:(NibFormat)nibFormat themes:(CPArray)themeList +- (id)initWithInputPath:(CPString)anInputPath outputPath:(CPString)anOutputPath { self = [super init]; @@ -71,52 +68,43 @@ ConverterConversionException = @"ConverterConversionException"; if (!SharedConverter) SharedConverter = self; - inputPath = aPath; - format = nibFormat; - themes = themeList; - compileNib = YES; + inputPath = anInputPath; + outputPath = anOutputPath; } return self; } -- (void)convert +- (CPData)convert { - if ([resourcesPath length] && !FILE.isReadable(resourcesPath)) - [CPException raise:ConverterConversionException reason:@"Could not read Resources at path \"" + resourcesPath + "\""]; + // Assume its a Mac file. + var inferredFormat = NibFormatMac; - var inferredFormat = format; + // Some .xibs are iPhone nibs, check the actual contents in this case. + if (FILE.extension(inputPath) !== ".nib" && FILE.isFile(inputPath) && + FILE.read(inputPath, { charset:"UTF-8" }).indexOf(" @import -@class Converter +@class Nib2Cib @global CP_NSMapClassName @@ -48,7 +48,7 @@ var _CPCibClassSwapperClassNameKey = @"_CPCibClassSwapperClassNameKey", // If this is a userland NS class, call its KVC methods directly var nsClass = nil; - if ([[[Converter sharedConverter] userNSClasses] containsObject:aClassName]) + if ([[[Nib2Cib sharedNib2Cib] userNSClasses] containsObject:aClassName]) nsClass = objj_lookUpClass("NS_" + aClassName); var originalClass = nsClass || objj_lookUpClass(anOriginalClassName); diff --git a/Tools/nib2cib/NSComboBox.j b/Tools/nib2cib/NSComboBox.j index 27b9d0715..f2c227adf 100644 --- a/Tools/nib2cib/NSComboBox.j +++ b/Tools/nib2cib/NSComboBox.j @@ -25,7 +25,7 @@ @import "NSTextField.j" -@class Converter +@class Nib2Cib @implementation CPComboBox (NSCoding) @@ -48,12 +48,13 @@ [self setEnabled:[cell isEnabled]]; // Make sure the height is clipped to the max given by the theme - var maxSize = [[[Converter sharedConverter] themes][0] valueForAttributeWithName:@"max-size" forClass:[CPComboBox class]], + var theme = [Nib2Cib defaultTheme], + maxSize = [theme valueForAttributeWithName:@"max-size" forClass:[CPComboBox class]], size = [self frameSize], widthOffset = -3; // Adjust for differences between Cocoa and Cappuccino widget framing. - if ([[[Converter sharedConverter] themes][0] name] == @"Aristo") + if ([theme name] == @"Aristo") { _frame.origin.x += 1; widthOffset = -5; diff --git a/Tools/nib2cib/NSCustomResource.j b/Tools/nib2cib/NSCustomResource.j index 0864f222a..ad0886c38 100644 --- a/Tools/nib2cib/NSCustomResource.j +++ b/Tools/nib2cib/NSCustomResource.j @@ -41,7 +41,8 @@ var FILE = require("file"), _className = CP_NSMapClassName([aCoder decodeObjectForKey:@"NSClassName"]); _resourceName = [aCoder decodeObjectForKey:@"NSResourceName"]; - var size = CGSizeMakeZero(); + var size = CGSizeMakeZero(), + framework = @""; if (_resourceName == "NSSwitch") return nil; @@ -52,21 +53,47 @@ var FILE = require("file"), } else { - var resourcePath = [aCoder resourcePathForName:_resourceName]; + var match = /^(.+)@(.+)$/.exec(_resourceName), + framework = @"", + bundleIdentifier = @""; - if (!resourcePath) - CPLog.warn("Resource \"" + _resourceName + "\" not found in the resources path: " + [aCoder resourcesPath]); + if (match) + { + framework = match[1]; + _resourceName = match[2]; + } + + var resourceInfo = [aCoder resourceInfoForName:_resourceName inFramework:framework]; + + if (!resourceInfo) + CPLog.warn("Resource \"" + _resourceName + "\" not found in the Resources directories"); else - size = imageSize(FILE.canonical(resourcePath)) || CGSizeMakeZero(); + { + size = imageSize(FILE.canonical(resourceInfo.path)) || CGSizeMakeZero(); + framework = resourceInfo.framework; + } // Account for the fact that an extension may have been inferred. - if (resourcePath && FILE.extension(resourcePath) !== FILE.extension(_resourceName)) - _resourceName += FILE.extension(resourcePath); + if (resourceInfo && + resourceInfo.path && + FILE.extension(resourceInfo.path) !== FILE.extension(_resourceName)) + { + _resourceName += FILE.extension(resourceInfo.path); + } - CPLog.debug(" Rsrc: %s\n Path: %s\n Size: %d x %d", _resourceName, FILE.canonical(resourcePath), size.width, size.height); + CPLog.debug(" Resource: %s\n Framework: %s\n Path: %s\n Size: %d x %d", _resourceName, framework, resourceInfo ? FILE.canonical(resourceInfo.path) : "", size.width, size.height); } - _properties = [CPDictionary dictionaryWithObject:size forKey:@"size"]; + if (resourceInfo && resourceInfo.path && resourceInfo.framework) + { + var frameworkPath = FILE.dirname(FILE.dirname(resourceInfo.path)), + bundle = [CPBundle bundleWithPath:frameworkPath]; + + [bundle loadWithDelegate:nil]; + bundleIdentifier = [bundle bundleIdentifier] || @""; + } + + _properties = @{ @"size":size, @"bundleIdentifier":bundleIdentifier, @"framework":framework }; } return self; diff --git a/Tools/nib2cib/NSDatePicker.j b/Tools/nib2cib/NSDatePicker.j index 0d2335299..f0ebb2579 100644 --- a/Tools/nib2cib/NSDatePicker.j +++ b/Tools/nib2cib/NSDatePicker.j @@ -24,7 +24,7 @@ @import "NSCell.j" -@class Converter +@class Nib2Cib @global CPTextFieldDatePickerStyle @global CPTextFieldAndStepperDatePickerStyle @@ -57,7 +57,7 @@ var NSDatePickerDefaultSize = 22, [self setBezeled:[cell isBezeled]]; [self setBordered:[cell isBordered]]; - var theme = [[Converter sharedConverter] themes][0]; + var theme = [Nib2Cib defaultTheme]; if (_datePickerStyle != CPClockAndCalendarDatePickerStyle) { @@ -225,4 +225,4 @@ var NSDatePickerDefaultSize = 22, return [CPDate class]; } -@end \ No newline at end of file +@end diff --git a/Tools/nib2cib/NSNib.j b/Tools/nib2cib/NSNib.j index aaf13a2c0..f25f49e63 100644 --- a/Tools/nib2cib/NSNib.j +++ b/Tools/nib2cib/NSNib.j @@ -34,23 +34,15 @@ var FILE = require("file"), { self = [super init]; + // FIXME: change /tmp/ to os.tmpDir() in Node var nibPath = @"/tmp/" + UUID.uuid() + ".nib", - data = [aCoder decodeObjectForKey:@"NSNibFileData"], - sharedConverter = [Converter sharedConverter]; + data = [aCoder decodeObjectForKey:@"NSNibFileData"]; FILE.write(nibPath, data.bytes(), { charset:"UTF-16" }); - var converter = [[Converter alloc] initWithInputPath:@"" - format:[sharedConverter format] - themes:[sharedConverter themes]]; - [converter setCompileNib:NO]; - [converter setResourcesPath:[sharedConverter resourcesPath]]; - [converter setUserNSClasses:[sharedConverter userNSClasses]]; + var converter = [[Converter alloc] initWithInputPath:nibPath outputPath:nil]; - CPLog.info("Converting sub nib to plist..."); - - var nibData = [converter CPCompliantNibDataAtFilePath:nibPath]; - _data = [converter convertedDataFromMacData:nibData resourcesPath:[sharedConverter resourcesPath]]; + _data = [converter convert]; return self; } diff --git a/Tools/nib2cib/NSProgressIndicator.j b/Tools/nib2cib/NSProgressIndicator.j index 94795d77f..fa4ceff82 100644 --- a/Tools/nib2cib/NSProgressIndicator.j +++ b/Tools/nib2cib/NSProgressIndicator.j @@ -22,7 +22,7 @@ @import -@class Converter +@class Nib2Cib var NSProgressIndicatorSpinningFlag = 1 << 12; @@ -59,7 +59,7 @@ var NSProgressIndicatorSpinningFlag = 1 << 12; if (_style !== CPProgressIndicatorSpinningStyle) { - var theme = [[Converter sharedConverter] themes][0], + var theme = [Nib2Cib defaultTheme], height = [theme valueForAttributeWithName:@"default-height" forClass:CPProgressIndicator]; currentFrameSize.height = height; diff --git a/Tools/nib2cib/NSSearchField.j b/Tools/nib2cib/NSSearchField.j index 30bf0d384..367b5a7ed 100644 --- a/Tools/nib2cib/NSSearchField.j +++ b/Tools/nib2cib/NSSearchField.j @@ -24,7 +24,7 @@ @import "NSTextField.j" -@class Converter +@class Nib2Cib @implementation CPSearchField (NSCoding) @@ -43,7 +43,7 @@ [self setSendsWholeSearchString:[cell sendsWholeSearchString]]; [self setSendsSearchStringImmediately:[cell sendsSearchStringImmediately]]; - if ([[[Converter sharedConverter] themes][0] name] == @"Aristo" && [self isBezeled]) + if ([[Nib2Cib defaultTheme] name] === @"Aristo" && [self isBezeled]) { // NSTextField.j makes the field +7.0 pixels tall. We want +8.0 to go to 30. var frame = [self frame]; diff --git a/Tools/nib2cib/NSSegmentedControl.j b/Tools/nib2cib/NSSegmentedControl.j index 8525a2611..27abc164b 100644 --- a/Tools/nib2cib/NSSegmentedControl.j +++ b/Tools/nib2cib/NSSegmentedControl.j @@ -24,7 +24,7 @@ @import "NSCell.j" -@class Converter +@class Nib2Cib @implementation CPSegmentedControl (CPCoding) @@ -68,7 +68,7 @@ // Adjust for differences between Cocoa and Cappuccino widget framing. frame.origin.x += 6; - if ([[[Converter sharedConverter] themes][0] name] == @"Aristo2") + if ([[Nib2Cib defaultTheme] name] == @"Aristo2") frame.size.height += 1; frame.size.width = originalWidth; diff --git a/Tools/nib2cib/NSTableColumn.j b/Tools/nib2cib/NSTableColumn.j index 8a16d7a4d..cd53c1827 100644 --- a/Tools/nib2cib/NSTableColumn.j +++ b/Tools/nib2cib/NSTableColumn.j @@ -31,6 +31,7 @@ @import "NSTextField.j" @class Converter +@class Nib2Cib var IBDefaultFontSizeTableHeader = 11.0; @@ -63,7 +64,7 @@ var IBDefaultFontSizeTableHeader = 11.0; var headerCell = [aCoder decodeObjectForKey:@"NSHeaderCell"], headerView = [[_CPTableColumnHeaderView alloc] initWithFrame:CGRectMakeZero()], - theme = [[Converter sharedConverter] themes][0]; + theme = [Nib2Cib defaultTheme]; [headerView setStringValue:[headerCell objectValue]]; [headerView setFont:[headerCell font]]; @@ -124,7 +125,7 @@ var IBDefaultFontSizeTableHeader = 11.0; - (id)valueForDataViewThemeAttribute:(CPString)attribute inState:(int)state { - var themes = [[Converter sharedConverter] themes]; + var themes = [[Nib2Cib sharedNib2Cib] themes]; for (var i = 0; i < themes.length; ++i) { diff --git a/Tools/nib2cib/NSTableHeaderView.j b/Tools/nib2cib/NSTableHeaderView.j index 2ed89607a..48b0eab71 100644 --- a/Tools/nib2cib/NSTableHeaderView.j +++ b/Tools/nib2cib/NSTableHeaderView.j @@ -22,8 +22,8 @@ @import -@class Converter @class CPTableView +@class Nib2Cib @implementation CPTableHeaderView (NSCoding) @@ -37,7 +37,7 @@ // change the default height if (_bounds.size.height === 17) { - var theme = [[Converter sharedConverter] themes][0], + var theme = [Nib2Cib defaultTheme], height = [theme valueForAttributeWithName:@"default-row-height" forClass:CPTableView]; _bounds.size.height = height; diff --git a/Tools/nib2cib/NSTableView.j b/Tools/nib2cib/NSTableView.j index 46eefdd7c..3b4da3f54 100644 --- a/Tools/nib2cib/NSTableView.j +++ b/Tools/nib2cib/NSTableView.j @@ -22,7 +22,7 @@ @import -@class Converter +@class Nib2Cib @implementation CPTableView (NSCoding) @@ -43,7 +43,7 @@ // Convert xib default to cib default if (_rowHeight == 17) { - var theme = [[Converter sharedConverter] themes][0], + var theme = [Nib2Cib defaultTheme], height = [theme valueForAttributeWithName:@"default-row-height" forClass:CPTableView]; _rowHeight = height; diff --git a/Tools/nib2cib/Nib2Cib.j b/Tools/nib2cib/Nib2Cib.j index fa0887f01..e3080b9f1 100644 --- a/Tools/nib2cib/Nib2Cib.j +++ b/Tools/nib2cib/Nib2Cib.j @@ -42,7 +42,9 @@ var FILE = require("file"), BuildTypes = ["Debug", "Release"], DefaultFile = "MainMenu", AllowedStoredOptionsRe = new RegExp("^(defaultTheme|auxThemes|verbosity|quiet|frameworks|format)$"), - ArgsRe = /"[^\"]+"|'[^\']+'|\S+/g; + ArgsRe = /"[^\"]+"|'[^\']+'|\S+/g, + + SharedNib2Cib = nil; @implementation Nib2Cib : CPObject @@ -50,10 +52,22 @@ var FILE = require("file"), CPArray commandLineArgs; JSObject parser; JSObject nibInfo; - CPString appDirectory; - CPString resourcesDirectory; - CPDictionary infoPlist; - CPArray userNSClasses; + CPString appDirectory @accessors(readonly); + CPDictionary frameworks @accessors(readonly); + CPString appResourceDirectory @accessors(readonly); + CPDictionary infoPlist @accessors(readonly); + CPArray userNSClasses @accessors(readonly); + CPArray themes @accessors(readonly); +} + ++ (Nib2Cib)sharedNib2Cib +{ + return SharedNib2Cib; +} + ++ (CPTheme)defaultTheme +{ + return [SharedNib2Cib themes][0]; } - (id)initWithArgs:(CPArray)theArgs @@ -62,13 +76,18 @@ var FILE = require("file"), if (self) { + if (!SharedNib2Cib) + SharedNib2Cib = self; + commandLineArgs = theArgs; parser = new (require("narwhal/args").Parser)(); nibInfo = {}; appDirectory = @""; - resourcesDirectory = @""; + frameworks = [CPDictionary dictionary]; + appResourceDirectory = @""; infoPlist = @{}; userNSClasses = []; + themes = []; } return self; @@ -86,7 +105,7 @@ var FILE = require("file"), if (options.watch) [self watchWithOptions:options]; else - [self convertWithOptions:options inputFile:nil]; + [self convertWithOptions:options inputPath:nil]; } catch (anException) { @@ -104,25 +123,55 @@ var FILE = require("file"), [self failWithMessage:@"fontinfo does not appear to be installed"]; } -- (BOOL)convertWithOptions:(JSObject)options inputFile:(CPString)inputFile +- (void)enumerateFrameworks +{ + var frameworksDirectory = FILE.join(appDirectory, "Frameworks"), + debugFrameworksDirectory = FILE.join(frameworksDirectory, "Debug"); + + [debugFrameworksDirectory, frameworksDirectory].forEach(function(directory) + { + if (FILE.isDirectory(directory)) + { + var frameworkList = FILE.list(directory); + + frameworkList.forEach(function(framework) + { + if (framework !== @"Debug" && ![frameworks containsKey:framework]) + { + var resourceDirectory = FILE.join(directory, framework, "Resources"); + + if (FILE.isDirectory(resourceDirectory)) + resourceDirectory = FILE.canonical(resourceDirectory); + else + resourceDirectory = @""; + + [frameworks setValue:{resourceDirectory:resourceDirectory, loaded:false} forKey:framework]; + } + }); + } + }); +} + +- (BOOL)convertWithOptions:(JSObject)options inputPath:(CPString)inputPath { try { - inputFile = inputFile || [self getInputFile:options.args]; + inputPath = inputPath || [self getInputPath:options.args]; - [self getAppAndResourceDirectoriesFromInputFile:inputFile options:options]; + [self getAppAndResourceDirectoriesFromInputPath:inputPath options:options]; + [self enumerateFrameworks]; if (options.readStoredOptions) { - options = [self mergeOptionsWithStoredOptions:options inputFile:inputFile]; + options = [self mergeOptionsWithStoredOptions:options inputPath:inputPath]; [self setLogLevel:options.quiet ? -1 : options.verbosity]; } if (!options.quiet && options.verbosity > 0) [self printVersion]; - var outputFile = [self getOutputFileFromInputFile:inputFile args:options.args], - configInfo = [self readConfigFile:options.configFile || @"" inputFile:inputFile]; + var configInfo = [self readConfigFile:options.configFile || @"" inputPath:inputPath], + outputPath = [self getOutputPathFromInputPath:inputPath args:options.args]; infoPlist = configInfo.plist; @@ -141,37 +190,40 @@ var FILE = require("file"), else infoPlist = @{}; - var themeList = [self getThemeList:options], - themes = [self loadThemesFromList:themeList]; + var themeList = [self getThemeList:options]; + + [self loadThemesFromList:themeList]; + [self loadFrameworks:options.frameworks verbosity:options.verbosity]; + [self loadNSClassesFromBundle:[CPBundle mainBundle]]; + + var frameworkList = []; + + [frameworks allKeys].forEach(function(name) + { + var info = [frameworks valueForKey:name]; + + if (info.resourceDirectory) + name += "*"; + + if (info.loaded) + name += "+"; + + frameworkList.push(name); + }); CPLog.info("\n-------------------------------------------------------------"); - CPLog.info("Input : " + inputFile); - CPLog.info("Output : " + outputFile); - CPLog.info("Format : " + ["Auto", "Mac", "iPhone"][options.format]); + CPLog.info("Input : " + inputPath); + CPLog.info("Output : " + outputPath); CPLog.info("Application : " + appDirectory); - CPLog.info("Resources : " + resourcesDirectory); - CPLog.info("Frameworks : " + (options.frameworks || "")); + CPLog.info("Frameworks : " + (frameworkList.join(", ") || "")); CPLog.info("Default theme : " + themeList[0]); CPLog.info("Aux themes : " + themeList.slice(1).join(", ")); CPLog.info("Config file : " + (configInfo.path || "")); CPLog.info("System Font : " + [CPFont systemFontSize] + "px " + [CPFont systemFontFace]); CPLog.info("-------------------------------------------------------------\n"); - var converter = [[Converter alloc] initWithInputPath:inputFile - format:options.format - themes:themes]; - - [converter setOutputPath:outputFile]; - [converter setResourcesPath:resourcesDirectory]; - - var loadFrameworksCallback = function() - { - [self loadNSClassesFromBundle:[CPBundle mainBundle]]; - [converter setUserNSClasses:userNSClasses]; - [converter convert]; - }; - - [self loadFrameworks:options.frameworks verbosity:options.verbosity callback:loadFrameworksCallback]; + var converter = [[Converter alloc] initWithInputPath:inputPath outputPath:outputPath]; + [converter convert]; return YES; } @@ -250,7 +302,7 @@ var FILE = require("file"), // Let the converter log however the user configured it [self setLogLevel:verbosity]; - var success = [self convertWithOptions:options inputFile:nib]; + var success = [self convertWithOptions:options inputPath:nib]; [self setLogLevel:1]; @@ -275,11 +327,6 @@ var FILE = require("file"), .set(true) .help("Ask nib2cib to watch a directory for changes"); - parser.option("-R", "resourcesDir") - .set() - .displayName("directory") - .help("Set the Resources directory, usually unnecessary as it is inferred from the input path"); - parser.option("--default-theme", "defaultTheme") .set() .displayName("name") @@ -312,15 +359,15 @@ var FILE = require("file"), .def(true) .help("Do not read stored options"); - parser.option("--mac", "format") - .set(NibFormatMac) - .def(NibFormatUndetermined) - .help("Set format to Mac"); - parser.option("--version", "showVersion") .action(function() { [self printVersionAndExit]; }) .help("Show the version of nib2cib and quit"); + parser.option("-R", "deprecatedResourcesDir") + .set() + .displayName("resources directory") + .help("This option is deprecated."); + parser.helpful(); var options = parser.parse(theArgs, null, null, true); @@ -334,13 +381,13 @@ var FILE = require("file"), return options; } -- (JSObject)mergeOptionsWithStoredOptions:(JSObject)options inputFile:(CPString)inputFile +- (JSObject)mergeOptionsWithStoredOptions:(JSObject)options inputPath:(CPString)inputPath { // We have to clone options var userOptions = [self readStoredOptionsAtPath:FILE.join(SYS.env["HOME"], ".nib2cibconfig")], appOptions = [self readStoredOptionsAtPath:FILE.join(appDirectory, "nib2cib.conf")], - filename = FILE.basename(inputFile, FILE.extension(inputFile)) + ".conf", - fileOptions = [self readStoredOptionsAtPath:FILE.join(FILE.dirname(inputFile), filename)]; + filename = FILE.basename(inputPath, FILE.extension(inputPath)) + ".conf", + fileOptions = [self readStoredOptionsAtPath:FILE.join(FILE.dirname(inputPath), filename)]; // At this point we have an array of args without the initial command in args[0], // add the command and parse the options. @@ -445,31 +492,31 @@ var FILE = require("file"), CPLogRegister(CPLogPrint, null, logFormatter); } -- (CPString)getInputFile:(CPArray)theArgs +- (CPString)getInputPath:(CPArray)theArgs { - var inputFile = theArgs[0] || DefaultFile, + var inputPath = theArgs[0] || DefaultFile, path = ""; - if (!/^.+\.[nx]ib$/.test(inputFile)) + if (!/^.+\.[nx]ib$/.test(inputPath)) { - if (path = [self findInputFile:inputFile extension:@".xib"]) - inputFile = path; - else if (path = [self findInputFile:inputFile extension:@".nib"]) - inputFile = path; + if (path = [self findInputPath:inputPath extension:@".xib"]) + inputPath = path; + else if (path = [self findInputPath:inputPath extension:@".nib"]) + inputPath = path; else - [self failWithMessage:@"Cannot find the input file (.xib or .nib): " + FILE.canonical(inputFile)]; + [self failWithMessage:@"Cannot find the input file (.xib or .nib): " + FILE.canonical(inputPath)]; } - else if (path = [self findInputFile:inputFile extension:nil]) - inputFile = path; + else if (path = [self findInputPath:inputPath extension:nil]) + inputPath = path; else - [self failWithMessage:@"Could not read the input file: " + FILE.canonical(inputFile)]; + [self failWithMessage:@"Could not read the input file: " + FILE.canonical(inputPath)]; - return FILE.canonical(inputFile); + return FILE.canonical(inputPath); } -- (void)findInputFile:(CPString)inputFile extension:(CPString)extension +- (void)findInputPath:(CPString)inputPath extension:(CPString)extension { - var path = inputFile; + var path = inputPath; if (extension) path += extension; @@ -477,7 +524,7 @@ var FILE = require("file"), if (FILE.isReadable(path)) return path; - if (FILE.basename(FILE.dirname(inputFile)) !== "Resources" && FILE.isDirectory("Resources")) + if (FILE.basename(FILE.dirname(inputPath)) !== "Resources" && FILE.isDirectory("Resources")) { path = FILE.resolve(path, FILE.join("Resources", FILE.basename(path))); @@ -488,71 +535,59 @@ var FILE = require("file"), return null; } -- (void)getAppAndResourceDirectoriesFromInputFile:(CPString)inputFile options:(JSObject)options +- (void)getAppAndResourceDirectoriesFromInputPath:(CPString)aPath options:(JSObject)options { - appDirectory = resourcesDirectory = ""; + appDirectory = @""; - if (options.resourcesDir) - { - var path = FILE.canonical(options.resourcesDir); - - if (!FILE.isDirectory(path)) - [self failWithMessage:@"Cannot read resources at: " + path]; - - resourcesDirectory = path; - } - - var parentDir = FILE.dirname(inputFile); + var parentDir = FILE.dirname(aPath); if (FILE.basename(parentDir) === "Resources") { appDirectory = FILE.dirname(parentDir); - resourcesDirectory = resourcesDirectory || parentDir; + appResourceDirectory = parentDir; } else { appDirectory = parentDir; - if (!resourcesDirectory) + if (!appResourceDirectory) { var path = FILE.join(appDirectory, "Resources"); if (FILE.isDirectory(path)) - resourcesDirectory = path; + appResourceDirectory = path; } } } -- (CPString)getOutputFileFromInputFile:(CPString)inputFile args:(CPArray)theArgs +- (CPString)getOutputPathFromInputPath:(CPString)aPath args:(CPArray)theArgs { - var outputFile = null; + var outputPath = null; if (theArgs.length > 1) { - outputFile = theArgs[1]; + outputPath = theArgs[1]; - if (!/^.+\.cib$/.test(outputFile)) - outputFile += ".cib"; + if (!/^.+\.cib$/.test(outputPath)) + outputPath += ".cib"; } else - outputFile = FILE.join(FILE.dirname(inputFile), FILE.basename(inputFile, FILE.extension(inputFile))) + ".cib"; + outputPath = FILE.join(FILE.dirname(aPath), FILE.basename(aPath, FILE.extension(aPath))) + ".cib"; - outputFile = FILE.canonical(outputFile); + outputPath = FILE.canonical(outputPath); - if (!FILE.isWritable(FILE.dirname(outputFile))) - [self failWithMessage:@"Cannot write the output file at: " + outputFile]; + if (!FILE.isWritable(FILE.dirname(outputPath))) + [self failWithMessage:@"Cannot write the output file at: " + outputPath]; - return outputFile; + return outputPath; } -- (void)loadFrameworks:(CPArray)frameworks verbosity:(int)verbosity callback:(JSObject)aCallback +- (void)loadFrameworks:(CPArray)frameworksToLoad verbosity:(int)verbosity { - if (!frameworks || frameworks.length === 0) - return aCallback(); + if (!frameworksToLoad || frameworksToLoad.length === 0) + return; - var returnPath = function(path) { return path; }; - - frameworks.forEach(function(aFramework) + frameworksToLoad.forEach(function(aFramework) { [self setLogLevel:verbosity]; @@ -561,19 +596,13 @@ var FILE = require("file"), // If it is just a name with no path components, try to locate it if (aFramework.indexOf("/") === -1) { - frameworkPath = [self findInCappBuild:aFramework isDirectory:YES callback:returnPath]; - - if (!frameworkPath) - frameworkPath = [self findInFrameworks:FILE.join(appDirectory, "Frameworks") - path:aFramework - isDirectory:YES - callback:returnPath]; - - if (!frameworkPath) - frameworkPath = [self findInInstalledFrameworks:aFramework isDirectory:YES callback:returnPath]; + frameworkPath = [self findInFrameworks:FILE.join(appDirectory, "Frameworks") + path:aFramework + isDirectory:YES + callback:function(path) { return path; }]; } else - frameworkPath = FILE.canonical(aFramework); + [self failWithMessage:@"-F should be used only with a framework name that is in the app's Framework directory"]; if (!frameworkPath) [self failWithMessage:@"Cannot find the framework \"" + aFramework + "\""]; @@ -592,6 +621,11 @@ var FILE = require("file"), [self setLogLevel:verbosity]; [self loadNSClassesFromBundle:frameworkBundle]; + + var frameworkName = FILE.basename(frameworkPath), + info = [frameworks valueForKey:frameworkName]; + + info.loaded = true; } finally { @@ -600,8 +634,6 @@ var FILE = require("file"), require("browser/timeout").serviceTimeouts(); }); - - aCallback(); } - (void)loadNSClassesFromBundle:(CPBundle)aBundle @@ -634,20 +666,20 @@ var FILE = require("file"), if (!defaultTheme) defaultTheme = [self getAppKitDefaultThemeName]; - var themes = [CPSet setWithObject:defaultTheme]; + var themeList = [CPSet setWithObject:defaultTheme]; if (options.auxThemes) - [themes addObjectsFromArray:options.auxThemes]; + [themeList addObjectsFromArray:options.auxThemes]; var auxThemes = infoPlist.valueForKey("CPAuxiliaryThemes"); if (auxThemes) - [themes addObjectsFromArray:auxThemes]; + [themeList addObjectsFromArray:auxThemes]; // Now remove the default theme, get the list as an array, and insert the default at the beginning - [themes removeObject:defaultTheme]; + [themeList removeObject:defaultTheme]; - var allThemes = [themes allObjects]; + var allThemes = [themeList allObjects]; [allThemes insertObject:defaultTheme atIndex:0]; @@ -735,23 +767,20 @@ var FILE = require("file"), return themeName; } -- (CPArray)loadThemesFromList:(CPArray)themeList +- (void)loadThemesFromList:(CPArray)themeList { - var themes = []; - for (var i = 0; i < themeList.length; ++i) - themes.push([self loadThemeNamed:themeList[i] directory:resourcesDirectory]); - - return themes; + themes.push([self loadThemeNamed:themeList[i]]); } -- (CPTheme)loadThemeNamed:(CPString)themeName directory:(CPString)themeDir +- (CPTheme)loadThemeNamed:(CPString)themeName { if (/^.+\.blend$/.test(themeName)) themeName = themeName.substr(0, themeName.length - ".blend".length); var blendName = themeName + ".blend", - themePath = ""; + themePath = "", + themeDir = appResourceDirectory; if (themeDir) { @@ -820,7 +849,7 @@ var FILE = require("file"), return theme; } -- (JSObject)readConfigFile:(CPString)configFile inputFile:(CPString)inputFile +- (JSObject)readConfigFile:(CPString)configFile inputPath:(CPString)inputPath { var configPath = null, path; diff --git a/Tools/nib2cib/Nib2CibKeyedUnarchiver.j b/Tools/nib2cib/Nib2CibKeyedUnarchiver.j index e60efd233..256a19759 100644 --- a/Tools/nib2cib/Nib2CibKeyedUnarchiver.j +++ b/Tools/nib2cib/Nib2CibKeyedUnarchiver.j @@ -22,22 +22,13 @@ @import +@class Nib2Cib + var FILE = require("file"); @implementation Nib2CibKeyedUnarchiver : CPKeyedUnarchiver { - CPString resourcesPath @accessors(readonly); -} - -- (id)initForReadingWithData:(CPData)data resourcesPath:(CPString)aResourcesPath -{ - self = [super initForReadingWithData:data]; - - if (self) - resourcesPath = aResourcesPath; - - return self; } - (CPArray)allObjects @@ -45,12 +36,51 @@ var FILE = require("file"); return _objects; } -- (CPString)resourcePathForName:(CPString)aName +- (JSObject)resourceInfoForName:(CPString)aName inFramework:(CPString)framework { - if (!resourcesPath) - return NULL; + var nib2cib = [Nib2Cib sharedNib2Cib], + frameworks = [nib2cib frameworks]; - var pathGroups = [FILE.listPaths(resourcesPath)]; + if (framework) + { + var info = [frameworks valueForKey:framework]; + + if (!info || !info.resourceDirectory) + return nil; + + return { path:[self _resourcePathForName:aName inDirectory:info.resourceDirectory], framework:framework }; + } + else + { + // Try the app's resource directory first + var resourcesDirectory = [nib2cib appResourceDirectory], + path = [self _resourcePathForName:aName inDirectory:resourcesDirectory]; + + if (path) + return { path:path, framework:framework }; + + var enumerator = [frameworks keyEnumerator]; + + while ((framework = [enumerator nextObject])) + { + var info = [frameworks valueForKey:framework]; + + if (!info || !info.resourceDirectory) + continue; + + path = [self _resourcePathForName:aName inDirectory:info.resourceDirectory]; + + if (path) + return { path:path, framework:framework }; + } + } + + return nil; +} + +- (CPString)_resourcePathForName:(CPString)aName inDirectory:(CPString)directory +{ + var pathGroups = [FILE.listPaths(directory)]; while (pathGroups.length > 0) { @@ -73,7 +103,7 @@ var FILE = require("file"); } } - return NULL; + return nil; } @end