Plugin registry, unique ID determining in "create" sheet, export of resources etc.

This commit is contained in:
Uli Kusterer 2003-08-02 00:23:50 +02:00
parent 7abf1cbb73
commit 1b2cc9ce34
26 changed files with 868 additions and 147 deletions

View File

@ -27,6 +27,9 @@
- (void)awakeFromNib
{
NSTableColumn *tableColumn;
NSButtonCell *buttonCell;
// Part of my EvilPlanª to find out how many people use ResKnife and how often!
int launchCount = [[NSUserDefaults standardUserDefaults] integerForKey:@"LaunchCount"];
[[NSUserDefaults standardUserDefaults] setInteger:launchCount + 1 forKey:@"LaunchCount"];
@ -38,8 +41,8 @@
[icons setObject:[[NSWorkspace sharedWorkspace] iconForFileType:@"icns"] forKey:@"icns"];
// set up open dialog's aux table view
NSTableColumn *tableColumn = [forkTableView tableColumnWithIdentifier:@"parse"];
NSButtonCell *buttonCell = [[[NSButtonCell alloc] initTextCell:@""] autorelease];
tableColumn = [forkTableView tableColumnWithIdentifier:@"parse"];
buttonCell = [[[NSButtonCell alloc] initTextCell:@""] autorelease];
[buttonCell setEditable:YES];
[buttonCell setButtonType:NSSwitchButton];
[tableColumn setDataCell:buttonCell];
@ -170,10 +173,10 @@
/* Don't tell anyone I did this... */
- (void)setTreatsFilePackagesAsDirectories:(BOOL)flag
/*- (void)setTreatsFilePackagesAsDirectories:(BOOL)flag
{
#pragma unused( flag )
_spFlags.treatsFilePackagesAsDirectories = YES;
}
}*/
@end

View File

@ -5,32 +5,56 @@
@implementation CreateResourceSheetController
- (void)controlTextDidChange:(NSNotification *)notification
{
BOOL enableButton = NO, clash = NO;
NSString *type = [typeView stringValue];
NSNumber *resID = [NSNumber numberWithInt:[resIDView intValue]];
/* -----------------------------------------------------------------------------
controlTextDidChange:
Someone changed the control ID edit field. Check whether this is
a unique ID and appropriately enable the "create" button.
Check "notification" against being nil, which is how we call it when
we need to explicitly update the enabled state of the "create" button.
if( [type length] == 4 && [[resIDView stringValue] length] > 0 )
REVISIONS:
2003-08-01 UK Commented, changed to use data source's resourceOfType
instead of directly messing with the resource list's
enumerator Removed ID > 0 check -- negative IDs are
allowed as well.
-------------------------------------------------------------------------- */
-(void) controlTextDidChange: (NSNotification*)notification
{
BOOL enableButton = NO;
NSString *type = [typeView stringValue];
NSNumber *resID = [NSNumber numberWithInt:[resIDView intValue]];
if( [type length] == 4 )
{
// I could use +[Resource getResourceOfType:andID:inDocument:] != nil, but that would be much slower
Resource *resource;
NSEnumerator *enumerator = [[[document dataSource] resources] objectEnumerator];
while( resource = [enumerator nextObject] )
{
if( [type isEqualToString:[resource type]] && [resID isEqualToNumber:[resource resID]] )
clash = YES;
}
if( !clash ) enableButton = YES;
Resource *resource = [[document dataSource] resourceOfType:type andID:resID];
if( resource == nil ) // No resource with that type and ID yet?
enableButton = YES;
}
[createButton setEnabled:enableButton];
}
- (void)showCreateResourceSheet:(ResourceDocument *)sheetDoc
/* -----------------------------------------------------------------------------
showCreateResourceSheet:
Show our sheet and set it up before that.
REVISIONS:
2003-08-01 UK Commented, made it "fake" a popup selection so
type field and popup match. Made it suggest an unused
resource ID.
-------------------------------------------------------------------------- */
-(void) showCreateResourceSheet: (ResourceDocument*)sheetDoc
{
// bug: didEndSelector could be better employed than using the button's targets from interface builder
document = sheetDoc;
[NSApp beginSheet:[self window] modalForWindow:[document mainWindow] modalDelegate:self didEndSelector:NULL contextInfo:nil];
[resIDView setObjectValue: [[document dataSource] uniqueIDForType: [typeView stringValue]]];
[self typePopupSelection: typePopup]; // Puts current popup value in text field and updates state of "create" button.
}
- (IBAction)hideCreateResourceSheet:(id)sender
@ -55,10 +79,21 @@
[NSApp endSheet:[self window]];
}
- (IBAction)typePopupSelection:(id)sender
/* -----------------------------------------------------------------------------
typePopupSelection:
Someone chose an item from our "res type" popup menu. Update our
edit field to show that.
REVISIONS:
2003-08-01 UK Commented, made it update state of "create" button..
-------------------------------------------------------------------------- */
-(IBAction) typePopupSelection:(id)sender
{
[typeView setStringValue:[typePopup titleOfSelectedItem]];
[typeView selectText:sender];
[self controlTextDidChange: nil]; // Make sure "create" button is updated.
}
@end

View File

@ -35,6 +35,7 @@ enum Attributes
- (void)updateInfoWindow;
- (void)setMainWindow:(NSWindow *)mainWindow;
- (IBAction)attributesChanged:(id)sender;
- (IBAction)nameChanged: (id)sender;
- (void)resourceAttributesDidChange:(NSNotification *)notification;
- (void)documentInfoDidChange:(NSNotification *)notification;

View File

@ -40,8 +40,11 @@
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(documentInfoDidChange:) name:DocumentInfoDidChangeNotification object:nil];
}
- (void)updateInfoWindow
-(void) updateInfoWindow
{
[nameView setEditable:(selectedResource != nil)];
[nameView setDrawsBackground:(selectedResource != nil)];
if( selectedResource )
{
[[self window] setTitle:@"Resource Info"];
@ -71,8 +74,8 @@
[[self window] setTitle:@"Document Info"];
[iconView setImage:[NSImage imageNamed:@"Resource file"]];
[nameView setStringValue:[currentDocument fileName]? [[currentDocument fileName] lastPathComponent]:[currentDocument displayName]];
[[filePropertyForm cellAtIndex:0] setStringValue:@"hi:)"/*[currentDocument creator]*/];
[[filePropertyForm cellAtIndex:1] setStringValue:@"helo"/*[currentDocument type]*/];
[[filePropertyForm cellAtIndex:0] setStringValue:[currentDocument creator]];
[[filePropertyForm cellAtIndex:1] setStringValue:[currentDocument type]];
// [[filePropertyForm cellAtIndex:2] setObjectValue:[NSNumber numberWithUnsignedLongLong:dataLogicalSize]];
// [[filePropertyForm cellAtIndex:3] setObjectValue:[NSNumber numberWithUnsignedLongLong:rsrcLogicalSize]];
[[filePropertyForm cellAtIndex:2] setStringValue:[[NSNumber numberWithUnsignedLongLong:dataLogicalSize] description]];
@ -102,6 +105,8 @@
- (void)selectedResourceChanged:(NSNotification *)notification
{
if( ![[nameView stringValue] isEqualToString: [selectedResource name]] )
[self nameChanged:nameView];
selectedResource = [[notification object] selectedItem];
[self updateInfoWindow];
}
@ -119,8 +124,15 @@
[selectedResource setAttributes:[NSNumber numberWithShort:number]];
}
-(IBAction) nameChanged: (id)sender
{
[selectedResource setName: [nameView stringValue]];
}
- (void)resourceAttributesDidChange:(NSNotification *)notification;
{
if( ![[nameView stringValue] isEqualToString: [selectedResource name]] )
[self nameChanged:nameView];
[self updateInfoWindow];
}

View File

@ -3,8 +3,8 @@
#import <sys/attr.h>
struct directoryinfo {
ÊÊ unsigned long length;
ÊÊ u_int32_t dirid;
unsigned long length;
u_int32_t dirid;
};
struct dunnowhat
@ -25,8 +25,8 @@ struct dunnowhat
/* NSTableView data source protocol implementation */
- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
NSBrowser *browser = [(NSOpenPanel *)[tableView window] browser];
if( [[browser selectedCells] count] == 1 )
//NSBrowser *browser = [(NSOpenPanel *)[tableView window] browser];
//if( [[browser selectedCells] count] == 1 )
{
// only one file is selected, parse it for forks
/* const char *path = [[browser path] cString];
@ -45,7 +45,7 @@ struct dunnowhat
NSLog( @"%d", fileinfo.length );
NSLog( @"%d", fileinfo.dirid );
*/
struct attrlist alist;
/*struct attrlist alist;
struct directoryinfo dirinfo;
char *path = [[browser path] cString];
bzero(&alist, sizeof(alist));
@ -54,11 +54,11 @@ struct dunnowhat
int result = getattrlist(path, &alist, &dirinfo, sizeof(dirinfo), 0);
printf("result: %d; directory id: %lu; %s\n", result, dirinfo.dirid, path);
return 3;
return 3;*/
}
// multiple/no selected files, return nothing
else return 0;
/*else*/ return 0;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
@ -107,7 +107,7 @@ struct dunnowhat
- (NSBrowser *)browser
{
return _browser;
return nil; //return _browser;
}
@end

View File

@ -26,15 +26,19 @@
// create the resource & add it to the array
NSString *name = pbName;
NSString *type;
NSNumber *resID;
NSNumber *attributes;
NSData *data;
Resource *resource;
NS_DURING
type = [currentType substringToIndex:3];
NS_HANDLER
type = currentType;
NS_ENDHANDLER
NSNumber *resID = [NSNumber numberWithShort:0];
NSNumber *attributes = [NSNumber numberWithShort:0];
NSData *data = [pb dataForType:type];
Resource *resource = [Resource resourceOfType:type andID:resID withName:name andAttributes:attributes data:data];
resID = [NSNumber numberWithShort:0];
attributes = [NSNumber numberWithShort:0];
data = [pb dataForType:type];
resource = [Resource resourceOfType:type andID:resID withName:name andAttributes:attributes data:data];
[resources addObject:resource]; // array retains resource
}
[[self undoManager] enableUndoRegistration];

