Add missing files for various editors.

This commit is contained in:
Nicholas Shanks 2009-11-09 10:20:26 +00:00
parent fe032b0da4
commit 5acb769f40
9 changed files with 1223 additions and 2 deletions

View File

@ -0,0 +1,338 @@
#import "FontWindowController.h"
#import "Categories.h"
#import <stdarg.h>
UInt32 TableChecksum(UInt32 *table, UInt32 length)
{
UInt32 sum = 0, nLongs = (length+3) >> 2;
while(nLongs-- > 0) sum += *table++;
return sum;
}
@implementation FontWindowController
- (id)initWithResource:(id <ResKnifeResourceProtocol>)inResource
{
self = [self initWithWindowNibName:@"FontDocument"];
if(!self) return nil;
resource = [(id)inResource retain];
headerTable = [[NSMutableArray alloc] init];
[self loadFontFromResource];
// load the window from the nib
[self window];
return self;
}
- (void)loadFontFromResource
{
char *start = (char *)[[resource data] bytes];
if (start != 0x0)
{
arch = *(OSType*)start;
numTables = *(UInt16*)(start+4);
searchRange = *(UInt16*)(start+6);
entrySelector = *(UInt16*)(start+8);
rangeShift = *(UInt16*)(start+10);
UInt32 *pos = (UInt32 *)(start+12);
/* printf("%s\n", [[self displayName] cString]);
printf(" architecture: %#lx '%.4s'\n", arch, &arch);
printf(" number of tables: %hu\n", numTables);
printf(" searchRange: %hu\n", searchRange);
printf(" entrySelector: %hu\n", entrySelector);
printf(" rangeShift: %hu\n\n", rangeShift);
*/ for(int i = 0; i < numTables; i++)
{
OSType name = *pos++;
UInt32 checksum = *pos++;
UInt32 offset = *pos++;
UInt32 length = *pos++;
[headerTable addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
[[[NSString alloc] initWithBytes:&name length:4 encoding:NSMacOSRomanStringEncoding] autorelease], @"name",
[NSNumber numberWithUnsignedLong: checksum], @"checksum",
[NSNumber numberWithUnsignedLong: offset], @"offset",
[NSNumber numberWithUnsignedLong: length], @"length",
[NSData dataWithBytes:start+offset length:length], @"data",
nil]];
}
}
else
{
NSLog(@"Invalid sfnt, aborting parse.");
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[(id)resource release];
[headerTable release];
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
// set the window's title
if(![[resource name] isEqualToString:@""])
{
[[self window] setTitle:[resource name]];
SetWindowAlternateTitle((WindowRef) [[self window] windowRef], (CFStringRef) [NSString stringWithFormat:NSLocalizedString(@"%@ %@: '%@'", nil), [resource type], [resource resID], [resource name]]);
}
// we don't want this notification until we have a window! (Only register for notifications on the resource we're editing)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:resource];
// finally, show the window
[self showWindow:self];
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
NSMenu *resourceMenu = [[[NSApp mainMenu] itemAtIndex:3] submenu];
NSMenuItem *createItem = [resourceMenu itemAtIndex:[resourceMenu indexOfItemWithTarget:nil andAction:@selector(showCreateResourceSheet:)]];
[createItem setTitle: NSLocalizedString(@"Add Font Table...", nil)];
[createItem setAction:@selector(showAddFontTableSheet:)];
}
- (void)windowDidResignKey:(NSNotification *)notification
{
NSMenu *resourceMenu = [[[NSApp mainMenu] itemAtIndex:3] submenu];
NSMenuItem *createItem = [resourceMenu itemAtIndex:[resourceMenu indexOfItemWithTarget:nil andAction:@selector(showAddFontTableSheet:)]];
[createItem setTitle: NSLocalizedString(@"Create New Resource...", nil)];
[createItem setAction:@selector(showCreateResourceSheet:)];
}
- (void)resourceDataDidChange:(NSNotification *)notification
{
[headerTable removeAllObjects];
[self loadFontFromResource];
}
- (BOOL)windowShouldClose:(id)sender
{
if([[self window] isDocumentEdited])
{
NSBeginAlertSheet(@"Do you want to keep the changes you made to this font?", @"Keep", @"Don't Keep", @"Cancel", sender, self, @selector(saveSheetDidClose:returnCode:contextInfo:), nil, nil, @"Your changes cannot be saved later if you don't keep them.");
return NO;
}
else return YES;
}
- (void)saveSheetDidClose:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
switch(returnCode)
{
case NSAlertDefaultReturn: // keep
[self saveResource:nil];
[[self window] close];
break;
case NSAlertAlternateReturn: // don't keep
[[self window] close];
break;
case NSAlertOtherReturn: // cancel
break;
}
}
- (void)saveResource:(id)sender
{
// write header fields
NSMutableData *data = [NSMutableData data];
[data appendBytes:&arch length:4];
[data appendBytes:&numTables length:2];
[data appendBytes:&searchRange length:2];
[data appendBytes:&entrySelector length:2];
[data appendBytes:&rangeShift length:2];
UInt32 offset = 12 + ([headerTable count] << 4);
// add table index
for(int i = 0; i < numTables; i++)
{
NSMutableDictionary *table = [headerTable objectAtIndex:i];
NSData *tableData = [table valueForKey:@"data"];
UInt32 length = [tableData length];
UInt32 checksum = TableChecksum((UInt32 *)[tableData bytes], length);
[table setValue:[NSNumber numberWithUnsignedLong:checksum] forKey:@"checksum"];
[table setValue:[NSNumber numberWithUnsignedLong:offset] forKey:@"offset"];
[table setValue:[NSNumber numberWithUnsignedLong:length] forKey:@"length"];
[data appendBytes:[[table valueForKey:@"name"] cStringUsingEncoding:NSMacOSRomanStringEncoding] length:4];
[data appendBytes:&checksum length:4];
[data appendBytes:&offset length:4];
[data appendBytes:&length length:4];
offset += length;
if(offset % 4)
offset += 4-(offset%4);
}
// append tables
long align = 0;
for(int i = 0; i < numTables; i++)
{
// note that this doesn't output in the order thet they were read, nor align on long boundries
[data appendData:[[headerTable objectAtIndex:i] valueForKey:@"data"]];
if([data length] % 4) // pads the last table too... oh well
[data appendBytes:&align length:4-([data length]%4)];
}
// write checksum adjustment to head table
NSDictionary *head = [headerTable firstObjectReturningValue:@"head" forKey:@"name"];
if(head)
{
UInt32 fontChecksum = 0;
NSRange csRange = NSMakeRange([[head valueForKey:@"offset"] unsignedLongValue]+8,4);
[data replaceBytesInRange:csRange withBytes:&fontChecksum length:4];
fontChecksum = TableChecksum((UInt32 *)[data bytes], [data length]);
[data replaceBytesInRange:csRange withBytes:&fontChecksum length:4];
}
// [[NSNotificationCenter defaultCenter] removeObserver:self name:ResourceDataDidChangeNotification object:backup];
[resource setData:data];
// [backup setData:[data copy]];
// [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:backup];
[self setDocumentEdited:NO];
}
- (IBAction)showAddFontTableSheet:(id)sender
{
}
- (void)addFontTable:(NSString *)name
{
NSMutableDictionary *table = [NSMutableDictionary dictionaryWithObjectsAndKeys:
name, @"name",
[NSNumber numberWithUnsignedLong: 0], @"checksum",
[NSNumber numberWithUnsignedLong: 0], @"offset",
[NSNumber numberWithUnsignedLong: 0], @"length",
[NSData data], @"data", nil];
[headerTable addObject:table];
numTables = [headerTable count];
[self openTable:table inEditor:YES];
[self setDocumentEdited:YES];
}
- (void)openTableInEditor:(NSTableView *)sender
{
if([sender action])
{
// action set in IB but swapped at first click for a doubleAction ;)
[sender setAction:nil];
[sender setDoubleAction:@selector(openTableInEditor:)];
return;
}
[self openTable:[headerTable objectAtIndex:[sender clickedRow]] inEditor:YES];
}
- (void)openTable:(NSDictionary *)table inEditor:(BOOL)editor
{
NSData *data = [table valueForKey:@"data"];
if (data)
{
id tableResource = [NSClassFromString(@"Resource") resourceOfType:[table valueForKey:@"name"] andID:[NSNumber numberWithInt:0] withName:[NSString stringWithFormat:NSLocalizedString(@"%@ >> %@", nil), [resource name], [table valueForKey:@"name"]] andAttributes:[NSNumber numberWithUnsignedShort:0] data:[table valueForKey:@"data"]];
if (!tableResource)
{
NSLog(@"Couldn't create Resource with data for table '%@'.", [table valueForKey:@"name"]);
return;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tableDataDidChange:) name:ResourceDataDidChangeNotification object:tableResource];
if (editor) [(id)[resource document] openResourceUsingEditor:tableResource];
else [(id)[resource document] openResource:tableResource usingTemplate:[NSString stringWithFormat:@"sfnt subtable '%@'", [table valueForKey:@"name"]]];
}
}
- (void)tableDataDidChange:(NSNotification *)notification
{
[self setTableData:[notification object]];
}
- (void)setTableData:(id)tableResource
{
NSDictionary *table = [headerTable firstObjectReturningValue:[tableResource type] forKey:@"name"];
if(!table)
{
NSLog(@"Couldn't retrieve table with name '%@'.", [tableResource type]);
return;
}
id undoResource = [[tableResource copy] autorelease];
[undoResource setData:[table valueForKey:@"data"]];
[[[resource document] undoManager] registerUndoWithTarget:resource selector:@selector(setTableData:) object:undoResource];
[[[resource document] undoManager] setActionName:[NSString stringWithFormat:NSLocalizedString(@"Edit of table '%@'", nil), [tableResource type]]];
[table setValue:[tableResource data] forKey:@"data"];
[self setDocumentEdited:YES];
}
+ (NSString *)filenameExtensionForFileExport:(id <ResKnifeResourceProtocol>)resource
{
if([[resource type] isEqualToString:@"sfnt"]) return @"ttf";
else return [resource type];
}
@end
/* sfnt_header *header = (sfnt_header *) [[resource data] bytes];
for(int i = 0; i < header->tableCount; i++)
{
switch(header->tableInfo[i].tagname)
{
case 'name':
name_table_header *name_table = (name_table_header *) (((char *)[[resource data] bytes]) + header->tableInfo[i].offset);
NSMutableArray *nameArray = [NSMutableArray array];
for(int j = 0; j < name_table->record_count; j++)
{
// load names into array of name_record classes
NSMutableDictionary *name = [NSMutableDictionary dictionary];
NSData *stringData = [NSData dataWithBytes:((char *)name_table + name_table->names[j].offset) length:name_table->names[j].length];
NSStringEncoding stringEncoding;
switch(name_table->names[j].platform_id)
{
case 0: // unicode
stringEncoding = NSUnicodeStringEncoding;
break;
case 1: // mac - values originally were smScript values, which are equivalent to CFStringEncoding values
stringEncoding = CFStringConvertEncodingToNSStringEncoding(name_table->names[j].platform_specific_id);
break;
case 2: // ISO
switch(name_table->names[j].platform_specific_id)
{
case 0: stringEncoding = NSASCIIStringEncoding; break;
case 1: stringEncoding = NSUnicodeStringEncoding; break; // ISO 10646
case 2: stringEncoding = NSISOLatin1StringEncoding; break; // ISO 8859-1
default: stringEncoding = NSASCIIStringEncoding; break;
}
break;
case 2: // windows
switch(name_table->names[j].platform_specific_id)
{
// bug: should use correct encodings here
default: stringEncoding = NSWindowsCP1252StringEncoding; break;
}
break;
default: // undefined
stringEncoding = NSWindowsCP1250StringEncoding; // guess Win-Latin-2
break;
}
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].platform_id] forKey:@"platform"];
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].platform_specific_id] forKey:@"specific"];
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].language_id] forKey:@"language"];
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].name_id] forKey:@"name"];
[name setValue:[NSString stringWithData:stringData encoding:stringEncoding]] forKey:@"string"];
[nameArray addObject:name];
}
[tables setObject:nameArray forKey:@"name"];
break;
default:
// else just save the data of the table
[tables setObject:[NSData dataWithBytes:(((char *)[[resource data] bytes]) + header->tableInfo[i].offset) length:header->tableInfo[i].length] forKey:[NSString stringWithCString:&(header->tableInfo[i].tagname) length:4]];
break;
}
}
*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -0,0 +1,317 @@
#import "HexWindowController.h"
#import "HexTextView.h"
#import "FindSheetController.h"
#import "NSData-HexRepresentation.h"
/*
OSStatus Plug_InitInstance(Plug_PlugInRef plug, Plug_ResourceRef resource)
{
// init function called by carbon apps
if(NSApplicationLoad())
{
id newResource = [NSClassFromString(@"Resource") resourceOfType:[NSString stringWithCString:length:4] andID:[NSNumber numberWithInt:] withName:[NSString stringWithCString:length:] andAttributes:[NSNumber numberWithUnsignedShort:] data:[NSData dataWithBytes:length:]];
if(!newResource) return paramErr;
id windowController = [[HexWindowController alloc] initWithResource:newResource];
if(!windowController) return paramErr;
else return noErr;
}
else return paramErr;
}
*/
@implementation HexWindowController
- (id)initWithResource:(id)newResource
{
self = [self initWithWindowNibName:@"HexWindow"];
if(!self) return nil;
// one instance of your principal class will be created for every resource the user wants to edit (similar to Windows apps)
undoManager = [[NSUndoManager alloc] init];
liveEdit = NO;
if(liveEdit)
{
resource = [newResource retain]; // resource to work on and monitor for external changes
backup = [newResource copy]; // for reverting only
}
else
{
resource = [newResource copy]; // resource to work on
backup = [newResource retain]; // actual resource to change when saving data and monitor for external changes
}
bytesPerRow = 16;
// load the window from the nib file
[self window];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[undoManager release];
[(id)resource release];
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
{
// set up tab, shift-tab and enter behaviour (cannot set these in IB at the moment)
[hex setFieldEditor:YES];
[ascii setFieldEditor:YES];
[offset setDrawsBackground:NO];
[[offset enclosingScrollView] setDrawsBackground:NO];
// from HexEditorDelegate, here until bug is fixed
[[NSNotificationCenter defaultCenter] addObserver:hexDelegate selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:[[offset enclosingScrollView] contentView]];
[[NSNotificationCenter defaultCenter] addObserver:hexDelegate selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:[[hex enclosingScrollView] contentView]];
[[NSNotificationCenter defaultCenter] addObserver:hexDelegate selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:[[ascii enclosingScrollView] contentView]];
}
// insert the resources' data into the text fields
[self refreshData:[resource data]];
[[self window] setResizeIncrements:NSMakeSize(kWindowStepWidthPerChar * kWindowStepCharsPerStep, 1)];
// min 346, step 224, norm 570, step 224, max 794
// here because we don't want these notifications until we have a window! (Only register for notifications on the resource we're editing)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceNameDidChange:) name:ResourceNameDidChangeNotification object:resource];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:resource];
if(liveEdit) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceWasSaved:) name:ResourceDataDidChangeNotification object:resource];
else [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceWasSaved:) name:ResourceDataDidChangeNotification object:backup];
// finally, set the window title & show the window
[[self window] setTitle:[resource defaultWindowTitle]];
[self showWindow:self];
}
- (void)windowDidResize:(NSNotification *)notification
{
int width = [(NSWindow *)[notification object] frame].size.width;
int oldBytesPerRow = bytesPerRow;
bytesPerRow = (((width - (kWindowStepWidthPerChar * kWindowStepCharsPerStep) - 122) / (kWindowStepWidthPerChar * kWindowStepCharsPerStep)) + 1) * kWindowStepCharsPerStep;
if(bytesPerRow != oldBytesPerRow)
[offset setString:[hexDelegate offsetRepresentation:[resource data]]];
[[hex enclosingScrollView] setFrameSize:NSMakeSize((bytesPerRow * 21) + 5, [[hex enclosingScrollView] frame].size.height)];
[[ascii enclosingScrollView] setFrameOrigin:NSMakePoint((bytesPerRow * 21) + 95, 20)];
[[ascii enclosingScrollView] setFrameSize:NSMakeSize((bytesPerRow * 7) + 28, [[ascii enclosingScrollView] frame].size.height)];
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
NSMenu *editMenu = [[[NSApp mainMenu] itemAtIndex:2] submenu];
NSMenuItem *copyItem = [editMenu itemAtIndex:[editMenu indexOfItemWithTarget:nil andAction:@selector(copy:)]];
NSMenuItem *pasteItem = [editMenu itemAtIndex:[editMenu indexOfItemWithTarget:nil andAction:@selector(paste:)]];
// swap copy: menu item for my own copy submenu
[copyItem setEnabled:YES];
[copyItem setKeyEquivalent:@"\0"]; // clear key equiv.
[copyItem setKeyEquivalentModifierMask:0];
[editMenu setSubmenu:copySubmenu forItem:copyItem];
// swap paste: menu item for my own paste submenu
[pasteItem setEnabled:YES];
[pasteItem setKeyEquivalent:@"\0"];
[pasteItem setKeyEquivalentModifierMask:0];
[editMenu setSubmenu:pasteSubmenu forItem:pasteItem];
}
- (void)windowDidResignKey:(NSNotification *)notification
{
NSMenu *editMenu = [[[NSApp mainMenu] itemAtIndex:2] submenu];
NSMenuItem *copyItem = [editMenu itemAtIndex:[editMenu indexOfItemWithSubmenu:copySubmenu]];
NSMenuItem *pasteItem = [editMenu itemAtIndex:[editMenu indexOfItemWithSubmenu:pasteSubmenu]];
// swap my submenu for plain copy menu item
[editMenu setSubmenu:nil forItem:copyItem];
[copyItem setTarget:nil];
[copyItem setAction:@selector(copy:)];
[copyItem setKeyEquivalent:@"c"];
[copyItem setKeyEquivalentModifierMask:NSCommandKeyMask];
// swap my submenu for plain paste menu item
[editMenu setSubmenu:nil forItem:pasteItem];
[pasteItem setTarget:nil];
[pasteItem setAction:@selector(paste:)];
[pasteItem setKeyEquivalent:@"v"];
[pasteItem setKeyEquivalentModifierMask:NSCommandKeyMask];
}
- (BOOL)windowShouldClose:(id)sender
{
if([[self window] isDocumentEdited])
{
NSBeginAlertSheet(@"Do you want to keep the changes you made to this resource?", @"Keep", @"DonÕt Keep", @"Cancel", sender, self, @selector(saveSheetDidClose:returnCode:contextInfo:), nil, nil, @"Your changes cannot be saved later if you don't keep them.");
return NO;
}
else return YES;
}
- (void)saveSheetDidClose:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
switch(returnCode)
{
case NSAlertDefaultReturn: // keep
[self saveResource:nil];
[[self window] close];
break;
case NSAlertAlternateReturn: // don't keep
[[self window] close];
break;
case NSAlertOtherReturn: // cancel
break;
}
}
- (void)saveResource:(id)sender
{
[backup setData:[[resource data] copy]];
}
- (void)revertResource:(id)sender
{
[resource setData:[[backup data] copy]];
}
- (void)showFind:(id)sender
{
// bug: HexWindowController allocs a sheet controller, but it's never disposed of
FindSheetController *sheetController = [[FindSheetController alloc] initWithWindowNibName:@"FindSheet"];
[sheetController showFindSheet:self];
}
- (void)resourceNameDidChange:(NSNotification *)notification
{
[[self window] setTitle:[(id <ResKnifeResourceProtocol>)[notification object] defaultWindowTitle]];
}
- (void)resourceDataDidChange:(NSNotification *)notification
{
// ensure it's our resource which got changed (should always be true, we don't register for other resource notifications)
// bug: if liveEdit is false and another editor changes backup, if we are dirty we need to ask the user whether to accept the changes from the other editor and discard our changes, or vice versa.
if([notification object] == (id)resource)
{
[self refreshData:[resource data]];
[self setDocumentEdited:YES];
}
}
- (void)resourceWasSaved:(NSNotification *)notification
{
id <ResKnifeResourceProtocol> object = [notification object];
if(liveEdit)
{
// haven't worked out what to do here yet
}
else
{
// this should refresh the view automatically
[resource setData:[[object data] copy]];
[self setDocumentEdited:NO];
}
}
- (void)refreshData:(NSData *)data;
{
// save selections
NSRange hexSelection = [hex selectedRange];
NSRange asciiSelection = [ascii selectedRange];
// clear delegates (see HexEditorDelegate class for explanation of why)
id oldDelegate = [hex delegate];
[hex setDelegate:nil];
[ascii setDelegate:nil];
// prepare attributes dictionary
NSMutableParagraphStyle *paragraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraph setLineBreakMode:NSLineBreakByCharWrapping];
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:paragraph forKey:NSParagraphStyleAttributeName];
// do stuff with data
[offset setString:[hexDelegate offsetRepresentation:data]];
if([data length] > 0)
[hex setString:[[data hexRepresentation] stringByAppendingString:@" "]];
else [hex setString:[data hexRepresentation]];
[ascii setString:[data asciiRepresentation]];
// apply attributes
[[offset textStorage] addAttributes:dictionary range:NSMakeRange(0, [[offset textStorage] length])];
[[hex textStorage] addAttributes:dictionary range:NSMakeRange(0, [[hex textStorage] length])];
[[ascii textStorage] addAttributes:dictionary range:NSMakeRange(0, [[ascii textStorage] length])];
// restore selections (this is the dumbest way to do it, but it'll do for now)
[hex setSelectedRange:NSIntersectionRange(hexSelection, [hex selectedRange])];
[ascii setSelectedRange:NSIntersectionRange(asciiSelection, [ascii selectedRange])];
// restore delegates
[hex setDelegate:oldDelegate];
[ascii setDelegate:oldDelegate];
}
- (id)resource
{
return resource;
}
- (NSData *)data
{
return [resource data];
}
- (int)bytesPerRow
{
return bytesPerRow;
}
- (NSMenu *)copySubmenu
{
return copySubmenu;
}
- (NSMenu *)pasteSubmenu
{
return pasteSubmenu;
}
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)sender
{
return undoManager;
}
/* range conversion methods */
+ (NSRange)byteRangeFromHexRange:(NSRange)hexRange
{
// valid for all window widths
NSRange byteRange = NSMakeRange(0,0);
byteRange.location = (hexRange.location / 3);
byteRange.length = (hexRange.length / 3) + ((hexRange.length % 3)? 1:0);
return byteRange;
}
+ (NSRange)hexRangeFromByteRange:(NSRange)byteRange
{
// valid for all window widths
NSRange hexRange = NSMakeRange(0,0);
hexRange.location = (byteRange.location * 3);
hexRange.length = (byteRange.length * 3) - ((byteRange.length > 0)? 1:0);
return hexRange;
}
+ (NSRange)byteRangeFromAsciiRange:(NSRange)asciiRange
{
// one-to-one mapping
return asciiRange;
}
+ (NSRange)asciiRangeFromByteRange:(NSRange)byteRange
{
// one-to-one mapping
return byteRange;
}
@end

