From fdcd56e0ae7c9142bd759555e7f57b63036e9de3 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Thu, 31 Mar 2011 14:52:38 -0400 Subject: [PATCH 01/13] Added a man page for capp, global function for copying a man page --- Tools/capp/Jakefile | 10 +++- Tools/capp/capp.1 | 131 ++++++++++++++++++++++++++++++++++++++++++++ common.jake | 45 +++++++++++++++ 3 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 Tools/capp/capp.1 diff --git a/Tools/capp/Jakefile b/Tools/capp/Jakefile index b88e0956e..a1bb029ab 100644 --- a/Tools/capp/Jakefile +++ b/Tools/capp/Jakefile @@ -1,8 +1,12 @@ require ("../../common.jake"); -var app = require("objective-j/jake").app; -var BundleTask = require("objective-j/jake").BundleTask; +var app = require("objective-j/jake").app, + BundleTask = require("objective-j/jake").BundleTask, + fileCreate = require("jake").fileCreate, + FILE = require("file"), + OS = require("os"), + stream = require("narwhal/term").stream; app ("capp", function(cappTask) { @@ -36,6 +40,8 @@ filedir($BUILD_CJS_CAPP, ["capp"], function() make_objj_executable($BUILD_CJS_CAPP); }); +copyManPage("capp", 1); + task ("build", ["capp", $BUILD_CJS_CAPP]); CLOBBER.include($BUILD_CJS_CAPPUCCINO_BIN); diff --git a/Tools/capp/capp.1 b/Tools/capp/capp.1 new file mode 100644 index 000000000..6242eadee --- /dev/null +++ b/Tools/capp/capp.1 @@ -0,0 +1,131 @@ +.Dd March 30, 2011 +.Os "Cappuccino" +.Dt CAPP 1 "PRM" +.\"----------------------------------------------------------------------------------------- +.Sh NAME +.\"----------------------------------------------------------------------------------------- +.Nm capp +.Nd generate cappuccino projects +.\"----------------------------------------------------------------------------------------- +.Sh SYNOPSIS +.\"----------------------------------------------------------------------------------------- +.Nm +.Sy gen +.Op options +.Pa PATH +.Nm +.Cm gen +.Op --list-templates | --list-frameworks +.Nm +.Cm config +.Op options +.Nm +.Op --version | -h | --help +.\"----------------------------------------------------------------------------------------- +.Sh "DESCRIPTION" +.\"----------------------------------------------------------------------------------------- +.Nm +generates starter projects for cappuccino, ensuring that all of the necessary files +are present, and applying the values in your +.Ar configuration +to placeholders the template. +.\"----------------------------------------------------------------------------------------- +.Sh "GENERATING PROJECTS" +.\"----------------------------------------------------------------------------------------- +When invoked using +.Nm +.Cm gen , +you either create a new project or modify the frameworks of an existing project. +.Ss Create options +When creating a new project, if no options are specified, +.Nm +will use the +.Ar Application +template, which by default does not use a +.Ar cib +to create its interface. If you wish to use a different template, the following options are available: +.Bl -tag -width 4n +.It Fl t, \-template Ar template +Specifies the template name to use when generating the project. This determines the type +of project to generate. +.It Fl \-list-templates +Displays the templates names available for use with the +.Sy -t/--template +option. +.El +.Ss Frameworks options +There are a number of options available for specifying how to create or update +the Frameworks directory in your project. The available options are: +.Bl -tag -width 4n +.It Fl f, \-frameworks +This will copy or symlink +.Sy only +the Frameworks directory instead of generating an entire project. This is +especially useful if you want to update the Frameworks for an existing project. +.It Fl F, \-framework Ar name +The +.Sy -f/--frameworks +option always copies/symlinks the Objective-J, Foundation, and AppKit frameworks. You may +include additional frameworks with this option. You may use this option multiple times +to include multiple frameworks. +.It Fl \-list-frameworks +Displays a list of frameworks that are available for use with the +.Sy -F/--framework +option. +.It Fl \-symlink +By default frameworks are copied to the project directory. Use this option to symlink +to the source frameworks instead of copying. +.It Fl \-build +By default frameworks are copied/symlinked from the installed version created with +.Sy jake install. +Use this option to copy/symlink the version created in +.Ar $CAPP_BUILD +with +.Sy jake debug +or +.Sy jake release. +.It Fl l +This is a handy shortcut for +.Sy --symlink --build. +.El +.\"----------------------------------------------------------------------------------------- +.Sh CONFIGURING +.\"----------------------------------------------------------------------------------------- +When invoked using +.Nm +.Cm config , +you can set or inspect the values used to replace placeholder text in the templates. The syntax +for +.Cm capp config +is as follows: +.Bl -tag -width 4n +.It Ar key value +Creates or updates a configuration item with the given key and value. +.It Fl \-get Ar key +Displays the configuration value with the given key. If no item with the given key exists, +nothing happens. +.It Fl \-remove Ar key +Removes the configuration value with the given key. If no item with the given key exists, +nothing happens. +.It Fl -l, \-list +Displays a list of all configuration items in the form +.Sy key=value. +.El +.\"----------------------------------------------------------------------------------------- +.Sh GENERAL OPTIONS +.\"----------------------------------------------------------------------------------------- +.Bl -tag -width 4n +.It Fl \-version +Displays the current +.Nm +version and exits. +.It Fl h, \-help +Displays a concise list of the available commands and options. +.El +.\"----------------------------------------------------------------------------------------- +.Sh FILES +.\"----------------------------------------------------------------------------------------- +.Bl -hang -width 4n +.It Em ~/.cappconfig +Contains your configuration in 280north plist format. +.El diff --git a/common.jake b/common.jake index c00ed9377..f1fa62f74 100644 --- a/common.jake +++ b/common.jake @@ -412,6 +412,51 @@ global.sudo = function(/*String*/ aTaskName) OS.exit(1); //rake abort if ($? != 0) } +global.copyManPage = function(/*String*/ name, /*int*/ section) +{ + var manDir = "/usr/local/share/man/man" + section, + pageFile = name + "." + section, + manPagePath = FILE.join(manDir, pageFile); + + if (!FILE.exists(manPagePath) || FILE.mtime(pageFile) > FILE.mtime(manPagePath)) + { + var sudo = ["sudo", "-p", "\nEnter your admin password: "], + useSudo = false, + success = true, + cmd; + + if (!FILE.isDirectory(manDir)) + { + cmd = ["mkdir", "-p", "-m", "0755", manDir]; + + if (FILE.isWritable(FILE.dirname(manDir))) + success = OS.system(cmd) === 0; + else + { + useSudo = true; + success = OS.system(sudo.concat(cmd)) === 0; + } + + if (!success) + { + stream.print("\0red(Unable to create the man directory.\0)"); + OS.exit(1); + } + } + + cmd = ["cp", "-f", pageFile, manDir]; + + if (FILE.isWritable(manDir)) + success = OS.system(cmd) === 0; + else + success = OS.system(sudo.concat(cmd)) === 0; + + if (!success) + stream.print("\0red(Unable to copy the man file.\0)"); + } +} + + // built in tasks task ("build"); From c9fb862ee21fd5e0b194bb4f9f1acd2986da381b Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Sun, 3 Apr 2011 07:56:16 -0400 Subject: [PATCH 02/13] Tweak --- Tools/capp/capp.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/capp/capp.1 b/Tools/capp/capp.1 index 6242eadee..cc8e4919e 100644 --- a/Tools/capp/capp.1 +++ b/Tools/capp/capp.1 @@ -12,7 +12,7 @@ .Nm .Sy gen .Op options -.Pa PATH +.Pa path .Nm .Cm gen .Op --list-templates | --list-frameworks From f29dcc9c6bf7979306ab71ab764d5cacd82958a3 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sun, 3 Apr 2011 16:12:27 -0400 Subject: [PATCH 03/13] Document CPScreen. --- AppKit/CPScreen.j | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/AppKit/CPScreen.j b/AppKit/CPScreen.j index 3fb6df42f..d1ccaea50 100644 --- a/AppKit/CPScreen.j +++ b/AppKit/CPScreen.j @@ -21,11 +21,23 @@ */ @import +/*! + @ingroup appkit + @class CPScreen + A CPScreen object describes the attributes of a display device available + to Cappuccino. +*/ @implementation CPScreen : CPObject { } +/*! + Returns the position and size of the visible area of the receiving screen. + This will normally be smaller than the full size of the screen to account + for system UI elements. For example, on a Mac the top of the visible frame + is placed below the bottom of the menu bar. +*/ - (CGRect)visibleFrame { #if PLATFORM(DOM) @@ -35,4 +47,4 @@ #endif } -@end \ No newline at end of file +@end From 501c15775e3e16c98135d42790e0c8a929adb099 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sun, 3 Apr 2011 16:58:39 -0400 Subject: [PATCH 04/13] Use bsdtar explicitly for 'jake docs' in case the user has GNU tar installed through e.g. fink. This enables the -s parameter to work. --- Tools/Documentation/make_headers.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tools/Documentation/make_headers.sh b/Tools/Documentation/make_headers.sh index 80885a682..0d8cf3a4b 100755 --- a/Tools/Documentation/make_headers.sh +++ b/Tools/Documentation/make_headers.sh @@ -11,12 +11,12 @@ if [ -d Foundation.doc ]; then fi echo "Processing source files..." -tar cf AppKit.doc.tar --exclude='_*' -s /^AppKit/AppKit.doc/ AppKit/*.j AppKit/**/*.j -tar xf AppKit.doc.tar +bsdtar cf AppKit.doc.tar --exclude='_*' -s /^AppKit/AppKit.doc/ AppKit/*.j AppKit/**/*.j +bsdtar xf AppKit.doc.tar rm AppKit.doc.tar -tar cf Foundation.doc.tar --exclude='_*' -s /^Foundation/Foundation.doc/ Foundation/*.j Foundation/**/*.j -tar xf Foundation.doc.tar +bsdtar cf Foundation.doc.tar --exclude='_*' -s /^Foundation/Foundation.doc/ Foundation/*.j Foundation/**/*.j +bsdtar xf Foundation.doc.tar rm Foundation.doc.tar find AppKit.doc -name *.j -exec sed -e '/@import.*/ d' -i '' {} \; From 7107adfe16cfeb52ad19ceb51c203bb8bc4aa0a8 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sun, 3 Apr 2011 17:45:53 -0400 Subject: [PATCH 05/13] Minor doc formatting. --- AppKit/CPScreen.j | 2 + AppKit/CPViewController.j | 90 +++++++++++++++++++++++++-------------- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/AppKit/CPScreen.j b/AppKit/CPScreen.j index d1ccaea50..afa8ef2d5 100644 --- a/AppKit/CPScreen.j +++ b/AppKit/CPScreen.j @@ -37,6 +37,8 @@ This will normally be smaller than the full size of the screen to account for system UI elements. For example, on a Mac the top of the visible frame is placed below the bottom of the menu bar. + + @return the visible screen rectangle */ - (CGRect)visibleFrame { diff --git a/AppKit/CPViewController.j b/AppKit/CPViewController.j index 31ffe7d99..f5a3230d3 100644 --- a/AppKit/CPViewController.j +++ b/AppKit/CPViewController.j @@ -29,26 +29,38 @@ var CPViewControllerCachedCibs; -/*! @class CPViewController - The CPViewController class provides the fundamental view-management controller for Cappuccino applications. - The basic view controller class supports the presentation of an associated view in addition to basic support - for managing modal views and, in the future, animations. Subclasses such as CPNavigationController and - CPTabBarController provide additional behavior for managing complex hierarchies of view controllers and views. +/*! + @ingroup appkit + @class CPViewController - You use each instance of CPViewController to manage a single view (and hierarchy). For a simple view controller, - this entails managing the view hierarchy responsible for presenting your application content. - A typical view hierarchy consists of a root viewŃa reference to which is available in the view property of this classŃ - and one or more subviews presenting the actual content. In the case of navigation and tab bar controllers, the view - controller manages not only the high-level view hierarchy (which provides the navigation controls) but also one - or more additional view controllers that handle the presentation of the application content. + The CPViewController class provides the fundamental view-management + controller for Cappuccino applications. The basic view controller class + supports the presentation of an associated view in addition to basic + support for managing modal views and, in the future, animations. + Subclasses such as CPNavigationController and CPTabBarController provide + additional behavior for managing complex hierarchies of view controllers + and views. - Unlike UIViewController in Cocoa Touch, a CPViewController does not represent an entire screen of content. You - will add your root view to an existing view or window's content view. You can manage many view controllers - on screen at once. CPViewController is also the preferred way of working with Cibs. + You use each instance of CPViewController to manage a single view (and + hierarchy). For a simple view controller, this entails managing the view + hierarchy responsible for presenting your application content. A typical + view hierarchy consists of a root view, a reference to which is available + in the view property of this class, and one or more subviews presenting + the actual content. In the case of navigation and tab bar controllers, the + view controller manages not only the high-level view hierarchy (which + provides the navigation controls) but also one or more additional view + controllers that handle the presentation of the application content. - Subclasses can override -loadView to create their custom view hierarchy, or specify a cib name to be loaded automatically. - It has methods that are called when a view appears or disappears. - This class is also a good place for delegate & datasource methods, and other controller stuff. + Unlike UIViewController in Cocoa Touch, a CPViewController does not + represent an entire screen of content. You will add your root view to an + existing view or window's content view. You can manage many view + controllers on screen at once. CPViewController is also the preferred way + of working with Cibs. + + Subclasses can override -loadView to create their custom view hierarchy, + or specify a cib name to be loaded automatically. It has methods that are + called when a view appears or disappears. This class is also a good place + for delegate & datasource methods, and other controller stuff. */ @implementation CPViewController : CPResponder { @@ -88,9 +100,14 @@ var CPViewControllerCachedCibs; } /*! - The designated initializer. If you subclass CPViewController, you must call the super implementation of this method, even if you aren't using a Cib. - In the specified Cib, the File's Owner proxy should have its class set to your view controller subclass, with the view outlet connected to the main view. - If you pass in a nil Cib name, then you must either call -setView: before -view is invoked, or override -loadView to set up your views. + The designated initializer. If you subclass CPViewController, you must + call the super implementation of this method, even if you aren't using a + Cib. + + In the specified Cib, the File's Owner proxy should have its class set to + your view controller subclass, with the view outlet connected to the main + view. If you pass in a nil Cib name, then you must either call -setView: + before -view is invoked, or override -loadView to set up your views. @param cibNameOrNil The path to the cib to load for the root view or nil to programmatically create views. @param cibBundleOrNil The bundle that the cib is located in or nil for the main bundle. @@ -113,14 +130,18 @@ var CPViewControllerCachedCibs; } /*! - Programmatically creates the view that the controller manages. - You should never call this method directly. The view controller calls this method when the view property is requested but is nil. + Programmatically creates the view that the controller manages. You should + never call this method directly. The view controller calls this method + when the view property is requested but is nil. - If you create your views manually, you must override this method and use it to create your view and assign it to the view property. - The default implementation for programmatic views is to create a plain view. You can invoke super to utilize this view. + If you create your views manually, you must override this method and use + it to create your view and assign it to the view property. The default + implementation for programmatic views is to create a plain view. You can + invoke super to utilize this view. - If you use Interface Builder to create your views, you initialize the view using the - initWithCibName:bundle: method then you must not override this method. The consequences risk shattering the space-time continuum. + If you use Interface Builder to create your views, you initialize the view + using the initWithCibName:bundle: method then you must not override this + method. The consequences risk shattering the space-time continuum. Note: The cib loading system is currently synchronous. */ @@ -144,8 +165,10 @@ var CPViewControllerCachedCibs; /*! Returns the view that the controller manages. - If this property is nil, the controller sends loadView to itself to create the view that it manages. - Subclasses should override the loadView method to create any custom views. The default value is nil. + + If this property is nil, the controller sends loadView to itself to create + the view that it manages. Subclasses should override the loadView method + to create any custom views. The default value is nil. */ - (CPView)view { @@ -183,8 +206,11 @@ var CPViewControllerCachedCibs; /*! This method is called after the view controller has loaded its associated views into memory. - This method is called regardless of whether the views were stored in a nib file or created programmatically in the loadView method. - This method is most commonly used to perform additional initialization steps on views that are loaded from cib files. + + This method is called regardless of whether the views were stored in a nib + file or created programmatically in the loadView method. This method is + most commonly used to perform additional initialization steps on views + that are loaded from cib files. */ - (void)viewDidLoad { @@ -194,7 +220,9 @@ var CPViewControllerCachedCibs; /*! Manually sets the view that the controller manages. - Setting to nil will cause -loadView to be called on all subsequent calls of -view. + + Setting to nil will cause -loadView to be called on all subsequent calls + of -view. @param aView The view this controller should represent. */ From f0bba44e45c6c0468ce8f58005da1c6bf5d9a3d3 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sun, 3 Apr 2011 17:56:31 -0400 Subject: [PATCH 06/13] Completed CPTabView documentation. --- AppKit/CPTabView.j | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/AppKit/CPTabView.j b/AppKit/CPTabView.j index dfa108e50..e2e97048d 100644 --- a/AppKit/CPTabView.j +++ b/AppKit/CPTabView.j @@ -16,6 +16,14 @@ var CPTabViewDidSelectTabViewItemSelector = 1, CPTabViewWillSelectTabViewItemSelector = 4, CPTabViewDidChangeNumberOfTabViewItemsSelector = 8; +/*! + @ingroup appkit + @class CPTabView + + A CPTabView object presents a tabbed interface where each page is one a + complete view hiearchy of its own. The user can navigate between various + pages by clicking on the tab headers. +*/ @implementation CPTabView : CPView { CPArray _items; @@ -75,8 +83,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, } /*! - Inserts a CPTabViewItem into the tab view - at the specified index. + Inserts a CPTabViewItem into the tab view at the specified index. @param aTabViewItem the item to insert @param anIndex the index for the item */ @@ -122,6 +129,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the index of the specified item @param aTabViewItem the item to find the index for + @return the index of aTabViewItem or CPNotFound */ - (int)indexOfTabViewItem:(CPTabViewItem)aTabViewItem { @@ -131,6 +139,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the index of the CPTabViewItem with the specified identifier. @param anIdentifier the identifier of the item + @return the index of the tab view item identified by anIdentifier, or CPNotFound */ - (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifier { @@ -143,6 +152,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the number of items in the tab view. + @return the number of tab view items in the receiver */ - (unsigned)numberOfTabViewItems { @@ -151,6 +161,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the CPTabViewItem at the specified index. + @return a tab view item, or nil */ - (CPTabViewItem)tabViewItemAtIndex:(unsigned)anIndex { @@ -159,6 +170,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the array of items that backs this tab view. + @return a copy of the array of items in the receiver */ - (CPArray)tabViewItems { @@ -262,6 +274,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the current item being displayed. + @return the tab view item currenly being displayed by the receiver */ - (CPTabViewItem)selectedTabViewItem { @@ -271,6 +284,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, // Modifying the font /*! Returns the font for tab label text. + @return the font for tab label text */ - (CPFont)font { @@ -279,6 +293,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Sets the font for tab label text to font. + @param font the font the receiver should use for tab label text */ - (void)setFont:(CPFont)font { @@ -336,6 +351,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the tab view type. + @return the tab view type of the receiver */ - (CPTabViewType)tabViewType { @@ -344,6 +360,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1, /*! Returns the receiver's delegate. + @return the receiver's delegate */ - (id)delegate { From 40bfb55b64cffc90af6d551cb0d651906b5a9144 Mon Sep 17 00:00:00 2001 From: Klaas Pieter Annema Date: Mon, 4 Apr 2011 14:23:36 +0200 Subject: [PATCH 07/13] don't resolve nib2cib output path --- Tools/nib2cib/main.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/nib2cib/main.j b/Tools/nib2cib/main.j index 098117cf2..7cfc62d40 100644 --- a/Tools/nib2cib/main.j +++ b/Tools/nib2cib/main.j @@ -283,7 +283,7 @@ function getOutputFile(inputFile, args) if (!/^.+\.cib$/.test(outputFile)) outputFile += ".cib"; - outputFile = FILE.resolve(inputFile, outputFile); + outputFile = FILE.absolute(outputFile); if (!FILE.isWritable(FILE.dirname(outputFile))) fail("Cannot write the output file at: " + outputFile); From 0d8bd95c86ce51b0b71fc34bee2f92d681978638 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Mon, 4 Apr 2011 23:56:30 -0400 Subject: [PATCH 08/13] Fixed: systems without xcodebuild would fail jake clean and jake clobber. --- Tools/fontinfo/Jakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/fontinfo/Jakefile b/Tools/fontinfo/Jakefile index de6d54f40..4ab6d6c6e 100644 --- a/Tools/fontinfo/Jakefile +++ b/Tools/fontinfo/Jakefile @@ -27,13 +27,13 @@ task ("build", function() task ("clean", function() { - if (OS.system("xcodebuild clean")) + if (executableExists("xcodebuild") && OS.system("xcodebuild clean")) OS.exit(1); }); task ("clobber", function() { - if (OS.system("xcodebuild clean")) + if (executableExists("xcodebuild") && OS.system("xcodebuild clean")) OS.exit(1); }); From af6d38fd16d43f3319c7e0ec83d99bd16b80e14e Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Tue, 5 Apr 2011 16:57:16 -0400 Subject: [PATCH 09/13] Fixes and enhancements - When a xib/nib is deleted, the corresponding cib is deleted. - Fixed the calculation of the output path when given a relative path. - Works with .nib files now. - Works with -R option now. NSCustomResource was assuming an image path would always be relative to cwd(). - The Resources directory is always inferred from the input path, so the -R option is usually unnecessary. - Added --default-theme option. - Multiple extra themes can be specified with the --theme option. - Verbose mode displays the inferred application and resources paths, and the source theme used when doing font substitutions. - Added a man page. - Themes are read using StaticResource instead of parsing the file myself. - Error message tweaks. --- Tools/nib2cib/Converter+Mac.j | 12 +- Tools/nib2cib/Converter.j | 6 +- Tools/nib2cib/Jakefile | 2 + Tools/nib2cib/NSCustomResource.j | 4 +- Tools/nib2cib/main.j | 401 +++++++++++++++++++++---------- Tools/nib2cib/nib2cib.1 | 219 +++++++++++++++++ 6 files changed, 507 insertions(+), 137 deletions(-) create mode 100644 Tools/nib2cib/nib2cib.1 diff --git a/Tools/nib2cib/Converter+Mac.j b/Tools/nib2cib/Converter+Mac.j index 581f12686..41b1b22fc 100644 --- a/Tools/nib2cib/Converter+Mac.j +++ b/Tools/nib2cib/Converter+Mac.j @@ -132,7 +132,16 @@ { var bold = [nibFont isBold]; - cibFont = [theme valueForAttributeWithName:@"font" inState:[object themeState] forClass:[object class]]; + for (var i = 0; i < themes.length; ++i) + { + cibFont = [themes[i] valueForAttributeWithName:@"font" inState:[object themeState] forClass:[object class]]; + + if (cibFont) + { + source = " (from " + [themes[i] name] + ")"; + break; + } + } // Substitute legacy theme fonts for the current system font if (!cibFont || [cibFont familyName] === CPFontDefaultSystemFontFace) @@ -144,7 +153,6 @@ size = [CPFont systemFontSize]; cibFont = bold ? [CPFont boldSystemFontOfSize:size] : [CPFont systemFontOfSize:size]; - source = " (from theme)" } } diff --git a/Tools/nib2cib/Converter.j b/Tools/nib2cib/Converter.j index f8f747e38..be2a1b346 100644 --- a/Tools/nib2cib/Converter.j +++ b/Tools/nib2cib/Converter.j @@ -44,7 +44,7 @@ ConverterConversionException = @"ConverterConversionException"; CPString outputPath @accessors; CPString resourcesPath @accessors; NibFormat format @accessors(readonly); - CPTheme theme @accessors(readonly); + CPArray themes @accessors(readonly); } + (Converter)sharedConverter @@ -55,7 +55,7 @@ ConverterConversionException = @"ConverterConversionException"; return SharedConverter; } -- (id)initWithInputPath:(CPString)aPath format:(NibFormat)nibFormat theme:(CPTheme)aTheme +- (id)initWithInputPath:(CPString)aPath format:(NibFormat)nibFormat themes:(CPArray)themeList { self = [super init]; @@ -63,7 +63,7 @@ ConverterConversionException = @"ConverterConversionException"; { inputPath = aPath; format = nibFormat; - theme = aTheme; + themes = themeList; } return self; diff --git a/Tools/nib2cib/Jakefile b/Tools/nib2cib/Jakefile index 64f08edd7..18f830a3b 100644 --- a/Tools/nib2cib/Jakefile +++ b/Tools/nib2cib/Jakefile @@ -36,6 +36,8 @@ filedir($BUILD_CJS_NIB2CIB, ["nib2cib"], function() make_objj_executable($BUILD_CJS_NIB2CIB); }); +copyManPage("nib2cib", 1); + task ("build", ["nib2cib", $BUILD_CJS_NIB2CIB]); CLOBBER.include($BUILD_CJS_NIB2CIB); diff --git a/Tools/nib2cib/NSCustomResource.j b/Tools/nib2cib/NSCustomResource.j index 46afc017c..5656f2fbd 100644 --- a/Tools/nib2cib/NSCustomResource.j +++ b/Tools/nib2cib/NSCustomResource.j @@ -47,9 +47,9 @@ var FILE = require("file"); var resourcePath = [aCoder resourcePathForName:_resourceName]; if (!resourcePath) - CPLog.warn("Resource named " + _resourceName + " not found in the supplied resources path."); + CPLog.warn("Resource \"" + _resourceName + "\" not found in the resources path: " + [aCoder resourcesPath]); else - size = imageSize(FILE.join(FILE.cwd(), resourcePath)); + size = imageSize(FILE.canonical(resourcePath)); // Account for the fact that an extension may have been inferred. if (resourcePath && FILE.extension(resourcePath) !== FILE.extension(_resourceName)) diff --git a/Tools/nib2cib/main.j b/Tools/nib2cib/main.j index 7cfc62d40..4796675b7 100644 --- a/Tools/nib2cib/main.j +++ b/Tools/nib2cib/main.j @@ -35,13 +35,16 @@ var FILE = require("file"), SYS = require("system"), FileList = require("jake").FileList, stream = require("narwhal/term").stream, + StaticResource = require("objective-j").StaticResource, DefaultTheme = "Aristo", BuildTypes = ["Debug", "Release"], - DefaultXibFile = "MainMenu.xib"; + DefaultFile = "MainMenu"; var parser = new (require("narwhal/args").Parser)(), - nibInfo = {}; + nibInfo = {}, + appDirectory = "", + resourcesDirectory = ""; function main(args) @@ -68,50 +71,52 @@ function convert(options, inputFile) { inputFile = inputFile || getInputFile(options.args); + getAppAndResourceDirectoriesFromInputFile(inputFile, options); + var outputFile = getOutputFile(inputFile, options.args), - resourcesPath = ""; + configInfo = readConfigFile(options.configFile || "", inputFile), + defaultTheme = null; - if (options.resources) + if (configInfo.plist) { - resourcesPath = FILE.canonical(options.resources); + var systemFontFace = configInfo.plist.valueForKey("CPSystemFontFace"); - if (!FILE.isDirectory(resourcesPath) || !FILE.isReadable(resourcesPath)) - fail("Cannot read resources at: " + resourcesPath); + if (systemFontFace) + [CPFont setSystemFontFace:systemFontFace]; + + var systemFontSize = configInfo.plist.valueForKey("CPSystemFontSize"); + + if (systemFontSize) + [CPFont setSystemFontSize:parseFloat(systemFontSize, 10)]; + + if (!options.defaultTheme) + defaultTheme = configInfo.plist.valueForKey("CPDefaultTheme"); } - var configPath = setSystemFontAndSize(options.configFile || "", inputFile), - themeName = "", - themeDir = options.themeDir || ""; - - if (themeDir) - themeName = FILE.basename(themeDir, FILE.extension(themeDir)); - - themeName = themeName || getDefaultThemeName(); - - if (!themeName) - fail("Could not determine the theme name."); - - var theme = loadTheme(themeName, themeDir); + var themeList = getThemeList(defaultTheme || options.defaultTheme, options), + themes = loadThemes(themeList); CPLog.info("\n-------------------------------------------------------------"); - CPLog.info("Input : " + inputFile); - CPLog.info("Output : " + outputFile); - CPLog.info("Format : " + ["Auto", "Mac", "iPhone"][options.format]); - CPLog.info("Resources : " + resourcesPath); - CPLog.info("Frameworks : " + (options.frameworks || "")); - CPLog.info("Theme : " + themeName); - CPLog.info("Config file : " + (configPath || "")); - CPLog.info("System Font : " + [CPFont systemFontSize] + "px " + [CPFont systemFontFace]); + CPLog.info("Input : " + inputFile); + CPLog.info("Output : " + outputFile); + CPLog.info("Format : " + ["Auto", "Mac", "iPhone"][options.format]); + CPLog.info("Application : " + appDirectory); + CPLog.info("Resources : " + resourcesDirectory); + CPLog.info("Frameworks : " + (options.frameworks || "")); + CPLog.info("Default theme : " + themeList[0]); + CPLog.info("Extra 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 - theme:theme]; + themes:themes]; [converter setOutputPath:outputFile]; - [converter setResourcesPath:resourcesPath]; + [converter setResourcesPath:resourcesDirectory]; - loadFrameworks(options.frameworks, function() + loadFrameworks(options.frameworks, options.verbosity, function() { [converter convert]; }); @@ -131,9 +136,19 @@ function watch(options) directory = options.args[0]; if (!directory) - directory = FILE.isDirectory("Resources") ? "Resources" : "."; + directory = FILE.canonical(FILE.isDirectory("Resources") ? "Resources" : "."); + else + { + directory = FILE.canonical(directory); - directory = FILE.canonical(directory); + if (FILE.basename(directory) !== "Resources") + { + var path = FILE.join(directory, "Resources"); + + if (FILE.isDirectory(path)) + directory = path; + } + } if (!FILE.isDirectory(directory)) fail("Cannot find the directory: " + directory); @@ -185,25 +200,24 @@ function parseOptions(args) .set(true) .help("Ask nib2cib to watch a directory for changes"); - parser.option("-F", "framework", "frameworks") + 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") + .help("Specify a custom default theme which is not set in your Info.plist"); + + parser.option("-t", "--theme", "extraThemes") .push() - .help("Add a framework to load"); - - parser.option("-R", "resources") - .set() - .help("Set the Resources directory"); - - parser.option("--mac", "format") - .set(NibFormatMac) - .def(NibFormatUndetermined) - .help("Set format to Mac"); - - parser.option("-t", "--theme-dir", "themeDir") - .set() - .help("A .build directory to use for theme attribute values"); + .displayName("name") + .help("An additional theme loaded dynamically by your application"); parser.option("--config", "configFile") .set() + .displayName("path") .help("A path to an Info.plist file from which the system font and/or size can be retrieved"); // parser.option("--iphone", "format") @@ -218,6 +232,15 @@ function parseOptions(args) .set(true) .help("No output"); + parser.option("-F", "framework", "frameworks") + .push() + .help("Add a framework to load"); + + parser.option("--mac", "format") + .set(NibFormatMac) + .def(NibFormatUndetermined) + .help("Set format to Mac"); + parser.option("--version", "showVersion") .action(printVersionAndExit) .help("Show the version of nib2cib and quit"); @@ -254,21 +277,80 @@ function setLogLevel(level) function getInputFile(args) { - var inputFile = args[0] || DefaultXibFile; + var inputFile = args[0] || DefaultFile, + path = ""; - if (!/^.+\.xib$/.test(inputFile)) - inputFile += ".xib"; + if (!/^.+\.[nx]ib$/.test(inputFile)) + { + if (path = findInputFile(inputFile, ".xib")) + inputFile = path; + else if (path = findInputFile(inputFile, ".nib")) + inputFile = path; + else + fail("Cannot find the input file (.xib or .nib): " + FILE.canonical(inputFile)); + } + else if (path = findInputFile(inputFile)) + inputFile = path; + else + fail("Could not read the input file: " + FILE.canonical(inputFile)); - inputFile = FILE.canonical(inputFile); + return FILE.canonical(inputFile); +} - if (!FILE.exists(inputFile) && FILE.basename(FILE.dirname(inputFile)) !== "Resources") - if (FILE.isDirectory("Resources")) - inputFile = FILE.resolve(inputFile, FILE.join("Resources", FILE.basename(inputFile))); +function findInputFile(inputFile, extension) +{ + var path = inputFile; - if (!FILE.isReadable(inputFile)) - fail("Cannot read the input file: " + inputFile); + if (extension) + path += extension; - return inputFile; + if (FILE.isReadable(path)) + return path; + + if (FILE.basename(FILE.dirname(inputFile)) !== "Resources" && FILE.isDirectory("Resources")) + { + path = FILE.resolve(path, FILE.join("Resources", FILE.basename(path))); + + if (FILE.isReadable(path)) + return path; + } + + return null; +} + +function getAppAndResourceDirectoriesFromInputFile(inputFile, options) +{ + appDirectory = resourcesDirectory = ""; + + if (options.resourcesDir) + { + var path = FILE.canonical(options.resourcesDir); + + if (!FILE.isDirectory(path)) + fail("Cannot read resources at: " + path); + + resourcesDirectory = path; + } + + var parentDir = FILE.dirname(inputFile); + + if (FILE.basename(parentDir) === "Resources") + { + appDirectory = FILE.dirname(parentDir); + resourcesDirectory = resourcesDirectory || parentDir; + } + else + { + appDirectory = parentDir; + + if (!resourcesDirectory) + { + var path = FILE.join(appDirectory, "Resources"); + + if (FILE.isDirectory(path)) + resourcesDirectory = path; + } + } } function getOutputFile(inputFile, args) @@ -276,14 +358,16 @@ function getOutputFile(inputFile, args) var outputFile = null; if (args.length > 1) + { outputFile = args[1]; + + if (!/^.+\.cib$/.test(outputFile)) + outputFile += ".cib"; + } else - outputFile = FILE.basename(inputFile, FILE.extension(inputFile)); + outputFile = FILE.join(FILE.dirname(inputFile), FILE.basename(inputFile, FILE.extension(inputFile))) + ".cib"; - if (!/^.+\.cib$/.test(outputFile)) - outputFile += ".cib"; - - outputFile = FILE.absolute(outputFile); + outputFile = FILE.canonical(outputFile); if (!FILE.isWritable(FILE.dirname(outputFile))) fail("Cannot write the output file at: " + outputFile); @@ -291,18 +375,30 @@ function getOutputFile(inputFile, args) return outputFile; } -function loadFrameworks(frameworkPaths, aCallback) +function loadFrameworks(frameworkPaths, verbosity, aCallback) { if (!frameworkPaths || frameworkPaths.length === 0) return aCallback(); frameworkPaths.forEach(function(aFrameworkPath) { + setLogLevel(verbosity); CPLog.info("Loading " + aFrameworkPath); - var frameworkBundle = [[CPBundle alloc] initWithPath:aFrameworkPath]; + try + { + // CPBundle is a bit loquacious with logging, we will defer + // logging its exceptions till later. + setLogLevel(-1); - [frameworkBundle loadWithDelegate:nil]; + var frameworkBundle = [[CPBundle alloc] initWithPath:aFrameworkPath]; + + [frameworkBundle loadWithDelegate:nil]; + } + finally + { + setLogLevel(verbosity); + } require("browser/timeout").serviceTimeouts(); }); @@ -318,6 +414,18 @@ function logFormatter(aString, aLevel, aTitle) return CPLogColorize(aString, aLevel); } +function getThemeList(defaultTheme, options) +{ + var themes = [defaultTheme || getDefaultThemeName()]; + + if (options.extraThemes) + for (var i = 0; i < options.extraThemes.length; ++i) + if (themes.indexOf(options.extraThemes[i]) < 0) + themes.push(options.extraThemes[i]); + + return themes; +} + function getDefaultThemeName() { var themeName = nil, @@ -328,6 +436,7 @@ function getDefaultThemeName() for (var i = 0; i < BuildTypes.length; ++i) { var path = FILE.join(cappBuild, BuildTypes[i], "AppKit", "Info.plist"); + themeName = themeNameFromPropertyList(path); if (themeName) @@ -352,11 +461,36 @@ function themeNameFromPropertyList(path) return themeName; } +function loadThemes(themeList) +{ + var themes = []; + + for (var i = 0; i < themeList.length; ++i) + themes.push(loadTheme(themeList[i], resourcesDirectory)); + + return themes; +} + function loadTheme(themeName, themeDir) { + if (/^.+\.blend$/.test(themeName)) + themeName = themeName.substr(0, themeName.length - ".blend".length); + + var blendName = themeName + ".blend", + themePath = ""; + + if (themeDir) + { + themePath = FILE.join(FILE.canonical(themeDir), blendName); + + if (!FILE.isDirectory(themePath)) + themePath = themeDir = null; + } + if (!themeDir) { - cappBuild = SYS.env["CAPP_BUILD"]; + // Try in $CAPP_BUILD + cappBuild = FILE.canonical(SYS.env["CAPP_BUILD"]); if (!cappBuild) fail("$CAPP_BUILD is not set, exiting."); @@ -364,69 +498,76 @@ function loadTheme(themeName, themeDir) if (!FILE.isDirectory(cappBuild)) fail("$CAPP_BUILD does not exist: " + cappBuild) - var baseThemeName = themeName, - pos = themeName.indexOf("-"); - - if (pos > 0) - baseThemeName = themeName.substr(0, pos); - - themeDir = FILE.join(cappBuild, baseThemeName + ".build"); - } - - themeDir = FILE.canonical(themeDir); - - if (!FILE.isDirectory(themeDir)) - fail("Cannot find the theme directory: " + themeDir); - - var themePath = null; - - for (var i = 0; i < BuildTypes.length; ++i) - { - var path = FILE.join(themeDir, BuildTypes[i], "Browser.environment/Resources", themeName + ".keyedtheme"); - - if (FILE.isReadable(path)) + for (var i = 0; i < BuildTypes.length; ++i) { - themePath = path; - break; + var path = FILE.join(cappBuild, BuildTypes[i], blendName); + + if (FILE.isDirectory(path)) + { + themePath = path; + break; + } + } + + // Last resort, try the cwd + if (!themePath) + { + var path = FILE.canonical(blendName); + + if (FILE.isDirectory(path)) + themePath = path; } } if (!themePath) - fail("Could not find the keyed theme data for \"" + themeName + "\" in the directory: " + themeDir); + fail("Cannot find the theme \"" + themeName + "\""); - themePath = FILE.canonical(themePath); - var plist = FILE.read(themePath); + return readTheme(themeName, themePath); +} - if (!plist) - fail("Could not read the keyed theme at: " + themePath); +function readTheme(name, path) +{ + var themeBundle = new CFBundle(path); - // The .keyedtheme file has a header that is data I don't need. Strip it off. - var m = plist.match(/^t;\d+;/); + // By default when we try to load the bundle it will use the CommonJS environment, + // but we want the Browser environment. So we override mostEligibleEnvironment(). + themeBundle.mostEligibleEnvironment = function() { return "Browser"; } + themeBundle.load(); - if (!m || m.length === 0) - fail("Invalid keyed theme data at: " + themePath); + var keyedThemes = themeBundle.valueForInfoDictionaryKey("CPKeyedThemes"); - plist = plist.substr(m[0].length); - plist = CFPropertyList.propertyListFromString(plist); + if (!keyedThemes) + fail("Could not find the keyed themes in the theme: " + path); - var data = [CPData dataWithPlistObject:plist], - theme = [CPKeyedUnarchiver unarchiveObjectWithData:data]; + var index = keyedThemes.indexOf(name + ".keyedtheme"); + + if (index < 0) + fail("Could not find the main theme data (" + name + ".keyedtheme" + ") in the theme: " + path); + + // Load the keyed theme data, making sure to resolve it + var resourcePath = themeBundle.pathForResource(keyedThemes[index]), + themeData = new CFMutableData(); + + themeData.setRawString(StaticResource.resourceAtURL(new CFURL(resourcePath), true).contents()); + + var theme = [CPKeyedUnarchiver unarchiveObjectWithData:themeData]; if (!theme) - fail("Could not unarchive the theme at: " + themePath); + fail("Could not unarchive the theme at: " + path); - CPLog.debug("Loaded theme: " + themePath); + CPLog.debug("Loaded theme: " + path); return theme; } -function setSystemFontAndSize(configFile, inputFile) +function readConfigFile(configFile, inputFile) { - var configPath = null; + var configPath = null, + path; // First see if the user passed a config file path if (configFile) { - var path = FILE.canonical(configFile); + path = FILE.canonical(configFile); if (!FILE.isReadable(path)) fail("Cannot find the config file: " + path); @@ -435,19 +576,14 @@ function setSystemFontAndSize(configFile, inputFile) } else { - // See if we can find an Info.plist in the parent directory of the input file, - // if the input file's directory is "Resources". - var path = FILE.canonical(FILE.dirname(inputFile)); + path = FILE.join(appDirectory, "Info.plist"); - if (FILE.basename(path) === "Resources") - { - path = FILE.join(FILE.dirname(path), "Info.plist"); - - if (FILE.isReadable(path)) - configPath = path; - } + if (FILE.isReadable(path)) + configPath = path; } + var plist = null; + if (configPath) { var plist = FILE.read(configPath); @@ -459,24 +595,14 @@ function setSystemFontAndSize(configFile, inputFile) if (!plist) fail("Could not parse the Info.plist at: " + configPath); - - var systemFontFace = plist.valueForKey("CPSystemFontFace"); - - if (systemFontFace) - [CPFont setSystemFontFace:systemFontFace]; - - var systemFontSize = plist.valueForKey("CPSystemFontSize"); - - if (systemFontSize) - [CPFont setSystemFontSize:parseFloat(systemFontSize, 10)]; } - return configPath; + return {path: configPath, plist: plist}; } function getModifiedNibs(path) { - var nibs = new FileList(FILE.join(path, "*.xib")).items(), + var nibs = new FileList(FILE.join(path, "*.[nx]ib")).items(), count = nibs.length, newNibInfo = {}, modifiedNibs = []; @@ -503,7 +629,22 @@ function getModifiedNibs(path) for (var nib in nibInfo) { if (nibInfo.hasOwnProperty(nib)) + { CPLog.info(">> %s %s", CPLogColorize("Deleted:", "warn"), nib); + + var cib = nib.substr(0, nib.length - 3) + "cib"; + + if (FILE.exists(cib)) + { + if (FILE.isWritable(cib)) + { + FILE.remove(cib); + CPLog.warn("Deleted: " + cib); + } + else + CPLog.info("%s could not remove the file: %s", CPLogColorize("Warning:", "warn"), cib); + } + } } nibInfo = newNibInfo; diff --git a/Tools/nib2cib/nib2cib.1 b/Tools/nib2cib/nib2cib.1 new file mode 100644 index 000000000..3c541435b --- /dev/null +++ b/Tools/nib2cib/nib2cib.1 @@ -0,0 +1,219 @@ +.Dd April 3, 2011 +.Os "Cappuccino" +.Dt NIB2CIB 1 "PRM" +.\"----------------------------------------------------------------------------------------- +.Sh NAME +.\"----------------------------------------------------------------------------------------- +.Nm nib2cib +.Nd convert Interface Builder files to Cappuccino cib files +.\"----------------------------------------------------------------------------------------- +.Sh SYNOPSIS +.\"----------------------------------------------------------------------------------------- +.Nm +--watch +.Op options +.Op Pa directory +.Nm +.Op options +.Op Pa input Op Pa output +.Nm +.Op --version | -h | --help +.\"----------------------------------------------------------------------------------------- +.Sh "DESCRIPTION" +.\"----------------------------------------------------------------------------------------- +.Nm +converts an Interface Builder file into a Cappuccino cib file that can be loaded +into a Cappuccino application. +.Pp +There are two main converting modes in which nib2cib can be used: +.Sy watch mode +and +.Sy single mode. +.\"----------------------------------------------------------------------------------------- +.Ss "Watch Mode" +.\"----------------------------------------------------------------------------------------- +The easiest way to use nib2cib is in +.Sy watch mode. +This is done by using the syntax: +.Pp +.D1 nib2cib --watch [options] Op Pa directory +.Pp +If +.Ar directory +is omitted, a "Resources" directory in the current working directory is watched, +and if no "Resources" directory exists, the current working directory is watched. +.Pp +If +.Ar directory +is passed, is not a path to a "Resources" directory, and there is a "Resources" directory +within +.Ar directory , +then that "Resources" directory is watched. Otherwise +.Ar directory +is watched. This allows you to pass the path to your application directory instead +of its "Resources" directory. +.Pp +In watch mode, +.Nm +goes into an infinite loop and monitors the watched directory for changes to xib/nib +files. There are three types of changes +.Nm +will respond to: +.Bl -tag -hang +.It add, modify +When a new xib/nib file is detected or an existing xib/nib file is modified, it is converted into +a cib file with the same base name. +.It delete +When a xib/nib file is deleted, the corresponding cib file is deleted as well. +.El +.Pp +Ordinarily in watch mode +.Nm +will only display a log line for each file that is added, modified or deleted, and a confirmation +when the corresponding cib has been generated or deleted. You can use the +.Fl v +option in watch mode to display more detailed information when a cib is generated. +.\"----------------------------------------------------------------------------------------- +.Ss "Single Mode" +.\"----------------------------------------------------------------------------------------- +In +.Sy single mode, +you convert a single file xib/nib file into a cib. The only advantages of using single mode over +watch mode are: +.Bl -bullet -width 1n +.It +You may want to view verbose output temporarily to debug the conversion, or use different +options for different source files. +.It +You can specify an output name that is not based on the input name. +.El +.Pp +Most of the time watch mode will be sufficient, but if you do need to use single mode, +.Nm +tries to make it as easy as possible. +.Pp +If you navigate to your application directory and invoke: +.Pp +.D1 nib2cib +.Pp +this will look for "MainMenu.xib" or "MainMenu.nib" in the current directory and then in a "Resources" +directory within the current directory, if such a directory exists. Since these are +the default names used in Cappuccino, this usage covers the default case with no extra work. +.Pp +If your xib/nib file is not named "MainMenu.xib" or "MainMenu.nib", or you want to run +.Nm +from a directory other than the application directory or its "Resources" directory, +you can specify an input path: +.Pp +.D1 nib2cib Pa path +.Pp +Note that +.Pa path +does not need the ".xib" or ".nib" extension, since that is assumed. If you were to invoke: +.Pp +.D1 nib2cib foo +.Pp +this would look for "foo.xib" and "foo.nib" in the current directory and in a "Resources" directory +within the current directory. The output of nib2cib in this case would be "foo.cib" in +the same directory as the source file. +.Pp +If you want +.Nm +to generate a cib file with a different base name or in a different directory than +the input file, you may also specify an output path: +.Pp +.D1 nib2cib Pa input Pa output +.Pp +As with input filenames, you do not need to add the ".cib" extension to the output path, +this is assumed. +.\"----------------------------------------------------------------------------------------- +.Ss "Themes" +.\"----------------------------------------------------------------------------------------- +To do its conversions accurately, +.Nm +needs to access the themes you use. If you are using the standard Aristo theme, there is +nothing extra to do. +.Pp +If you are using a custom default theme, usually you specify this using a +.Ar CPDefaultTheme +item in your application's Info.plist. In this case +.Nm +will read the theme name from the Info.plist. If you set the default theme programmatically +and not in the Info.plist, then you should use the +.Fl \-default-theme +option to specify the theme name. +.Pp +If you are loading additional themes in your code using: +.Pp +.D1 [[CPThemeBlend alloc] initWithContentsOfURL:] +.Pp +then you should inform +.Nm +about it by passing the theme names as +.Fl \-theme Ar name +options, one for each theme that you load. +.Pp +Themes are searched for in the Resources, $CAPP_BUILD/Debug and +$CAPP_BUILD/Release directories. +.\"----------------------------------------------------------------------------------------- +.Sh "COMMON OPTIONS" +.\"----------------------------------------------------------------------------------------- +The following options are available in watch mode or single mode: +.Bl -tag -width 4n +.It Fl v, \-verbose +Displays more information about the internal workings of nib2cib. This can be set multiple +times to increase the amount of information displayed. If passed once, general information +is displayed. If passed more than once, detailed information about individual view conversions +is displayed. +.It Fl R Pa path +NOTE: This option is for the most part no longer necessary. +.Pp +Specifies the path (relative or absolute) to a directory from which images +and custom themes are retrieved. Formerly +it was necessary to use this when your xib/nib contained references to images, but now +.Nm +infers the "Resources" directory from the input file, so usually you will not need to use +this option. +.It Fl \-default-theme Ar name +Specifies the name of the default theme used by your application. This is only necessary +if you are not using Aristo and you have not specified the default theme in your application's +Info.plist. For more information see +.Sy Themes +above. +.It Fl t, \-theme Ar name +Specifies the name of an additional theme to load. May be used multiple times. For more +information, see +.Sy Themes +above. +.It Fl \-config Pa path +Specifies the path to an Info.plist file from which to read configuration information about +your application, such as the system font and default theme. Ordinarily you do not need to +use this option, as +.Nm +uses the Info.plist in the application directory, which is inferred from +the input file. +.It Fl \-quiet +Tells +.Nm +to output nothing. This is useful if you are using +.Nm +in a shell script and are only interested in the return value. +.It Fl \-version +Prints the current version of +.Nm +and immediately exits. +.Nm +in a shell script and are only interested in the return value. +.It Fl h, \-help +Displays +.Nm +usage and options. +.It Fl F +Specify the URL of a framework to load before converting. How this is useful is not actually +known at this time. +.El +.\"----------------------------------------------------------------------------------------- +.Sh "RETURN VALUES" +.\"----------------------------------------------------------------------------------------- +.Nm +returns 0 for a successful conversion and >0 if an error occurred. From 327d7966535efa1f6204fed2ba518f428d206439 Mon Sep 17 00:00:00 2001 From: Klaas Pieter Annema Date: Wed, 6 Apr 2011 08:14:29 +0200 Subject: [PATCH 10/13] implement outlineViewDeleteKeyPressed --- AppKit/CPOutlineView.j | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AppKit/CPOutlineView.j b/AppKit/CPOutlineView.j index 26b79fc00..e814c954c 100644 --- a/AppKit/CPOutlineView.j +++ b/AppKit/CPOutlineView.j @@ -952,6 +952,17 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, } +- (BOOL)_sendDelegateDeleteKeyPressed +{ + if ([[self delegate] respondsToSelector: @selector(outlineViewDeleteKeyPressed:)]) + { + [[self delegate] outlineViewDeleteKeyPressed:self]; + return YES; + } + + return NO; +} + /*! Returns the delegate object for the outlineview. */ From 73746e00b0de06faedd0f0705123d91a2ed358e2 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Wed, 6 Apr 2011 04:34:56 -0400 Subject: [PATCH 11/13] Fixes and enhancements... - If an IB file is deleted, the corresponding cib is no longer deleted. - In watch mode, the first time through no conversion is done if an IB file has a corresponding cib file with a later mtime. - In watch mode, an add does not convert if there is a corresponding cib file with a later mtime. - When catching exceptions, deal correctly with both CPException and strings. --- Tools/nib2cib/main.j | 86 ++++++++++++++++++++++++----------------- Tools/nib2cib/nib2cib.1 | 19 ++++----- 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/Tools/nib2cib/main.j b/Tools/nib2cib/main.j index 4796675b7..6de188ae6 100644 --- a/Tools/nib2cib/main.j +++ b/Tools/nib2cib/main.j @@ -60,7 +60,7 @@ function main(args) } catch (anException) { - CPLog.fatal([anException reason]); + CPLog.fatal(exceptionReason(anException)); OS.exit(1); } } @@ -125,7 +125,7 @@ function convert(options, inputFile) } catch (anException) { - CPLog.fatal([anException reason]); + CPLog.fatal(exceptionReason(anException)); return false; } } @@ -133,49 +133,72 @@ function convert(options, inputFile) function watch(options) { var verbosity = options.quiet ? -1 : options.verbosity, - directory = options.args[0]; + watchDir = options.args[0]; - if (!directory) - directory = FILE.canonical(FILE.isDirectory("Resources") ? "Resources" : "."); + if (!watchDir) + watchDir = FILE.canonical(FILE.isDirectory("Resources") ? "Resources" : "."); else { - directory = FILE.canonical(directory); + watchDir = FILE.canonical(watchDir); - if (FILE.basename(directory) !== "Resources") + if (FILE.basename(watchDir) !== "Resources") { - var path = FILE.join(directory, "Resources"); + var path = FILE.join(watchDir, "Resources"); if (FILE.isDirectory(path)) - directory = path; + watchDir = path; } } - if (!FILE.isDirectory(directory)) - fail("Cannot find the directory: " + directory); + if (!FILE.isDirectory(watchDir)) + fail("Cannot find the directory: " + watchDir); // Turn on info messages setLogLevel(1); - CPLog.info("Watching: " + CPLogColorize(directory, "debug")); + var nibs = new FileList(FILE.join(watchDir, "*.[nx]ib")).items(), + count = nibs.length; + + // First time through only IB files with no corresponding cib + // or a cib with an earlier mtime are converted. + while (count--) + { + var nib = nibs[count], + cib = nib.substr(0, nib.length - 4) + ".cib"; + + if (FILE.exists(cib) && (FILE.mtime(nib) - FILE.mtime(cib)) < 0) + nibInfo[nib] = FILE.mtime(nib); + } + + CPLog.info("Watching: " + CPLogColorize(watchDir, "debug")); CPLog.info("Press Control-C to stop..."); while (true) { - var modifiedNibs = getModifiedNibs(directory); + var modifiedNibs = getModifiedNibs(watchDir); for (var i = 0; i < modifiedNibs.length; ++i) { var action = modifiedNibs[i][0], - path = modifiedNibs[i][1], - label = action === "add" ? "Added:" : "Modified:", + nib = modifiedNibs[i][1], + label = action === "add" ? "Added" : "Modified", level = action === "add" ? "info" : "debug"; - CPLog.info(">> %s %s", CPLogColorize(label, level), path); + CPLog.info(">> %s: %s", CPLogColorize(label, level), nib); + + // Don't convert an add if there is an existing cib with a later mtime + if (action === "add") + { + var cib = nib.substr(0, nib.length - 4) + ".cib"; + + if (FILE.exists(cib) && (FILE.mtime(nib) - FILE.mtime(cib)) < 0) + continue; + } // Let the converter log however the user configured it setLogLevel(verbosity); - var success = convert(options, path); + var success = convert(options, nib); setLogLevel(1); @@ -627,25 +650,8 @@ function getModifiedNibs(path) } for (var nib in nibInfo) - { if (nibInfo.hasOwnProperty(nib)) - { - CPLog.info(">> %s %s", CPLogColorize("Deleted:", "warn"), nib); - - var cib = nib.substr(0, nib.length - 3) + "cib"; - - if (FILE.exists(cib)) - { - if (FILE.isWritable(cib)) - { - FILE.remove(cib); - CPLog.warn("Deleted: " + cib); - } - else - CPLog.info("%s could not remove the file: %s", CPLogColorize("Warning:", "warn"), cib); - } - } - } + CPLog.info(">> %s: %s", CPLogColorize("Deleted", "warn"), nib); nibInfo = newNibInfo; @@ -699,6 +705,16 @@ function printVersion() stream.print(""); } +function exceptionReason(exception) +{ + if (typeof(exception) === "string") + return exception; + else if (exception.isa && [exception respondsToSelector:@selector(reason)]) + return [exception reason]; + else + return "An unknown error occurred"; +} + function fail(message) { [CPException raise:ConverterConversionException reason:message]; diff --git a/Tools/nib2cib/nib2cib.1 b/Tools/nib2cib/nib2cib.1 index 3c541435b..4c44e44fb 100644 --- a/Tools/nib2cib/nib2cib.1 +++ b/Tools/nib2cib/nib2cib.1 @@ -62,7 +62,8 @@ will respond to: .Bl -tag -hang .It add, modify When a new xib/nib file is detected or an existing xib/nib file is modified, it is converted into -a cib file with the same base name. +a cib file with the same base name if there is no corresponding cib file with a later +modification time. .It delete When a xib/nib file is deleted, the corresponding cib file is deleted as well. .El @@ -129,14 +130,14 @@ this is assumed. .\"----------------------------------------------------------------------------------------- .Ss "Themes" .\"----------------------------------------------------------------------------------------- -To do its conversions accurately, +To do its conversions accurately, .Nm needs to access the themes you use. If you are using the standard Aristo theme, there is nothing extra to do. .Pp -If you are using a custom default theme, usually you specify this using a +If you are using a custom default theme, usually you specify this using a .Ar CPDefaultTheme -item in your application's Info.plist. In this case +item in your application's Info.plist. In this case .Nm will read the theme name from the Info.plist. If you set the default theme programmatically and not in the Info.plist, then you should use the @@ -149,7 +150,7 @@ If you are loading additional themes in your code using: .Pp then you should inform .Nm -about it by passing the theme names as +about it by passing the theme names as .Fl \-theme Ar name options, one for each theme that you load. .Pp @@ -168,7 +169,7 @@ is displayed. .It Fl R Pa path NOTE: This option is for the most part no longer necessary. .Pp -Specifies the path (relative or absolute) to a directory from which images +Specifies the path (relative or absolute) to a directory from which images and custom themes are retrieved. Formerly it was necessary to use this when your xib/nib contained references to images, but now .Nm @@ -193,9 +194,9 @@ use this option, as uses the Info.plist in the application directory, which is inferred from the input file. .It Fl \-quiet -Tells +Tells .Nm -to output nothing. This is useful if you are using +to output nothing. This is useful if you are using .Nm in a shell script and are only interested in the return value. .It Fl \-version @@ -205,7 +206,7 @@ and immediately exits. .Nm in a shell script and are only interested in the return value. .It Fl h, \-help -Displays +Displays .Nm usage and options. .It Fl F From a7161e3ca775d4f5e7c74f50dc03cc7214caa181 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Wed, 6 Apr 2011 04:40:05 -0400 Subject: [PATCH 12/13] Changed comparison in initial check to <=, a cib with an equal mtime could not have been converted from an IB file, it would have a later mtime. --- Tools/nib2cib/main.j | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/nib2cib/main.j b/Tools/nib2cib/main.j index 6de188ae6..4102d29ae 100644 --- a/Tools/nib2cib/main.j +++ b/Tools/nib2cib/main.j @@ -160,13 +160,13 @@ function watch(options) count = nibs.length; // First time through only IB files with no corresponding cib - // or a cib with an earlier mtime are converted. + // or a cib with an earlier or equal mtime are converted. while (count--) { var nib = nibs[count], cib = nib.substr(0, nib.length - 4) + ".cib"; - if (FILE.exists(cib) && (FILE.mtime(nib) - FILE.mtime(cib)) < 0) + if (FILE.exists(cib) && (FILE.mtime(nib) - FILE.mtime(cib)) <= 0) nibInfo[nib] = FILE.mtime(nib); } From 52ce4c1ebf4c85d4e93242e033970f9cc19873c1 Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Wed, 6 Apr 2011 05:15:52 -0400 Subject: [PATCH 13/13] Properly documented the -F option, some formatting tweaks --- Tools/nib2cib/nib2cib.1 | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Tools/nib2cib/nib2cib.1 b/Tools/nib2cib/nib2cib.1 index 4c44e44fb..44c5ec433 100644 --- a/Tools/nib2cib/nib2cib.1 +++ b/Tools/nib2cib/nib2cib.1 @@ -81,7 +81,7 @@ In .Sy single mode, you convert a single file xib/nib file into a cib. The only advantages of using single mode over watch mode are: -.Bl -bullet -width 1n +.Bl -dash -width 0n .It You may want to view verbose output temporarily to debug the conversion, or use different options for different source files. @@ -91,9 +91,7 @@ You can specify an output name that is not based on the input name. .Pp Most of the time watch mode will be sufficient, but if you do need to use single mode, .Nm -tries to make it as easy as possible. -.Pp -If you navigate to your application directory and invoke: +tries to make it as easy as possible. If you navigate to your application directory and invoke: .Pp .D1 nib2cib .Pp @@ -193,6 +191,18 @@ use this option, as .Nm uses the Info.plist in the application directory, which is inferred from the input file. +.It Fl F +Specify the path of a framework to load before converting. This is only useful if: +.Bl -dash -width 0n -hang +.It +Your IB files use classes from the framework. +.It +Those classes need to serialize/deserialize more data than their superclasses. +.It +Those classes implement encodeWithCoder: and initWithCoder:. +.El +.Pp +This option may be used multiple times to load multiple frameworks. .It Fl \-quiet Tells .Nm @@ -209,9 +219,6 @@ in a shell script and are only interested in the return value. Displays .Nm usage and options. -.It Fl F -Specify the URL of a framework to load before converting. How this is useful is not actually -known at this time. .El .\"----------------------------------------------------------------------------------------- .Sh "RETURN VALUES"