View File

@ -0,0 +1,44 @@
/* =============================================================================
PROJECT: ResKnife
FILE: RKEditorRegistry.h
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 <Cocoa/Cocoa.h>
#import "ResKnifePluginProtocol.h"
#import "ResKnifeResourceProtocol.h"
/* -----------------------------------------------------------------------------
Class interface:
-------------------------------------------------------------------------- */
@interface RKEditorRegistry : NSObject
{
NSMutableDictionary* typeRegistry; // Private. Use editorForType: to access this.
}
+(RKEditorRegistry*) mainRegistry; // There's usually only one object, and this returns or creates it.
-(IBAction) scanForPlugins: (id)sender; // Called automatically by mainRegistry.
-(Class) editorForType: (NSString*)typeStr; // You probably want to call this.
@end

View File

@ -0,0 +1,185 @@
/* =============================================================================
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"
/* -----------------------------------------------------------------------------
Globals:
-------------------------------------------------------------------------- */
RKEditorRegistry* gRKEditorRegistryMainRegistry = nil; // Access through +mainRegistry!
@implementation RKEditorRegistry
/* -----------------------------------------------------------------------------
mainRegistry:
Returns the main plugin registry of this application, instantiating
it first if there is none yet. As soon as this is instantiated, the
plugins are loaded.
REVISIONS:
2003-07-31 UK Created.
-------------------------------------------------------------------------- */
+(RKEditorRegistry*) mainRegistry
{
if( !gRKEditorRegistryMainRegistry )
{
gRKEditorRegistryMainRegistry = [[RKEditorRegistry alloc] init];
[gRKEditorRegistryMainRegistry scanForPlugins: gRKEditorRegistryMainRegistry];
}
return gRKEditorRegistryMainRegistry;
}
/* -----------------------------------------------------------------------------
awakeFromNib:
Makes sure that if an instance of this is instantiated from a NIB file,
it automatically loads the plugins.
REVISIONS:
2003-07-31 UK Created.
-------------------------------------------------------------------------- */
-(void) awakeFromNib
{
[self scanForPlugins: self];
}
/* -----------------------------------------------------------------------------
scanForPlugins:
(Re)loads our list of plugins. You can use this as an action for a menu
item, if you want.
This scans the application's internal Plugins folder,
~/Library/Application Support/ResKnife/Plugins/ and
/Library/Application Support/ResKnife/Plugins/ for plugins that have
the extension ".plugin" and implement initWithResource: (which means
this won't get into the way if you want to support other kinds of
plugins).
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 its Info.plist), it
will be registered for these types as well. If several plugins register
for the same type, the last one loaded wins.
To instantiate an object from a plugin, see the method below.
TODO:
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.
REVISIONS:
2003-07-31 UK Created.
-------------------------------------------------------------------------- */
-(IBAction) scanForPlugins: (id)sender
{
// TODO: Instead of hard-coding sysPath we should use some FindFolder-like API!
Class pluginClass;
NSString *appSupport = @"Library/Application Support/ResKnife/Plugins/";
NSString *appPath = [[NSBundle mainBundle] builtInPlugInsPath];
NSString *userPath = [NSHomeDirectory() stringByAppendingPathComponent:appSupport];
NSString *sysPath = [@"/" stringByAppendingPathComponent:appSupport];
NSArray *paths = [NSArray arrayWithObjects:appPath, userPath, sysPath, nil];
NSEnumerator *pathEnum = [paths objectEnumerator];
NSString *path;
if( typeRegistry )
[typeRegistry release];
typeRegistry = [[NSMutableDictionary alloc] init];
while( path = [pathEnum nextObject] )
{
NSEnumerator *e = [[[NSFileManager defaultManager] directoryContentsAtPath:path] objectEnumerator];
NSString *name;
NSLog(@"Looking for plugins at %@", path);
while( name = [e nextObject] )
{
name = [path stringByAppendingPathComponent:name];
NSLog(@"Examining %@", name);
if( [[name pathExtension] isEqualToString:@"plugin"] )
{
NSBundle *plugin = [NSBundle bundleWithPath: name];
NSLog(@"Identifier %@", [plugin bundleIdentifier]);
if( pluginClass = [plugin principalClass] )
{
NSLog(@"Found plugin: %@", name);
if( [pluginClass instancesRespondToSelector:@selector(initWithResource:)] )
{
NSString *resType;
NSArray *types = [[plugin infoDictionary] objectForKey:@"RKEditedTypes"];
NSEnumerator *enny;
if( types == nil )
types = [NSArray arrayWithObject: [[plugin infoDictionary] objectForKey:@"RKEditedType"]];
for( enny = [types objectEnumerator]; resType = [enny nextObject]; )
{
[typeRegistry setObject:pluginClass forKey:resType];
NSLog(@"Registered for type %@.",resType);
}
}
}
}
}
}
}
/* -----------------------------------------------------------------------------
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;
return theClass;
}
@end

View File

@ -16,6 +16,8 @@
// the actual data
NSData *data;
NSDocument *document; // Our owner.
}
// accessor methods not part of the protocol
@ -33,4 +35,7 @@
+ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue;
+ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue data:(NSData *)dataValue;
-(void) setDocument: (NSDocument*)doc;
-(NSDocument*) document; // Resource protocol guarantees this.
@end

View File

@ -39,6 +39,7 @@ NSString *RKResourcePboardType = @"RKResourcePboardType";
return self;
}
+ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue
{
Resource *resource = [[Resource allocWithZone:[self zone]] initWithType:typeValue andID:resIDValue];
@ -156,6 +157,19 @@ NSString *RKResourcePboardType = @"RKResourcePboardType";
[[NSNotificationCenter defaultCenter] postNotificationName:ResourceDidChangeNotification object:self];
}
-(void) setDocument: (NSDocument*)doc
{
document = doc; // It owns us, so don't retain, or we could get a closed loop!
}
-(NSDocument*) document
{
return document;
}
- (NSString *)representedFork
{
return representedFork;

View File

@ -19,8 +19,10 @@
- (NSNumber *)uniqueIDForType:(NSString *)type;
// accessors
- (Resource *)resourceOfType:(NSString *)type andID:(NSNumber *)resID;
- (Resource *)resourceOfType:(NSString *)type withName:(NSString *)name;
- (NSArray *)allResourcesOfType:(NSString *)type;
-(Resource*) resourceOfType: (NSString*)type andID: (NSNumber*)resID;
-(Resource*) resourceOfType: (NSString*)type withName: (NSString*)name;
-(NSArray*) allResourcesOfType: (NSString*)type;
-(NSArray*) allResourceIDsOfType: (NSString*)type;
@end

View File

@ -1,6 +1,32 @@
/* =============================================================================
PROJECT: ResKnife
FILE: ResourceDataSource.m
PURPOSE:
Dedicated data source for our resource list. Shares its list of
resources with ResourceDocument. I have no idea why Nick did this
split, though...
AUTHORS: Nick Shanks, nick(at)nickshanks.com, (c) ~2001.
M. Uli Kusterer, witness(at)zathras.de, (c) 2003.
REVISIONS:
2003-07-31 UK Added storing document pointer in resource, commented.
========================================================================== */
/* -----------------------------------------------------------------------------
Headers:
-------------------------------------------------------------------------- */
#import "ResourceDataSource.h"
#import "ResourceDocument.h"
#import "Resource.h"
#import <limits.h>
/* -----------------------------------------------------------------------------
Notification names:
-------------------------------------------------------------------------- */
NSString *DataSourceWillAddResourceNotification = @"DataSourceWillAddResourceNotification";
NSString *DataSourceDidAddResourceNotification = @"DataSourceDidAddResourceNotification";
@ -46,6 +72,7 @@ NSString *DataSourceDidRemoveResourceNotification = @"DataSourceDidRemoveResourc
// it seems very inefficient to reload the entire data source when just adding/removing one item
// for large resource files, the data source gets reloaded hundereds of times upon load
[resource setDocument:document];
[resources addObject:resource];
[outlineView reloadData];
@ -66,17 +93,21 @@ NSString *DataSourceDidRemoveResourceNotification = @"DataSourceDidRemoveResourc
[[document undoManager] registerUndoWithTarget:self selector:@selector(addResource:) object:resource]; // NB: I hope the undo manager retains the resource, because it just got deleted :) - undo action name set by calling function
}
- (void)resourceDidChange:(NSNotification *)notification
/* -----------------------------------------------------------------------------
resourceDidChange:
Notification from the Resource class that we're registered to. It's
sent whenever the resource is "touch"ed.
REVISIONS:
2003-08-01 UK Commented, made this "touch" the document as well.
-------------------------------------------------------------------------- */
-(void) resourceDidChange: (NSNotification*)notification
{
// reload the data for the changed resource
[outlineView reloadItem:[notification object]];
}
- (NSNumber *)uniqueIDForType:(NSString *)type
{
short resID = 128;
while( [self resourceOfType:type andID:[NSNumber numberWithShort:resID]] != nil ) resID++;
return [NSNumber numberWithShort:resID];
[document updateChangeCount: NSChangeDone]; // TODO: We shouldn't need to do this, Undo should take care of this, according to the docs, but somehow the document always forgets its changes.
}
/* Data source protocol implementation */
@ -153,4 +184,54 @@ NSString *DataSourceDidRemoveResourceNotification = @"DataSourceDidRemoveResourc
return [NSArray arrayWithArray:array];
}
/* -----------------------------------------------------------------------------
allResourceIDsOfType:
Returns an NSArray full of NSNumber* objects containing the IDs of all
resources of specified type. Used by uniqueIDForType:.
REVISIONS:
2003-08-01 UK Created based on allResourcesOfType:.
-------------------------------------------------------------------------- */
-(NSArray*) allResourceIDsOfType: (NSString*)type
{
Resource *resource;
NSMutableArray *array = [NSMutableArray array];
NSEnumerator *enumerator = [resources objectEnumerator];
while( resource = [enumerator nextObject] )
{
if( [[resource type] isEqualToString:type] )
[array addObject:[resource resID]];
}
return [NSArray arrayWithArray:array];
}
/* -----------------------------------------------------------------------------
uniqueIDForType:
Tries to return an unused resource ID for a new resource of specified
type. If all IDs are used up (can't really happen, because the resource
manager can't take more than 2727 resources per file without crashing,
but just in theory...), this will return 128 no matter whether it's
used or not.
REVISIONS:
2003-08-01 UK Created.
-------------------------------------------------------------------------- */
-(NSNumber*) uniqueIDForType: (NSString*)type
{
short theID = 128;
NSArray *array = [self allResourceIDsOfType: type];
if( [array count] <= USHRT_MAX )
{
while( [array containsObject: [NSNumber numberWithShort:theID]] )
theID++;
}
return [NSNumber numberWithShort: theID];
}
@end