View File

@ -0,0 +1,33 @@
#import <Foundation/Foundation.h>
@interface Element : NSObject
{
NSString *type;
NSString *label;
union // for resource arrays only, ignored for TMPL arrays
{
NSString *string;
NSNumber *number;
NSData *data;
BOOL boolean;
} elementData;
}
- (id)initWithType:(NSString *)typeValue andLabel:(NSString *)labelValue;
+ (id)elementOfType:(NSString *)typeValue withLabel:(NSString *)labelValue;
- (NSString *)label;
- (NSString *)type;
- (unsigned long)typeAsLong;
- (NSString *)string;
- (void)setString:(NSString *)string;
- (NSNumber *)number;
- (void)setNumber:(NSNumber *)number;
- (long)numberAsLong;
- (void)setNumberWithLong:(long)number;
- (NSData *)data;
- (void)setData:(NSData *)data;
- (BOOL)boolean;
- (void)setBoolean:(BOOL)boolean;
@end

View File

@ -0,0 +1,113 @@
#import "NGSElement.h"
@implementation Element
- (id)initWithType:(NSString *)typeValue andLabel:(NSString *)labelValue
{
// sets values directly for speed reasons (less messaging overhead)
self = [super init];
type = [typeValue copy];
label = [labelValue copy];
return self;
}
+ (id)elementOfType:(NSString *)typeValue withLabel:(NSString *)labelValue
{
Element *element = [[Element allocWithZone:[self zone]] initWithType:typeValue andLabel:labelValue];
return [element autorelease];
}
- (id)copy
{
Element *element = [[Element alloc] initWithType:type andLabel:label];
// bug: copy other stuff here
return element;
}
/* ACCESSORS */
- (NSString *)label
{
return label;
}
- (NSString *)type
{
return type;
}
- (unsigned long)typeAsLong
{
return *(unsigned long *)[type cString];
}
/*
- (NSMutableArray *)subelements;
{
long myType = [self typeAsLong];
if( myType == 'LSTB' || myType == 'LSTC' )
{
if( subelements == nil )
subelements = [[NSMutableArray alloc] init];
}
else subelements = nil;
return subelements;
}
*/
/* DATA ACCESSORS */
- (NSString *)string
{
return elementData.string;
}
- (void)setString:(NSString *)string
{
elementData.string = [string retain];
}
- (NSNumber *)number
{
return elementData.number;
}
- (void)setNumber:(NSNumber *)number
{
elementData.number = [number retain];
}
- (long)numberAsLong
{
return [elementData.number longValue];
}
- (void)setNumberWithLong:(long)number
{
elementData.number = [[NSNumber numberWithLong:number] retain];
}
- (NSData *)data
{
return elementData.data;
}
- (void)setData:(NSData *)data
{
elementData.data = [data retain];
}
- (BOOL)boolean
{
return elementData.boolean;
}
- (void)setBoolean:(BOOL)boolean
{
elementData.boolean = boolean;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"{ %@, %@ }", type, label];
}
@end

