ResKnife/Cocoa/Classes/RKEditorRegistry.m

209 lines
8.4 KiB
Objective-C

/* =============================================================================
PROJECT: ResKnife
FILE: RKEditorRegistry.m
PURPOSE:
This is a registry where all our resource-editor plugins are looked
up and entered in a list, so you can ask for the editor for a specific
resource type and it is returned immediately. This registry reads the
types a plugin handles from their info.plist. This is better than
encoding the type in the plugin file name, as file names are not
guaranteed to be on a case-sensitive file system on Mac, and this also
allows an editor to register for several resource types.
AUTHORS: M. Uli Kusterer, witness(at)zathras.de, (c) 2003.
REVISIONS:
2003-07-31 UK Created.
========================================================================== */
/* -----------------------------------------------------------------------------
Headers:
-------------------------------------------------------------------------- */
#import "RKEditorRegistry.h"
#import "RKSupportResourceRegistry.h"
#import "NSString-FSSpec.h" // for ResKnifeBoolExtensions (in wrong file)
/*!
@class RKEditorRegistry
@author Uli Kusterer
@created 2003-07-31
@description This is a registry where all our resource-editor plugins are looked
up and entered in a list, so you can ask for the editor for a specific
resource type and it is returned immediately. This registry reads the
types a plugin handles from their info.plist. This is better than
encoding the type in the plugin file name, as file names are not
guaranteed to be on a case-sensitive file system on Mac, and this also
allows an editor to register for several resource types.
*/
@implementation RKEditorRegistry
/*!
@method +defaultRegistry
@author Uli Kusterer
@created 2003-07-31
@updated 2003-10-28 NGS: Changed method name from +mainRegistry (so it more closly matchs +defaultCenter) and moved global var inside method, making it a static.
@description Returns the default plugin registry of this application, instantiating it first if there is none yet. As soon as this is instantiated, the plugins are loaded.
*/
+ (RKEditorRegistry *)defaultRegistry
{
static RKEditorRegistry *defaultRegistry = nil;
if(!defaultRegistry)
{
defaultRegistry = [[RKEditorRegistry alloc] init];
[defaultRegistry scanForPlugins:nil];
}
return defaultRegistry;
}
/*!
@method awakeFromNib
@abstract Makes sure that if an instance of this is instantiated from a nib file, it automatically loads the plugins.
@author Uli Kusterer
@created 2003-07-31
*/
- (void)awakeFromNib
{
[self scanForPlugins:nil];
}
/*!
@method scanForPlugins:
@abstract (Re)loads our list of plugins. You can use this as an action for a menu item, if you want.
@author Uli Kusterer
@created 2003-07-31
@updated 2003-10-28 NGS: Updated to look for more sophisticated RKSupportedTypes key in addition to (the now deprecated) RKEditedTypes.
@pending Use NSSearchPathForDirectoriesInDomains() or equivalent to get folder paths instead of hard coding them.
@pending Currently, Cocoa classes can't be unloaded, which means we're
not leaking the NSBundles we load here. However, if this one
day shouldn't hold true anymore, we'll want to retain these
bundles in our dictionary and do the principalClass thingie
in editorForType: instead, which allows us to get rid of the
class and its bundle when reloading the plugin list by simply
relying on NSMutableDictionary to release it.
@description This scans the application's internal Plugins folder,
<tt>~/Library/Application Support/ResKnife/Plugins/</tt> and
<tt>/Library/Application Support/ResKnife/Plugins/</tt> for
plugins that have the extension ".plugin" and implement
<tt>initWithResource:</tt> (which means this won't get into
the way if you want to support other kinds of plugins).</p>
<p>It builds a registry of Class objects in an NSMutableDictionary,
where the key is the resource type. If a plugin supports several
resource types (as indicated by the RKEditedTypes key in it's
Info.plist), it will be registered for these types as well.
If several plugins register for the same type, the last one
loaded wins.</p>
<p>To instantiate an object from a plugin, see <tt>editorForType:</tt>
*/
- (IBAction)scanForPlugins:(id)sender
{
NSString *appSupport = @"Library/Application Support/ResKnife/Plugins/";
NSString *appPath = [[NSBundle mainBundle] builtInPlugInsPath];
NSString *userPath = [NSHomeDirectory() stringByAppendingPathComponent:appSupport];
NSString *sysPath = [@"/" stringByAppendingPathComponent:appSupport];
// NSArray *paths = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSAllDomainsMask, YES);
NSEnumerator *pathEnumerator = [[NSArray arrayWithObjects:appPath, userPath, sysPath, nil] objectEnumerator];
NSString *path;
// release any existing registry to clear old values
if(typeRegistry) [typeRegistry release];
typeRegistry = [[NSMutableDictionary alloc] init];
// scan all paths
while(path = [pathEnumerator nextObject])
{
NSEnumerator *fileEnumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:path] objectEnumerator];
NSString *pluginName;
// enumerate all files in this directory
while(pluginName = [fileEnumerator nextObject])
{
NSString *pluginPath = [path stringByAppendingPathComponent:pluginName];
// NSLog(@"Examining %@", pluginPath);
// verify file is a plugin
if([[pluginName pathExtension] isEqualToString:@"plugin"])
{
NSBundle *plugin = [NSBundle bundleWithPath:pluginPath];
Class pluginClass = [plugin principalClass];
if(plugin && pluginClass)
{
// NSLog(@"Principal class %@ %@ to ResKnifePluginProtocol", NSStringFromClass(pluginClass), [pluginClass conformsToProtocol:@protocol(ResKnifePluginProtocol)]? @"conforms":@"does not conform");
// check principal class implements ResKnifePluginProtocol
if([pluginClass conformsToProtocol:@protocol(ResKnifePluginProtocol)])
{
NSArray *supportedTypes = [[plugin infoDictionary] objectForKey:@"RKSupportedTypes"];
if(supportedTypes)
{
NSDictionary *typeEntry;
NSEnumerator *typesEnumerator = [supportedTypes objectEnumerator];
// enumerate entries
while(typeEntry = [typesEnumerator nextObject])
{
// get values for type entry
NSString *name = [typeEntry objectForKey:@"RKTypeName"];
// NSString *role = [typeEntry objectForKey:@"RKTypeRole"];
// BOOL isDefault = [(NSString *)[typeEntry objectForKey:@"IsResKnifeDefaultForType"] boolValue];
// register them
[typeRegistry setObject:pluginClass forKey:name]; // bug: very primative, doesn't use extra data
// NSLog(@"Plug-in class %@ registered as %@%@ for type %@.", NSStringFromClass(pluginClass), isDefault? @"default ":@"", role, name);
}
}
else
{
// try the old way of looking up types
NSString *resType;
NSEnumerator *enny;
supportedTypes = [[plugin infoDictionary] objectForKey:@"RKEditedTypes"];
if(supportedTypes == nil)
supportedTypes = [NSArray arrayWithObject: [[plugin infoDictionary] objectForKey:@"RKEditedType"]];
for(enny = [supportedTypes objectEnumerator]; resType = [enny nextObject];)
{
[typeRegistry setObject:pluginClass forKey:resType];
// NSLog(@"Registered for type %@.",resType);
}
}
// load any support resources in the plug-in
[RKSupportResourceRegistry scanForSupportResourcesInFolder:[[plugin resourcePath] stringByAppendingPathComponent:@"Support Resources"]];
}
}
}
}
}
}
/* -----------------------------------------------------------------------------
editorForType:
Looks up the editor for the specified type in our registry of plugins
and returns the main class "object" that registered for this type, or
Nil if there is none registered for this type.
Note that the resource type is stored as an NSString, which means it
can be longer than four characters (Currently used for looking up the
Hexadecimal and Template editors, which are special in that they work
with any resource).
REVISIONS:
2003-07-31 UK Created.
-------------------------------------------------------------------------- */
- (Class)editorForType:(NSString *)typeStr
{
Class theClass = [typeRegistry objectForKey: typeStr];
if(!theClass) return Nil;
else return theClass;
}
@end