View File

@ -29,6 +29,9 @@
- (IBAction)playSound:(id)sender;
- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)finished;
-(IBAction) exportResourceToFile: (id)sender;
-(IBAction) exportResourceToImageFile: (id)sender;
- (IBAction)copy:(id)sender;
- (IBAction)paste:(id)sender;
- (void)pasteResources:(NSArray *)pastedResources;

View File

@ -1,3 +1,25 @@
/* =============================================================================
PROJECT: ResKnife
FILE: ResourceDocument.m
PURPOSE:
This is a ResKnife document (I still write Resurrection occasionally,
sorry Nick...), it handles loading, display etc. of resources.
It uses a separate object as the data source for its outline view,
though, ResourceDataSource. I'd better ask Nick why.
AUTHORS: Nick Shanks, nick(at)nickshanks.com, (c) ~2001.
M. Uli Kusterer, witness(at)zathras.de, (c) 2003.
REVISIONS:
2003-07-31 UK Added support for plugin registry, commented.
========================================================================== */
/* -----------------------------------------------------------------------------
Headers:
-------------------------------------------------------------------------- */
#import "ResourceDocument.h"
#import "ResourceDataSource.h"
#import "ResourceNameCell.h"
@ -8,10 +30,17 @@
#import "NSOutlineView-SelectedItems.h"
#import "ResKnifePluginProtocol.h"
#import "RKEditorRegistry.h"
/* -----------------------------------------------------------------------------
Notification names:
-------------------------------------------------------------------------- */
NSString *DocumentInfoWillChangeNotification = @"DocumentInfoWillChangeNotification";
NSString *DocumentInfoDidChangeNotification = @"DocumentInfoDidChangeNotification";
extern NSString *RKResourcePboardType;
@implementation ResourceDocument
@ -19,9 +48,13 @@ extern NSString *RKResourcePboardType;
- (id)init
{
self = [super init];
if( !self )
return nil;
toolbarItems = [[NSMutableDictionary alloc] init];
resources = [[NSMutableArray alloc] init];
fork = nil;
creator = [@"ResK" retain];
type = [@"rsrc" retain];
return self;
}
@ -31,6 +64,8 @@ extern NSString *RKResourcePboardType;
if( fork ) DisposePtr( (Ptr) fork );
[resources release];
[toolbarItems release];
[type release];
[creator release];
[super dealloc];
}
@ -111,6 +146,16 @@ extern NSString *RKResourcePboardType;
else if( [item action] == @selector(openResourcesInTemplate:) ) return selectedRows > 0;
else if( [item action] == @selector(openResourcesWithOtherTemplate:) ) return selectedRows > 0;
else if( [item action] == @selector(openResourcesAsHex:) ) return selectedRows > 0;
else if( [item action] == @selector(exportResourceToImageFile:) )
{
Class edClass;
if( selectedRows < 1 )
return NO;
edClass = [[RKEditorRegistry mainRegistry] editorForType: [resource type]];
return [edClass respondsToSelector:@selector(imageForImageFileExport:)];
}
else if( [item action] == @selector(playSound:) ) return selectedRows == 1 && [[resource type] isEqualToString:@"snd "];
else if( [item action] == @selector(revertResourceToSaved:) ) return selectedRows == 1 && [resource isDirty];
else return [super validateMenuItem:item];
@ -126,6 +171,7 @@ static NSString *RKEditItemIdentifier = @"com.nickshanks.resknife.toolbar.edit"
static NSString *RKEditHexItemIdentifier = @"com.nickshanks.resknife.toolbar.edithex";
static NSString *RKSaveItemIdentifier = @"com.nickshanks.resknife.toolbar.save";
static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.showinfo";
static NSString *RKExportItemIdentifier = @"com.ulikusterer.resknife.toolbar.export";
- (void)setupToolbar:(NSWindowController *)windowController
{
@ -135,6 +181,7 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[toolbarItems removeAllObjects]; // just in case this method is called more than once per document (which it shouldn't be!)
item = [[NSToolbarItem alloc] initWithItemIdentifier:RKCreateItemIdentifier];
[item autorelease];
[item setLabel:NSLocalizedString(@"Create", nil)];
[item setPaletteLabel:NSLocalizedString(@"Create", nil)];
[item setToolTip:NSLocalizedString(@"Create New Resource", nil)];
@ -142,9 +189,9 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[item setTarget:self];
[item setAction:@selector(showCreateResourceSheet:)];
[toolbarItems setObject:item forKey:RKCreateItemIdentifier];
[item release];
item = [[NSToolbarItem alloc] initWithItemIdentifier:RKDeleteItemIdentifier];
[item autorelease];
[item setLabel:NSLocalizedString(@"Delete", nil)];
[item setPaletteLabel:NSLocalizedString(@"Delete", nil)];
[item setToolTip:NSLocalizedString(@"Delete Selected Resource", nil)];
@ -152,9 +199,9 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[item setTarget:self];
[item setAction:@selector(clear:)];
[toolbarItems setObject:item forKey:RKDeleteItemIdentifier];
[item release];
item = [[NSToolbarItem alloc] initWithItemIdentifier:RKEditItemIdentifier];
[item autorelease];
[item setLabel:NSLocalizedString(@"Edit", nil)];
[item setPaletteLabel:NSLocalizedString(@"Edit", nil)];
[item setToolTip:NSLocalizedString(@"Edit Resource In Default Editor", nil)];
@ -162,9 +209,9 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[item setTarget:self];
[item setAction:@selector(openResources:)];
[toolbarItems setObject:item forKey:RKEditItemIdentifier];
[item release];
item = [[NSToolbarItem alloc] initWithItemIdentifier:RKEditHexItemIdentifier];
[item autorelease];
[item setLabel:NSLocalizedString(@"Edit Hex", nil)];
[item setPaletteLabel:NSLocalizedString(@"Edit Hex", nil)];
[item setToolTip:NSLocalizedString(@"Edit Resource As Hexadecimal", nil)];
@ -172,9 +219,9 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[item setTarget:self];
[item setAction:@selector(openResourcesAsHex:)];
[toolbarItems setObject:item forKey:RKEditHexItemIdentifier];
[item release];
item = [[NSToolbarItem alloc] initWithItemIdentifier:RKSaveItemIdentifier];
[item autorelease];
[item setLabel:NSLocalizedString(@"Save", nil)];
[item setPaletteLabel:NSLocalizedString(@"Save", nil)];
[item setToolTip:[NSString stringWithFormat:NSLocalizedString(@"Save To %@ Fork", nil), !fork? NSLocalizedString(@"Data", nil) : NSLocalizedString(@"Resource", nil)]];
@ -182,9 +229,9 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[item setTarget:self];
[item setAction:@selector(saveDocument:)];
[toolbarItems setObject:item forKey:RKSaveItemIdentifier];
[item release];
item = [[NSToolbarItem alloc] initWithItemIdentifier:RKShowInfoItemIdentifier];
[item autorelease];
[item setLabel:NSLocalizedString(@"Show Info", nil)];
[item setPaletteLabel:NSLocalizedString(@"Show Info", nil)];
[item setToolTip:NSLocalizedString(@"Show Resource Information Window", nil)];
@ -192,14 +239,23 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[item setTarget:[NSApp delegate]];
[item setAction:@selector(showInfo:)];
[toolbarItems setObject:item forKey:RKShowInfoItemIdentifier];
[item release];
item = [[NSToolbarItem alloc] initWithItemIdentifier:RKExportItemIdentifier];
[item autorelease];
[item setLabel:NSLocalizedString(@"Export Data", nil)];
[item setPaletteLabel:NSLocalizedString(@"Export Resource Data", nil)];
[item setToolTip:NSLocalizedString(@"Export the resource's data to a file", nil)];
[item setImage:[NSImage imageNamed:@"Export"]];
[item setTarget:self];
[item setAction:@selector(exportResourceToFile:)];
[toolbarItems setObject:item forKey:RKExportItemIdentifier];
if( [windowController window] == mainWindow )
{
NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier:RKToolbarIdentifier] autorelease];
// set toolbar properties
[toolbar setVisible:NO];
[toolbar setVisible:YES];
[toolbar setAutosavesConfiguration:YES];
[toolbar setAllowsUserCustomization:YES];
[toolbar setDisplayMode:NSToolbarDisplayModeDefault];
@ -217,12 +273,12 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:RKCreateItemIdentifier, RKEditItemIdentifier, RKEditHexItemIdentifier, NSToolbarSeparatorItemIdentifier, RKSaveItemIdentifier, NSToolbarPrintItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, nil];
return [NSArray arrayWithObjects:RKCreateItemIdentifier, RKShowInfoItemIdentifier, RKDeleteItemIdentifier, NSToolbarSeparatorItemIdentifier, RKEditItemIdentifier, RKEditHexItemIdentifier, NSToolbarSeparatorItemIdentifier, RKSaveItemIdentifier, NSToolbarPrintItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, nil];
}
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:RKCreateItemIdentifier, RKDeleteItemIdentifier, RKEditItemIdentifier, RKEditHexItemIdentifier, RKSaveItemIdentifier, RKShowInfoItemIdentifier, NSToolbarPrintItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier, nil];
return [NSArray arrayWithObjects:RKCreateItemIdentifier, RKDeleteItemIdentifier, RKEditItemIdentifier, RKEditHexItemIdentifier, RKSaveItemIdentifier, RKExportItemIdentifier, RKShowInfoItemIdentifier, NSToolbarPrintItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier, nil];
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)item
@ -235,6 +291,7 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
else if( [identifier isEqualToString:RKDeleteItemIdentifier] ) valid = selectedRows > 0;
else if( [identifier isEqualToString:RKEditItemIdentifier] ) valid = selectedRows > 0;
else if( [identifier isEqualToString:RKEditHexItemIdentifier] ) valid = selectedRows > 0;
else if( [identifier isEqualToString:RKExportItemIdentifier] ) valid = selectedRows > 0;
else if( [identifier isEqualToString:RKSaveItemIdentifier] ) valid = [self isDocumentEdited];
else if( [identifier isEqualToString:NSToolbarPrintItemIdentifier] ) valid = YES;
@ -286,18 +343,29 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[self openResourceAsHex:resource];
}
- (void)openResourceUsingEditor:(Resource *)resource
/* -----------------------------------------------------------------------------
openResourceUsingEditor:
Open an editor for the specified Resource instance. This looks up
the editor to use in the plugin registry and then instantiates an
editor object, handing it the resource. If there is no editor for this
type registered, it falls back to the template editor, which in turn
uses the hex editor as a fallback.
REVISIONS:
2003-07-31 UK Changed to use plugin registry instead of file name.
-------------------------------------------------------------------------- */
-(void) openResourceUsingEditor: (Resource*)resource
{
// #warning openResourceUsingEditor: shortcuts to NovaTools !!
// opens resource in template using TMPL resource with name templateName
// NSBundle *editor = [NSBundle bundleWithPath:[[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:@"NovaTools.plugin"]];
NSBundle *editor = [NSBundle bundleWithPath:[[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ Editor.plugin", [resource type]]]];
Class editorClass = [[RKEditorRegistry mainRegistry] editorForType: [resource type]];
// open the resources, passing in the template to use
if( editor /*&& [[editor principalClass] respondsToSelector:@selector(initWithResource:)]*/ )
if( editorClass )
{
// bug: I alloc a plug instance here, but have no idea where I should dealloc it, perhaps the plug ought to call [self autorelease] when it's last window is closed?
id plug = [(id <ResKnifePluginProtocol>)[[editor principalClass] alloc] initWithResource:resource];
// update: doug says window controllers automatically release themselves when their window is closed.
id plug = [(id <ResKnifePluginProtocol>)[editorClass alloc] initWithResource:resource];
if( plug ) return;
}
@ -305,21 +373,34 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[self openResource:resource usingTemplate:[resource type]];
}
- (void)openResource:(Resource *)resource usingTemplate:(NSString *)templateName
/* -----------------------------------------------------------------------------
openResource:usingTemplate:
Open a template editor for the specified Resource instance. This looks
up the template editor in the plugin registry and then instantiates an
editor object, handing it the resource and the template resource to use.
If there is no template editor registered, or there is no template for
this resource type, it falls back to the hex editor.
REVISIONS:
2003-07-31 UK Changed to use plugin registry instead of file name.
-------------------------------------------------------------------------- */
-(void) openResource: (Resource*)resource usingTemplate: (NSString*)templateName
{
// opens resource in template using TMPL resource with name templateName
NSBundle *templateEditor = [NSBundle bundleWithPath:[[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:@"Template Editor.plugin"]];
Class editorClass = [[RKEditorRegistry mainRegistry] editorForType: @"Template Editor"];
// bug: this checks EVERY DOCUMENT for template resources (might not be desired)
// bug: it doesn't, however, check the application's resource map for a matching template!
// TODO: this checks EVERY DOCUMENT for template resources (might not be desired)
// TODO: it doesn't, however, check the application's resource map for a matching template!
Resource *tmpl = [Resource resourceOfType:@"TMPL" withName:[resource type] inDocument:nil];
// open the resources, passing in the template to use
if( tmpl /*&& [[templateEditor principalClass] respondsToSelector:@selector(initWithResources:)]*/ )
if( tmpl && editorClass )
{
// bug: I alloc a plug instance here, but have no idea where I should dealloc it, perhaps the plug ought to call [self autorelease] when it's last window is closed?
// update: doug says window controllers automatically release themselves when their window is closed.
NSWindowController *plugController = [(id <ResKnifePluginProtocol>)[[templateEditor principalClass] alloc] initWithResources:resource, tmpl, nil];
NSWindowController *plugController = [(id <ResKnifeTemplatePluginProtocol>)[editorClass alloc] initWithResources:resource, tmpl, nil];
if( plugController ) return;
}
@ -327,13 +408,30 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[self openResourceAsHex:resource];
}
- (void)openResourceAsHex:(Resource *)resource
/* -----------------------------------------------------------------------------
openResourceAsHex:
Open a hex editor for the specified Resource instance. This looks
up the hexadecimal editor in the plugin registry and then instantiates an
editor object, handing it the resource.
REVISIONS:
2003-07-31 UK Changed to use plugin registry instead of file name.
-------------------------------------------------------------------------- */
-(void) openResourceAsHex: (Resource*)resource
{
NSBundle *hexEditor = [NSBundle bundleWithPath:[[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:@"Hexadecimal Editor.plugin"]];
Class editorClass = [[RKEditorRegistry mainRegistry] editorForType: @"Hexadecimal Editor"];
// bug: I alloc a plug instance here, but have no idea where I should dealloc it, perhaps the plug ought to call [self autorelease] when it's last window is closed?
NSWindowController *plugController = [(id <ResKnifePluginProtocol>)[[hexEditor principalClass] alloc] initWithResource:resource];
// update: doug says window controllers automatically release themselves when their window is closed.
NSWindowController *plugController = [(id <ResKnifePluginProtocol>)[editorClass alloc] initWithResource:resource];
}
// TODO: These two should really be moved to a 'snd ' editor, but first we'd
// need to extend the plugin protocol to call the class so it can add
// such menu items. Of course, we could just make the 'snd ' editor
// have a button in its window that plays the sound.
- (IBAction)playSound:(id)sender
{
// bug: can only cope with one selected item
@ -500,7 +598,7 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
if( [outlineView numberOfSelectedRows] > 1 )
[[self undoManager] setActionName:NSLocalizedString(@"Delete Resources", nil)];
// deselct resources (otherwise other resources move into selected rows!)
// deselect resources (otherwise other resources move into selected rows!)
[outlineView deselectAll:self];
}
@ -513,18 +611,39 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
return YES;
}*/
- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type
/* -----------------------------------------------------------------------------
readFromFile:ofType:
Open the specified file and read its resources. This first tries to
load the resources from the res fork, and failing that tries the data
fork.
REVISIONS:
2003-08-01 UK Commented.
-------------------------------------------------------------------------- */
-(BOOL) readFromFile: (NSString*)fileName ofType: (NSString*)fileKind
{
BOOL succeeded = NO;
OSStatus error = noErr;
HFSUniStr255 *resourceForkName = (HFSUniStr255 *) NewPtrClear( sizeof(HFSUniStr255) );
FSRef *fileRef = (FSRef *) NewPtrClear( sizeof(FSRef) );
SInt16 fileRefNum = 0;
BOOL succeeded = NO;
OSStatus error = noErr;
HFSUniStr255 *resourceForkName = (HFSUniStr255 *) NewPtrClear( sizeof(HFSUniStr255) ); // This may be saved away in the instance variable "fork" to keep track of which fork our resources are in.
FSRef *fileRef = (FSRef *) NewPtrClear( sizeof(FSRef) );
SInt16 fileRefNum = 0;
FSCatalogInfo info = { 0 };
// open fork with resources in it
error = FSPathMakeRef( [fileName cString], fileRef, nil );
error = FSGetResourceForkName( resourceForkName );
SetResLoad( false ); // don't load "preload" resources
[type release];
[creator release];
error = FSGetCatalogInfo( fileRef, kFSCatInfoFinderInfo, &info, nil, nil, nil );
type = [[NSString stringWithCString: &((FileInfo*)info.finderInfo)->fileType length:4] retain];
creator = [[NSString stringWithCString: &((FileInfo*)info.finderInfo)->fileCreator length:4] retain];
// Try res fork first:
error = FSOpenResourceFile( fileRef, resourceForkName->length, (UniChar *) &resourceForkName->unicode, fsRdPerm, &fileRefNum);
if( error ) // try to open data fork instead
{
@ -540,7 +659,7 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
// read the resources (without spawning thousands of undos for resource creation)
[[self undoManager] disableUndoRegistration];
if( fileRefNum && !error )
if( fileRefNum && error == noErr )
succeeded = [self readResourceMap:fileRefNum];
else if( !fileRefNum )
{
@ -551,7 +670,7 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[[self undoManager] enableUndoRegistration];
// tidy up loose ends
if( !fork ) DisposePtr( (Ptr) resourceForkName ); // only delete if we're not saving it
if( !fork ) DisposePtr( (Ptr) resourceForkName ); // only delete if we're not saving it to "fork" instance var.
if( fileRefNum ) FSClose( fileRefNum );
DisposePtr( (Ptr) fileRef );
return succeeded;
@ -570,31 +689,41 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
It is perfectly legal in ResKnife to read in forks of these names when accessing a shared NTFS drive from a server running SFM. */
[resource setRepresentedFork:forkName];
[resource setDocument:self];
[resources insertObject:resource atIndex:0];
return YES;
}
else return NO;
}
- (BOOL)readResourceMap:(SInt16)fileRefNum
-(BOOL) readResourceMap: (SInt16)fileRefNum
{
OSStatus error = noErr;
unsigned short n;
unsigned short i;
SInt16 oldResFile = CurResFile();
UseResFile( fileRefNum );
for( unsigned short i = 1; i <= Count1Types(); i++ )
for( i = 1; i <= Count1Types(); i++ )
{
ResType resType;
unsigned short j;
Get1IndType( &resType, i );
n = Count1Resources( resType );
for( unsigned short j = 1; j <= n; j++ )
for( j = 1; j <= n; j++ )
{
Str255 nameStr;
long sizeLong;
short resIDShort;
short attrsShort;
Handle resourceHandle;
Str255 nameStr;
long sizeLong;
short resIDShort;
short attrsShort;
Handle resourceHandle;
NSString *name;
NSString *type;
NSNumber *resID;
NSNumber *attributes;
NSData *data;
Resource *resource;
resourceHandle = Get1IndResource( resType, j );
error = ResError();
@ -611,12 +740,13 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
HLockHi( resourceHandle );
// create the resource & add it to the array
NSString *name = [NSString stringWithCString:&nameStr[1] length:nameStr[0]];
NSString *type = [NSString stringWithCString:(char *) &resType length:4];
NSNumber *resID = [NSNumber numberWithShort:resIDShort];
NSNumber *attributes = [NSNumber numberWithShort:attrsShort];
NSData *data = [NSData dataWithBytes:*resourceHandle length:sizeLong];
Resource *resource = [Resource resourceOfType:type andID:resID withName:name andAttributes:attributes data:data];
name = [NSString stringWithCString:&nameStr[1] length:nameStr[0]];
type = [NSString stringWithCString:(char *) &resType length:4];
resID = [NSNumber numberWithShort:resIDShort];
attributes = [NSNumber numberWithShort:attrsShort];
data = [NSData dataWithBytes:*resourceHandle length:sizeLong];
resource = [Resource resourceOfType:type andID:resID withName:name andAttributes:attributes data:data];
[resource setDocument:self];
[resources addObject:resource]; // array retains resource
HUnlock( resourceHandle );
@ -678,33 +808,57 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
return succeeded;
}
- (BOOL)writeResourceMap:(SInt16)fileRefNum
/* -----------------------------------------------------------------------------
writeResourceMap:
Writes all resources (except the ones representing other forks of the
file) to the specified resource file.
REVISIONS:
2003-08-01 UK Swiss national holiday, and I'm stuck in Germany...
Commented, changed to use enumerator instead of
objectAtIndex.
-------------------------------------------------------------------------- */
-(BOOL) writeResourceMap: (SInt16)fileRefNum
{
OSStatus error = noErr;
unsigned long i;
SInt16 oldResFile = CurResFile();
OSStatus error = noErr;
NSEnumerator* enny;
Resource *resource;
// Make the resource file current:
SInt16 oldResFile = CurResFile();
UseResFile( fileRefNum );
for( i = 0; i < [resources count]; i++ )
// Loop over all our resources:
for( enny = [resources objectEnumerator]; resource = [enny nextObject]; )
{
Resource *resource = [resources objectAtIndex:i];
if( [resource representedFork] != nil ) continue;
Str255 nameStr;
ResType resType;
short resIDShort = [[resource resID] shortValue];
short attrsShort = [[resource attributes] shortValue];
Handle resourceHandle = NewHandleClear( [[resource data] length] );
short resIDShort;
short attrsShort;
Handle resourceHandle;
// Resource represents another fork in the file? Skip it.
if( [resource representedFork] != nil ) continue;
resIDShort = [[resource resID] shortValue];
attrsShort = [[resource attributes] shortValue];
resourceHandle = NewHandleClear( [[resource data] length] );
// Unicode name -> P-String:
nameStr[0] = [[resource name] cStringLength];
BlockMoveData( [[resource name] cString], &nameStr[1], nameStr[0] );
// Type string to ResType:
[[resource type] getCString:(char *) &resType maxLength:4];
// NSData to resource data Handle:
HLockHi( resourceHandle );
[[resource data] getBytes:*resourceHandle];
HUnlock( resourceHandle );
// Now that everything's converted, write it to our file:
AddResource( resourceHandle, resType, resIDShort, nameStr );
if( ResError() == addResFailed )
{
@ -720,7 +874,7 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
}
}
// save resource map and clean up
// Save resource map and clean up:
UseResFile( oldResFile );
return error? NO:YES;
}
@ -807,4 +961,77 @@ static NSString *RKShowInfoItemIdentifier = @"com.nickshanks.resknife.toolbar.sh
[[self undoManager] setActionName:NSLocalizedString( @"Change Creator & Type", nil)];
}
-(void) exportDataPanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
NSData* data = contextInfo;
[data autorelease];
if( returnCode == NSOKButton )
[data writeToFile:[sheet filename] atomically: YES];
}
-(void) exportImagePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
NSImage* img = contextInfo;
[img autorelease];
if( returnCode == NSOKButton )
{
NSData* data = [img TIFFRepresentation];
[data writeToFile:[sheet filename] atomically: YES];
}
}
-(IBAction) exportResourceToFile: (id)sender
{
Resource* resource = (Resource*) [outlineView selectedItem];
NSData* theData;
Class edClass = [[RKEditorRegistry mainRegistry] editorForType: [resource type]];
NSString* extension = [resource type];
NSSavePanel* panel;
NSString* fName;
if( [edClass respondsToSelector:@selector(dataForFileExport:)] )
theData = [edClass dataForFileExport: resource];
else
theData = [resource data];
if( [edClass respondsToSelector:@selector(extensionForFileExport:)] )
extension = [edClass extensionForFileExport];
panel = [NSSavePanel savePanel];
fName = [[resource name] stringByAppendingFormat: @".%@", extension];
[panel beginSheetForDirectory:nil file:fName modalForWindow:mainWindow modalDelegate:self
didEndSelector:@selector(exportDataPanelDidEnd:returnCode:contextInfo:) contextInfo:[theData retain]];
}
-(IBAction) exportResourceToImageFile: (id)sender
{
Resource* resource = (Resource*) [outlineView selectedItem];
NSImage* theData;
Class edClass = [[RKEditorRegistry mainRegistry] editorForType: [resource type]];
NSString* extension = @"tiff";
NSSavePanel* panel;
NSString* fName;
if( ![edClass respondsToSelector:@selector(imageForImageFileExport:)] )
return;
theData = [edClass imageForImageFileExport: resource];
if( [edClass respondsToSelector:@selector(extensionForImageFileExport:)] )
extension = [edClass extensionForFileExport];
panel = [NSSavePanel savePanel];
fName = [[resource name] stringByAppendingFormat: @".%@", extension];
[panel beginSheetForDirectory:nil file:fName modalForWindow:mainWindow modalDelegate:self
didEndSelector:@selector(exportImagePanelDidEnd:returnCode:contextInfo:) contextInfo:[theData retain]];
}
@end

View File

@ -21,8 +21,9 @@
int dataLength = [data length], bytesPerRow = [controller bytesPerRow];
int rows = (dataLength / bytesPerRow) + ((dataLength % bytesPerRow)? 1:0);
NSMutableString *representation = [NSMutableString string];
int row;
for( int row = 0; row < rows; row++ )
for( row = 0; row < rows; row++ )
[representation appendFormat:@"%08lX:", row * bytesPerRow];
return representation;
@ -35,11 +36,14 @@
char buffer[bytesPerRow*3 +1], hex1, hex2;
char *bytes = (char *) [data bytes];
NSMutableString *representation = [NSMutableString string];
int row;
// calculate bytes
for( int row = 0; row < rows; row++ )
for( row = 0; row < rows; row++ )
{
for( int addr = 0; addr < bytesPerRow; addr++ )
int addr;
for( addr = 0; addr < bytesPerRow; addr++ )
{
if( currentByte < dataLength )
{
@ -82,11 +86,14 @@
char buffer[bytesPerRow +1];
char *bytes = (char *) [data bytes];
NSMutableString *representation = [NSMutableString string];
int row;
// calculate bytes
for( int row = 0; row < rows; row++ )
for( row = 0; row < rows; row++ )
{
for( int addr = 0; addr < bytesPerRow; addr++ )
int addr;
for( addr = 0; addr < bytesPerRow; addr++ )
{
if( currentByte < dataLength )
{
@ -121,7 +128,9 @@
NSString *result;
unsigned long bytesEncoded = ([data length] + 1) / 3;
char *buffer = (char *) malloc( bytesEncoded ), hex1, hex2;
for( int i = 0; i < bytesEncoded; i++ )
int i;
for( i = 0; i < bytesEncoded; i++ )
{
hex1 = ((char *)[data bytes])[3*i];
hex2 = ((char *)[data bytes])[3*i+1];

View File

@ -264,6 +264,7 @@ static NSRange draggedRange;
NSPasteboard *pb = [sender draggingPasteboard];
NSData *pastedData = [pb dataForType:NSStringPboardType];
int charIndex = [self _insertionGlyphIndexForDrag:sender];
NSRange selection;
// convert hex string to data
if( [sender draggingSource] == [[self delegate] hex] )
@ -293,7 +294,7 @@ static NSRange draggedRange;
[self editData:[[[self window] windowController] data] replaceBytesInRange:NSMakeRange(charIndex,0) withData:pastedData];
// set the new selection/insertion point
NSRange selection = [self rangeForUserTextChange];
selection = [self rangeForUserTextChange];
selection.location -= draggedRange.length;
selection.length = draggedRange.length;
[self setSelectedRange:selection];
@ -327,8 +328,9 @@ static NSRange draggedRange;
if( self == (id) [[self delegate] hex] )
{
int i;
// bug: iteration through each character in string is broken, paste not yet mapped to this function
for( int i = 0; i < [string cStringLength]; i++ )
for( i = 0; i < [string cStringLength]; i++ )
{
char typedChar = [string characterAtIndex:i];
if( typedChar >= 0x30 && typedChar <= 0x39 ) typedChar -= 0x30; // 0 to 9

View File

@ -47,11 +47,6 @@ OSStatus Plug_InitInstance( Plug_PlugInRef plug, Plug_ResourceRef resource )
return self;
}
- (id)initWithResources:(id)newResource, ...
{
return nil;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];

View File

@ -1,14 +1,60 @@
#import <Foundation/Foundation.h>
#import "ResKnifeResourceProtocol.h"
/* Your plug-in's principle class must implement initWithResource: else it won't be loaded by ResKnife (so neh-neh!), all other methods are optional */
/* Your plug-in's principal class must implement initWithResource: else it
won't be loaded by ResKnife (so neh-neh!), all other methods are optional,
and thus declared in ResKnifeInformalPluginProtocol. */
@protocol ResKnifePluginProtocol
/*! @function initWithResource:
* @abstract Your plug-in is inited with this call. This allows immediate access to the resource you are about to edit, and with this information you can set up different windows, etc.
/*! @method initWithResource:
* @abstract Your plug-in is inited with this call. This allows immediate
access to the resource you are about to edit, and with this
information you can set up different windows, etc.
*/
- (id)initWithResource:(id <ResKnifeResourceProtocol>)newResource;
@end
/* Optional methods your plugin may implement to provide additional
functionality: */
@interface ResKnifeInformalPluginProtocol
/*! @method dataForFileExport:
@abstract Return the data to be saved to disk when your resource is
exported to a file. By default the host application substitutes
the raw resource data if you don't implement this. The idea is
that this export function is non-lossy, i.e. only override this
if there is a format that is a 100% equivalent to your data. */
+(NSData*) dataForFileExport: (id <ResKnifeResourceProtocol>)theRes;
/*! @method extensionForFileExport:
@abstract If you implement dataForFileExport, return a string here that
provides the proper file extension for your file. By default the
host application substitutes the resource type here. */
+(NSString*) extensionForFileExport: (id <ResKnifeResourceProtocol>)theRes;
/*! @method imageForImageFileExport:
@abstract Return the image to be saved to disk when your resource is
exported to an image file. If your resource contains image
data, this is your opportunity to export it to a well-known
image format. This will be a lossy conversion to a TIFF
file. */
+(NSImage*) imageForImageFileExport: (id <ResKnifeResourceProtocol>)theRes;
/*! @method extensionForImageFileExport:
@abstract If you implement imageForImageFileExport, return a string here that
provides the proper file extension for your file. By default the
host application substitutes "tiff" here. */
+(NSString*) extensionForImageFileExport: (id <ResKnifeResourceProtocol>)theRes;
@end
/* If you're implementing a template editor, you should implement this
extended protocol instead of the regular plugin protocol: */
@protocol ResKnifeTemplatePluginProtocol <ResKnifePluginProtocol>
- (id)initWithResources:(id <ResKnifeResourceProtocol>)newResource, ...;
@end

View File

@ -1,22 +1,57 @@
/* =============================================================================
PROJECT: ResKnife
FILE: ResKnifeResourceProtocol.h
PURPOSE: This protocol wraps up whatever implementation the host
application (i.e. ResKnife) uses for resources in a way that
every editor can get enough information about the resource
being edited.
Or in Nick's immortal words (which I found only *after* I had
written the stuff above):
This protocol allows your plug to interrogate a resource to
find out information about it.
AUTHORS: Nick Shanks, nick(at)nickshanks.com, (c) ~2001.
M. Uli Kusterer, witness(at)zathras.de, (c) 2003.
REVISIONS:
2003-07-31 UK Added document accessor, commented.
========================================================================== */
/* -----------------------------------------------------------------------------
Headers:
-------------------------------------------------------------------------- */
#import <Cocoa/Cocoa.h>
/* This protocol allows your plug to interrogate a resource to find out information about it. */
/* -----------------------------------------------------------------------------
Protocol:
-------------------------------------------------------------------------- */
@protocol ResKnifeResourceProtocol
- (void)touch;
- (BOOL)isDirty;
- (NSString *)name;
- (void)setName:(NSString *)newName;
- (NSString *)type;
- (void)setType:(NSString *)newType;
- (NSNumber *)resID;
- (void)setResID:(NSNumber *)newResID;
- (NSNumber *)attributes;
- (void)setAttributes:(NSNumber *)newAttributes;
- (NSNumber *)size;
- (NSData *)data;
- (void)setData:(NSData *)newData;
-(void) touch;
-(BOOL) isDirty;
-(NSString*) name;
-(void) setName: (NSString*)newName;
-(NSString*) type;
-(void) setType: (NSString*)newType;
-(NSNumber*) resID;
-(void) setResID: (NSNumber*)newResID;
-(NSNumber*) attributes;
-(void) setAttributes: (NSNumber*)newAttributes;
-(NSNumber*) size;
-(NSData*) data;
-(void) setData: (NSData*)newData;
-(NSDocument*) document; // Owner of this resource. Useful for looking for resources in same file as yours.
// These methods are used to retrieve resources other than the one you're editing.
// Passing a document of nil will indicate to search in all open documents.
@ -31,8 +66,11 @@
@end
// Resource notifications
// See note in Notifications.m about usage
/* -----------------------------------------------------------------------------
Resource Notifications:
See note in Notifications.m about usage.
-------------------------------------------------------------------------- */
extern NSString *ResourceWillChangeNotification;
extern NSString *ResourceNameWillChangeNotification;
extern NSString *ResourceTypeWillChangeNotification;

View File

@ -3,7 +3,7 @@
#import "ResKnifePluginProtocol.h"
#import "ResKnifeResourceProtocol.h"
@interface TemplateWindowController : NSWindowController <ResKnifePluginProtocol>
@interface TemplateWindowController : NSWindowController <ResKnifePluginProtocol, ResKnifeTemplatePluginProtocol>
{
IBOutlet NSView *containerView;

View File

@ -171,9 +171,11 @@
// while( currentTemplateElement = [enumerator nextObject] )
while( position < [[resource size] unsignedLongValue] )
{
unsigned long type;
currentTemplateElement = [tmpl objectAtIndex:n];
n++, c++;
unsigned long type = [currentTemplateElement typeAsLong];
type = [currentTemplateElement typeAsLong];
resourceElement = [[currentTemplateElement copy] autorelease];
NSLog( @"tmpl element = %@; position = %d", currentTemplateElement, position );
switch( type )

BIN
Cocoa/Resources/Export.tiff Normal file

Binary file not shown.

6
Cocoa/main.m Normal file
View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
int main(int argc, const char *argv[])
{
return NSApplicationMain(argc, argv);
}

View File

@ -2,7 +2,8 @@
#import <Carbon/Carbon.h>
#pragma options align=packed
#pragma options align=mac68k
//#pragma options align=packed // TODO: This doesn't compile in PB (but it does in xCode).
// see http://developer.apple.com/techpubs/macosx/DeveloperTools/MachORuntime/2rt_powerpc_abi/PowerPC_Data_Alignment.html
// align=reset is at the bottom of the file.

View File

@ -4,6 +4,10 @@
- (id)initWithResource:(id <ResKnifeResourceProtocol>)newResource
{
NSString *tempPrefix;
NSString *tempSuffix;
NSString *tempStart;
self = [self initWithWindowNibName:@"char"];
if( !self ) return nil;
@ -66,9 +70,9 @@
cash = [[NSNumber alloc] initWithLong:charRec->startCash];
kills = [[NSNumber alloc] initWithShort:charRec->startKills];
date = [[NSCalendarDate alloc] initWithYear:charRec->startYear month:charRec->startMonth day:charRec->startDay hour:0 minute:0 second:0 timeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
NSString *tempPrefix = [[[NSString alloc] initWithData:[NSData dataWithBytes:charRec->Prefix length:16] encoding:NSMacOSRomanStringEncoding] autorelease];
tempPrefix = [[[NSString alloc] initWithData:[NSData dataWithBytes:charRec->Prefix length:16] encoding:NSMacOSRomanStringEncoding] autorelease];
prefix = [[NSString alloc] initWithCString:[tempPrefix cString] length:[tempPrefix cStringLength]];
NSString *tempSuffix = [[[NSString alloc] initWithData:[NSData dataWithBytes:charRec->Suffix length:16] encoding:NSMacOSRomanStringEncoding] autorelease];
tempSuffix = [[[NSString alloc] initWithData:[NSData dataWithBytes:charRec->Suffix length:16] encoding:NSMacOSRomanStringEncoding] autorelease];
suffix = [[NSString alloc] initWithCString:[tempSuffix cString] length:[tempSuffix cStringLength]];
start1 = [[NSNumber alloc] initWithShort:charRec->startSystem[0]];
start2 = [[NSNumber alloc] initWithShort:charRec->startSystem[1]];
@ -91,7 +95,7 @@
introDelay2 = [[NSNumber alloc] initWithShort:charRec->introPictDelay[1]];
introDelay3 = [[NSNumber alloc] initWithShort:charRec->introPictDelay[2]];
introDelay4 = [[NSNumber alloc] initWithShort:charRec->introPictDelay[3]];
NSString *tempStart = [[[NSString alloc] initWithData:[NSData dataWithBytes:charRec->OnStart length:256] encoding:NSMacOSRomanStringEncoding] autorelease];
tempStart = [[[NSString alloc] initWithData:[NSData dataWithBytes:charRec->OnStart length:256] encoding:NSMacOSRomanStringEncoding] autorelease];
onStart = [[NSString alloc] initWithCString:[tempStart cString] length:[tempStart cStringLength]];
// rotating image
@ -162,6 +166,8 @@
- (void)update
{
NSData *stringData;
// principal character
[principalCharButton setState:principalChar];
@ -207,7 +213,7 @@
[introPictField4 setObjectValue:[pictureDataSource stringValueForResID:introPict4]];
[introTextField setObjectValue:[descriptionDataSource stringValueForResID:introText]];
NSData *stringData = [(id <ResKnifeResourceProtocol>)[NSClassFromString(@"Resource") getResourceOfType:[plugBundle localizedStringForKey:@"desc" value:@"" table:@"Resource Types"] andID:introText inDocument:nil] data];
stringData = [(id <ResKnifeResourceProtocol>)[NSClassFromString(@"Resource") getResourceOfType:[plugBundle localizedStringForKey:@"desc" value:@"" table:@"Resource Types"] andID:introText inDocument:nil] data];
if( stringData != nil )
{
[introTextView setString:[[[NSString alloc] initWithData:stringData encoding:NSMacOSRomanStringEncoding] autorelease]];

View File

@ -1 +1 @@
#undef __STDC_VERSION__
//#undef __STDC_VERSION__