View File

@ -0,0 +1,28 @@
#import <Cocoa/Cocoa.h>
#import "ResKnifePluginProtocol.h"
#import "ResKnifeResourceProtocol.h"
@interface TemplateWindowController : NSWindowController <ResKnifePluginProtocol, ResKnifeTemplatePluginProtocol>
{
IBOutlet NSView *containerView;
NSMutableArray *tmpl;
NSMutableArray *res;
id <ResKnifeResourceProtocol> resource;
}
// normal methods
- (void)readTemplate:(id <ResKnifeResourceProtocol>)tmpl;
- (void)readData;
- (void)parseData;
- (void)createUI;
- (void)enumerateElements:(NSMutableArray *)elements;
- (void)resourceDataDidChange:(NSNotification *)notification;
- (void)refreshData:(NSData *)data;
// accessors
- (id)resource;
- (NSData *)data;
@end

View File

@ -0,0 +1,392 @@
#import "NGSTemplateWindowController.h"
#import "NGSElement.h"
#import <stdarg.h>
@implementation TemplateWindowController
- (id)initWithResource:(id)newResource
{
return [self initWithResources:newResource, nil];
}
- (id)initWithResources:(id)newResource, ...
{
id currentResource;
va_list resourceList;
// one instance of your principal class will be created for every resource set the user wants to edit (similar to Windows apps)
self = [self initWithWindowNibName:@"TemplateWindow"];
if(!self) return self;
resource = [newResource retain];
tmpl = [[NSMutableArray alloc] init];
res = [[NSMutableArray alloc] init];
va_start(resourceList, newResource);
[self readTemplate:va_arg(resourceList, id)]; // reads (but doesn't retain) the template for this resource (the TMPL resource with a name equal to the passed resource's type)
while(currentResource = va_arg(resourceList, id))
NSLog(@"too many params passed to -initWithResources: %@", [currentResource description]);
va_end(resourceList);
NSString *warning = [self checkTemplate];
if(warning != nil)
{
// should display error message here
NSLog(warning);
return nil;
}
// load the window from the nib
[self window];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[(id)resource release];
[tmpl release];
[res release];
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
// set the window's title
[[self window] setTitle:[resource defaultWindowTitle]];
// parse data using pre-scanned template and create the fields as needed
[self readData];
[self createUI];
// insert the resources' data into the text fields
[self refreshData:[resource data]];
// we don't want this notification until we have a window! (Only register for notifications on the resource we're editing)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:resource];
// finally, show the window
[self showWindow:self];
}
- (void)readTemplate:(id <ResKnifeResourceProtocol>)tmplResource
{
if(tmplResource)
{
NSString *label, *type;
char *currentByte = (char *) [[tmplResource data] bytes];
unsigned long size = [[tmplResource data] length], position = 0;
while(position < size)
{
// check where pointer will be AFTER having loaded this element
position += *currentByte +5;
if(position >= size)
{
NSLog(@"Corrupt TMPL resource: not enough data. Dumping remaining resource as hex.");
[tmpl addObject:[Element elementOfType:@"HEXD" withLabel:NSLocalizedString(@"Corrupted Template: Hex Dump",nil)]];
break;
}
// obtain label and type
label = [[NSString alloc] initWithBytes:currentByte+1 length:*currentByte encoding:NSMacOSRomanStringEncoding];
currentByte += *currentByte +1;
type = [[NSString alloc] initWithBytes:currentByte length:4 encoding:NSMacOSRomanStringEncoding];
currentByte += 4;
// add element to array
[tmpl addObject:[Element elementOfType:type withLabel:label]];
}
}
else
{
// for some reason we got a nill object as the TMPL, do the best we can
NSLog(@"Corrupt TMPL resource: no template given. Dumping resource as hex.");
[tmpl addObject:[Element elementOfType:@"HEXD" withLabel:NSLocalizedString(@"Corrupted Template: Hex Dump",nil)]];
}
}
- (NSString *)checkTemplate
{
NSSet *recognised = [NSSet setWithObjects:nil];
NSMutableSet *fields = [NSMutableSet set];
NSEnumerator *enumerator = [tmpl objectEnumerator];
while(Element *element = [enumerator nextObject])
[fields addObject:[element type]]; // doesn't take into account Pxxx, Cxxx and Hxxx
NSSet *unrecognised = [fields copy];
[unrecognised minusSet:recognised];
if([unrecognised count] > 0)
return [NSString stringWithFormat:NSLocalizedString(@"The template contains the following unrecognised field types: '%@'",nil), [[unrecognised allObjects] componentsJoinedByString:@"', '"]];
else return nil;
}
- (void)readData
{
// tmpl == array of Elements describing the template for this resource
// res == array of either Elements or Arrays containing the resource data
// this function creates the res instance variable, filling it with data from the resource if there's data available.
// unsigned long position = 0; // position (byte offset) in resource I'm currently reading from
// char *data = (char *) [[resource data] bytes]; // address of initial byte of resource in memory
unsigned long templateCounter = 0; // index into template array of the current template element
Element *currentTemplateElement; // current template element
NSMutableArray *targetStack = [NSMutableArray arrayWithObject:res]; // stack of arrays (target for addition of new elements)
// NSMutableArray *loopStack = [NSMutableArray array]; // stack for 'LSTB' and 'LSTC' elements
// NSMutableArray *loopCountStack = [NSMutableArray array]; // stack for counting how many times to loop
// when templateCounter >= [tmpl count], loop is only exited if targetStack has more than one target (this handles empty templates)
while(templateCounter < [tmpl count] || [targetStack count] > 1)
{
currentTemplateElement = [tmpl objectAtIndex:templateCounter];
NSLog(@"template = %@", currentTemplateElement);
/* unsigned long type = [currentTemplateElement typeAsLong];
switch(type)
{
case 'BCNT':
case 'BZCT':
[resourceElement setNumberWithLong:*(unsigned char *)(data + position)];
lim = *(unsigned char *)(data + position) + (type == 'BZCT'? 1:0);
position += 1;
break;
case 'OCNT':
case 'ZCNT':
[resourceElement setNumberWithLong:*(unsigned short *)(data + position)];
lim = *(unsigned short *)(data + position) + (type == 'ZCNT'? 1:0);
position += 2;
break;
case 'LCNT':
case 'LZCT':
[resourceElement setNumberWithLong:*(unsigned long *)(data + position)];
lim = *(unsigned long *)(data + position) + (type == 'LZCT'? 1:0);
position += 4;
break;
case 'LSTB':
case 'LSTC':
[(NSMutableArray *)[targetStack lastObject] addObject:[NSMutableArray array]];
[loopStack addObject:currentTemplateElement]; // append the template loop start object to the array
break;
default:
// [(NSMutableArray *)[targetStack lastObject] addItem:[self createElementForTemplate:currentTemplateElement
}
*/
templateCounter++;
}
}
- (void)parseData
{
unsigned long position = 0;
char *data = (char *) [[resource data] bytes];
// used for nesting of elements, 'target' is current object to append to, targetStack is a FILO stack of mutable array pointers, loopStack is a stack of element indicies to the start of loops, so I can go back to the head of a loop when iterating it
NSMutableArray *target = res;
NSMutableArray *targetStack = [NSMutableArray arrayWithObject:res];
// NSMutableArray *loopStack = [NSMutableArray array];
// n = current item in TMPL to read, c = loop counter, when exiting loop, go back 'c' items in the template, lim is how many times to loop, obtained from a loop count
unsigned long n = 0, c = 0, lim = 0;
// creates an array of elements containing the data in whatever format the template dictates
// array can then simply be manipulated one element at a time, or flattened to save
Element *currentTemplateElement, *resourceElement;
// NSEnumerator *enumerator = [tmpl objectEnumerator];
// while(currentTemplateElement = [enumerator nextObject])
while(position < [[resource size] unsignedLongValue])
{
unsigned long type;
currentTemplateElement = [tmpl objectAtIndex:n];
type = [currentTemplateElement typeAsLong];
resourceElement = [[currentTemplateElement copy] autorelease];
NSLog(@"tmpl element = %@; position = %d", currentTemplateElement, position);
n++, c++;
switch(type)
{
/* Alignment */
case 'AWRD':
position += position % 2;
break;
case 'ALNG':
position += position % 4;
break;
/* Fillers */
case 'FBYT':
position += 1;
break;
case 'FWRD':
position += 2;
break;
case 'FLNG':
position += 4;
break;
/* Decimal */
case 'DBYT':
[resourceElement setNumberWithLong:*(char *)(data + position)];
position += 1;
break;
case 'DWRD':
[resourceElement setNumberWithLong:*(short *)(data + position)];
position += 2;
break;
case 'DLNG':
[resourceElement setNumberWithLong:*(long *)(data + position)];
position += 4;
break;
/* Hex */
case 'HBYT':
[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:1]];
position += 1;
break;
case 'HWRD':
[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:2]];
position += 2;
break;
case 'HLNG':
[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:4]];
position += 4;
break;
case 'HEXD':
// bug: doesn't check HEXD is the last element
[resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:([[resource size] intValue] - position)]];
position = [[resource size] intValue];
break;
/* Strings */
case 'CHAR':
[resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position) length:1] encoding:NSMacOSRomanStringEncoding]];
position += 1;
break;
case 'TNAM':
[resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position) length:4] encoding:NSMacOSRomanStringEncoding]];
position += 4;
break;
case 'PSTR':
[resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position + 1) length:*(unsigned char *)(data + position)] encoding:NSMacOSRomanStringEncoding]];
position += *(unsigned char *)(data + position) + 1;
break;
/* List Counts */
case 'BCNT':
case 'BZCT':
[resourceElement setNumberWithLong:*(unsigned char *)(data + position)];
lim = *(unsigned char *)(data + position) + (type == 'BZCT'? 1:0);
position += 1;
break;
case 'OCNT':
case 'ZCNT':
case 'WCNT':
case 'WZCT':
[resourceElement setNumberWithLong:*(unsigned short *)(data + position)];
lim = *(unsigned short *)(data + position) + (type == 'ZCNT'? 1:0);
position += 2;
break;
case 'LCNT':
case 'LZCT':
[resourceElement setNumberWithLong:*(unsigned long *)(data + position)];
lim = *(unsigned long *)(data + position) + (type == 'LZCT'? 1:0);
position += 4;
break;
/* List beginning and end */
case 'LSTB':
case 'LSTC':
[target addObject:resourceElement]; // add list item to current target array
// target = [resourceElement subelements]; // change current array to list's sub-elements
[targetStack addObject:target]; // append sub-element array to target stack so it can be popped off afterwards
resourceElement = nil; // don't add item to it's own sub-elements later!
break;
case 'LSTE':
// bug: if there is a LSTE without a preceeding LSTB or LSTC this will crash
[targetStack removeLastObject]; // pop off current target from stack
target = [targetStack lastObject]; // set current target to whatever was second from top on the stack
resourceElement = nil; // list end items are not needed in a resource array
if(n < lim) n -= c;
c = 0;
break;
/* Cxxx, Hxxx or P0xx */
default:
// bug: should look for Cxxx, Hxxx or P0xx and complain if it's something else (an unknown type)!!
{/* long lengthStr = (type & 0x00FFFFFF) << 8;
unsigned long length = strtoul((char *) &lengthStr, nil, 10);
char *lengthStr = (type & 0x00FFFFFF) & (3 << 24);
unsigned long length;
StringToNum(lengthStr, &length);
NSLog(@"error, '%@' is unsupported, skipping %d bytes", [resourceElement type], length);
resourceElement = nil; // relies on element being previously autoreleased to avoid a leak
position += length;*/
} break;
} // end template element type switch
if(resourceElement)
{
NSLog(@"adding %@", resourceElement);
[target addObject:resourceElement];
}
} // end while position < size
NSLog([target description]);
}
- (void)createUI
{
// iterate through res (the resource element array) creating fields
[self enumerateElements:res];
}
- (void)enumerateElements:(NSMutableArray *)elements
{
// iterate through the array of resource elements, creating fields
Element *currentResourceElement;
NSEnumerator *enumerator = [elements objectEnumerator];
NSLog(@"elements in resource array = %d", [elements count]);
while(currentResourceElement = [enumerator nextObject])
{
// if element is a container (subelements != nil), iterate inside it first
/* if([currentResourceElement subelements])
{
// bug: need to indent view right
[self enumerateElements:[currentResourceElement subelements]];
// bug: need to remove indentation
}
else // element is normal
*/ {
/* NSFormCell *newField = [[NSFormCell alloc] initTextCell:[currentResourceElement label]];
[fieldsMatrix addRowWithCells:[NSArray arrayWithObject:[newField autorelease]]]; */
NSLog([currentResourceElement description]);
}
}
}
- (void)resourceDataDidChange:(NSNotification *)notification
{
// ensure it's our resource which got changed (should always be true, we don't register for notifications on other resource objects)
if([notification object] == (id)resource)
[self refreshData:[resource data]];
}
- (void)refreshData:(NSData *)data;
{
// put data from resource into correct fields
}
- (id)resource
{
return resource;
}
- (NSData *)data
{
return [resource data];
}
@end

View File

@ -127,7 +127,7 @@
[super windowDidLoad];
// set the window's title
[[self window] setTitle:[resource nameForEditorWindow]];
[[self window] setTitle:[resource defaultWindowTitle]];
[self reloadResData];

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>Bitmap Editor</string>
<key>CFBundleIdentifier</key>
<string>com.ulikusterer.resknife.bitmapeditor</string>
<string>com.ulikusterer.resknife.bitmap-editor</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>