diff --git a/Cocoa/Classes/OutlineViewDelegate.h b/Cocoa/Classes/OutlineViewDelegate.h index a03db5b..e652e34 100644 --- a/Cocoa/Classes/OutlineViewDelegate.h +++ b/Cocoa/Classes/OutlineViewDelegate.h @@ -17,3 +17,10 @@ int compareResourcesAscending( Resource *r1, Resource *r2, void *context ); int compareResourcesDescending( Resource *r1, Resource *r2, void *context ); @end + +@interface NSOutlineView (OutlineSortView) +- (void)swapForOutlineSortView; +@end + +@interface OutlineSortView : NSOutlineView +@end \ No newline at end of file diff --git a/Cocoa/Classes/OutlineViewDelegate.m b/Cocoa/Classes/OutlineViewDelegate.m index 384a84c..5a86d23 100644 --- a/Cocoa/Classes/OutlineViewDelegate.m +++ b/Cocoa/Classes/OutlineViewDelegate.m @@ -8,16 +8,20 @@ - (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn { - NSArray *oldResources = [(ResourceDataSource *)[tableView dataSource] resources]; NSArray *newResources; - - NSLog( @"Clicked table column: %@", tableColumn ); + NSArray *oldResources = [(ResourceDataSource *)[tableView dataSource] resources]; // sort the array - if( ![[tableView indicatorImageInTableColumn:tableColumn] isFlipped] ) + NSImage *indicator = [tableView indicatorImageInTableColumn:tableColumn]; + NSImage *upArrow = [NSTableView _defaultTableHeaderSortImage]; + if( indicator == upArrow ) + { newResources = [oldResources sortedArrayUsingFunction:compareResourcesAscending context:(void*)[tableColumn identifier]]; + } else + { newResources = [oldResources sortedArrayUsingFunction:compareResourcesDescending context:(void*)[tableColumn identifier]]; + } // swap new array for old one [(ResourceDataSource *)[tableView dataSource] setResources:[NSMutableArray arrayWithArray:newResources]]; @@ -30,9 +34,15 @@ int compareResourcesAscending( Resource *r1, Resource *r2, void *context ) SEL sel = NSSelectorFromString(key); if( [key isEqualToString:@"name"] || [key isEqualToString:@"type"] ) + { + // compare two NSStrings (case-insensitive) return [(NSString *)[r1 performSelector:sel] caseInsensitiveCompare: (NSString *)[r2 performSelector:sel]]; + } else + { + // compare two NSNumbers (or any other class) return [(NSNumber *)[r1 performSelector:sel] compare: (NSNumber *)[r2 performSelector:sel]]; + } } int compareResourcesDescending( Resource *r1, Resource *r2, void *context ) @@ -41,9 +51,15 @@ int compareResourcesDescending( Resource *r1, Resource *r2, void *context ) SEL sel = NSSelectorFromString(key); if( [key isEqualToString:@"name"] || [key isEqualToString:@"type"] ) + { + // compare two NSStrings (case-insensitive) return -1 * [(NSString *)[r1 performSelector:sel] caseInsensitiveCompare: (NSString *)[r2 performSelector:sel]]; + } else + { + // compare two NSNumbers (or any other class) return -1 * [(NSNumber *)[r1 performSelector:sel] compare: (NSNumber *)[r2 performSelector:sel]]; + } } - (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item @@ -79,4 +95,109 @@ int compareResourcesDescending( Resource *r1, Resource *r2, void *context ) } } +@end + +@implementation NSOutlineView (OutlineSortView) + +- (void)swapForOutlineSortView +{ + isa = [OutlineSortView class]; +} + +@end + +@implementation OutlineSortView + +- (void)keyDown:(NSEvent *)event +{ + if( [self selectedRow] != -1 && [[event characters] isEqualToString:[NSString stringWithCString:"\r"]] ) + [self editColumn:0 row:[self selectedRow] withEvent:nil select:YES]; + else [super keyDown:event]; +} + +- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)selector +{ + // pressed return, end editing + if( selector == @selector(insertNewline:) ) + { + [[self window] makeFirstResponder:self]; + [self abortEditing]; + return YES; + } + + // pressed tab, move to next editable field + else if( selector == @selector(insertTab:) ) + { + int newColumn = ([self editedColumn] +1) % [self numberOfColumns]; + NSString *newColIdentifier = [[[self tableColumns] objectAtIndex:newColumn] identifier]; + if( [newColIdentifier isEqualToString:@"size"] || [newColIdentifier isEqualToString:@"attributes"] ) + { + newColumn = (newColumn +1) % [self numberOfColumns]; + newColIdentifier = [[[self tableColumns] objectAtIndex:newColumn] identifier]; + if( [newColIdentifier isEqualToString:@"size"] || [newColIdentifier isEqualToString:@"attributes"] ) + newColumn = (newColumn +1) % [self numberOfColumns]; + } + + [self editColumn:newColumn row:[self selectedRow] withEvent:nil select:YES]; + return YES; + } + + // pressed shift-tab, move to previous editable field + else if( selector == @selector(insertBacktab:) ) + { + int newColumn = ([self editedColumn] + [self numberOfColumns] -1) % [self numberOfColumns]; + NSString *newColIdentifier = [[[self tableColumns] objectAtIndex:newColumn] identifier]; + if( [newColIdentifier isEqualToString:@"size"] || [newColIdentifier isEqualToString:@"attributes"] ) + { + newColumn = (newColumn + [self numberOfColumns] -1) % [self numberOfColumns]; + newColIdentifier = [[[self tableColumns] objectAtIndex:newColumn] identifier]; + if( [newColIdentifier isEqualToString:@"size"] || [newColIdentifier isEqualToString:@"attributes"] ) + newColumn = (newColumn + [self numberOfColumns] -1) % [self numberOfColumns]; + } + + [self editColumn:newColumn row:[self selectedRow] withEvent:nil select:YES]; + return YES; + } + + return NO; +} + +//- (void)_sendDelegateDidMouseDownInHeader:(int)columnIndex +- (void)_sendDelegateDidClickColumn:(int)columnIndex +{ + NSTableColumn *tableColumn = [[self tableColumns] objectAtIndex:columnIndex]; + NSImage *indicator = [self indicatorImageInTableColumn:tableColumn]; + NSImage *upArrow = [NSTableView _defaultTableHeaderSortImage]; + NSImage *downArrow = [NSTableView _defaultTableHeaderReverseSortImage]; + if( indicator ) + { + // column already selected + if( indicator == upArrow ) + [self setIndicatorImage:downArrow inTableColumn:tableColumn]; + else [self setIndicatorImage:upArrow inTableColumn:tableColumn]; + } + else + { + // new column selected + if( [self highlightedTableColumn] != nil ) + { + // if there is an existing selection, clear it's image + [self setIndicatorImage:nil inTableColumn:[self highlightedTableColumn]]; + } + + if( [[tableColumn identifier] isEqualToString:@"name"] || [[tableColumn identifier] isEqualToString:@"type"] ) + { + // sort name and type columns ascending by default + [self setIndicatorImage:upArrow inTableColumn:tableColumn]; + } + else + { + // sort all other columns descending by default + [self setIndicatorImage:downArrow inTableColumn:tableColumn]; + } + [self setHighlightedTableColumn:tableColumn]; + } + [[self delegate] tableView:self didClickTableColumn:tableColumn]; +} + @end \ No newline at end of file diff --git a/Cocoa/Classes/ResourceDataSource.m b/Cocoa/Classes/ResourceDataSource.m index 32fbd88..4e29ae3 100644 --- a/Cocoa/Classes/ResourceDataSource.m +++ b/Cocoa/Classes/ResourceDataSource.m @@ -109,7 +109,9 @@ NSString *DataSourceDidRemoveResourceNotification = @"DataSourceDidRemoveResourc { #pragma unused( outlineView ) NSString *identifier = [tableColumn identifier]; - [item takeValue:object forKey:identifier]; + if( [identifier isEqualToString:@"resID"] ) + [item takeValue:[NSNumber numberWithInt:[object intValue]] forKey:identifier]; + else [item takeValue:object forKey:identifier]; } /* ACCESSORS */ diff --git a/Cocoa/Classes/ResourceDocument.m b/Cocoa/Classes/ResourceDocument.m index 32a54d8..d000a65 100644 --- a/Cocoa/Classes/ResourceDocument.m +++ b/Cocoa/Classes/ResourceDocument.m @@ -4,6 +4,7 @@ #import "Resource.h" #import "PrefsWindowController.h" #import "CreateResourceSheetController.h" +#import "OutlineViewDelegate.h" #import "NSOutlineView-SelectedItems.h" #import "ResKnifePluginProtocol.h" @@ -62,6 +63,9 @@ extern NSString *RKResourcePboardType; // set outline view's inter-cell psacing to zero to avoid getting gaps between blue bits [outlineView setIntercellSpacing:NSMakeSize(0,0)]; + [outlineView swapForOutlineSortView]; + [outlineView setTarget:self]; + [outlineView setDoubleAction:@selector(openResources:)]; // register for resource will change notifications (for undo management) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceNameWillChange:) name:ResourceNameWillChangeNotification object:nil]; diff --git a/Cocoa/Classes/ResourceNameCell.m b/Cocoa/Classes/ResourceNameCell.m index 2d3528e..679a6dc 100644 --- a/Cocoa/Classes/ResourceNameCell.m +++ b/Cocoa/Classes/ResourceNameCell.m @@ -124,7 +124,7 @@ // get the superclass to draw the text stuff [super drawWithFrame:cellFrame inView:controlView]; } - +/* - (NSSize)cellSize { NSSize cellSize = [super cellSize]; @@ -132,5 +132,5 @@ cellSize.width += (image? [image size].width:0) + 3; return cellSize; } - +*/ @end diff --git a/Cocoa/English.lproj/ResourceDocument.nib/info.nib b/Cocoa/English.lproj/ResourceDocument.nib/info.nib index 4d653da..56fe40c 100644 --- a/Cocoa/English.lproj/ResourceDocument.nib/info.nib +++ b/Cocoa/English.lproj/ResourceDocument.nib/info.nib @@ -1,13 +1,13 @@ - - + + IBDocumentLocation - 306 119 486 325 0 0 1280 1002 + 75 336 486 325 0 0 1024 746 IBFramework Version - 248.0 + 286.0 IBSystem Version - 5Q45 + 6C115 IBUserGuides CreateResourceSheet diff --git a/Cocoa/English.lproj/ResourceDocument.nib/objects.nib b/Cocoa/English.lproj/ResourceDocument.nib/objects.nib index 33cc3f2..361a601 100644 Binary files a/Cocoa/English.lproj/ResourceDocument.nib/objects.nib and b/Cocoa/English.lproj/ResourceDocument.nib/objects.nib differ diff --git a/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/classes.nib b/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/classes.nib index 6846315..e40a0f2 100644 --- a/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/classes.nib +++ b/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/classes.nib @@ -14,12 +14,15 @@ SUPERCLASS = NSObject; }, { + ACTIONS = {showFind = id; }; CLASS = HexWindowController; LANGUAGE = ObjC; OUTLETS = { ascii = NSTextView; + asciiScroll = NSScrollView; hex = NSTextView; hexDelegate = HexEditorDelegate; + hexScroll = NSScrollView; message = NSTextField; offset = NSTextView; }; diff --git a/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/info.nib b/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/info.nib index f0a9681..2380e24 100644 --- a/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/info.nib +++ b/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/info.nib @@ -1,12 +1,16 @@ - - + + IBDocumentLocation - 539 135 422 584 0 0 1280 1002 + 137 230 413 304 0 0 1024 746 IBFramework Version - 263.2 + 286.0 + IBOpenObjects + + 6 + IBSystem Version - 5Q125 + 6C115 diff --git a/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/objects.nib b/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/objects.nib index cdf347f..826e49a 100644 Binary files a/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/objects.nib and b/Cocoa/Plug-Ins/Hex Editor/English.lproj/HexWindow.nib/objects.nib differ diff --git a/Cocoa/Plug-Ins/Hex Editor/HexEditorDelegate.m b/Cocoa/Plug-Ins/Hex Editor/HexEditorDelegate.m index 35b29e9..4a66392 100644 --- a/Cocoa/Plug-Ins/Hex Editor/HexEditorDelegate.m +++ b/Cocoa/Plug-Ins/Hex Editor/HexEditorDelegate.m @@ -116,6 +116,23 @@ return representation; } +- (NSString *)hexToAscii:(NSData *)data; +{ + unsigned long bytesEncoded = ([data length] + 1) / 3; + char *buffer = (char *) malloc( bytesEncoded ), hex1, hex2, ascii; + for( int i = 0; i < bytesEncoded; i++ ) + { + hex1 = ((char *)[data bytes])[3*i]; + hex2 = ((char *)[data bytes])[3*i+1]; + hex1 -= (hex1 < 'A')? 0x30 : 0x37; + hex2 -= (hex2 < 'A')? 0x30 : 0x37; + hex1 <<= 4; + ascii = hex1 + hex2; + buffer[i] = ascii; + } + return [NSString stringWithCString:buffer length:bytesEncoded]; +} + /* delegation methods */ - (NSRange)textView:(NSTextView *)textView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange diff --git a/Cocoa/Plug-Ins/Hex Editor/HexTextView.h b/Cocoa/Plug-Ins/Hex Editor/HexTextView.h index cf0f392..59bd750 100644 --- a/Cocoa/Plug-Ins/Hex Editor/HexTextView.h +++ b/Cocoa/Plug-Ins/Hex Editor/HexTextView.h @@ -5,9 +5,9 @@ @interface HexTextView : NSTextView { } -- (void)pasteAsASCII:(id)sender; -- (void)pasteAsHex:(id)sender; -- (void)pasteAsUnicode:(id)sender; +- (IBAction)pasteAsASCII:(id)sender; +- (IBAction)pasteAsHex:(id)sender; +- (IBAction)pasteAsUnicode:(id)sender; - (void)editData:(NSData *)data replaceBytesInRange:(NSRange)range withData:(NSData *)newData; @end diff --git a/Cocoa/Plug-Ins/Hex Editor/HexTextView.m b/Cocoa/Plug-Ins/Hex Editor/HexTextView.m index dc0ee65..0f3d498 100644 --- a/Cocoa/Plug-Ins/Hex Editor/HexTextView.m +++ b/Cocoa/Plug-Ins/Hex Editor/HexTextView.m @@ -76,8 +76,6 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item { -// NSMenuItem *pasteItem = [[item menu] itemAtIndex:[[item menu] indexOfItemWithTarget:nil andAction:@selector(paste:)]]; - // paste submenu if( [item action] == @selector(paste:) ) { @@ -87,7 +85,33 @@ else return [super validateMenuItem:item]; } -- (void)paste:(id)sender +- (IBAction)cut:(id)sender +{ + [self copy:sender]; + [self clear:sender]; +} + +- (IBAction)copy:(id)sender +{ + NSRange selection = [self rangeForUserTextChange], byteSelection; + NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSGeneralPboard]; + + // get selection range + if( self == (id) [[self delegate] hex] ) + byteSelection = [[self delegate] byteRangeFromHexRange:selection]; + else if( self == (id) [[self delegate] ascii] ) + byteSelection = [[self delegate] byteRangeFromAsciiRange:selection]; + else + { + NSLog( @"Pasting text into illegal object: %@", self ); + return; + } + + [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self]; + [pb setData:[[[[self window] windowController] data] subdataWithRange:byteSelection] forType:NSStringPboardType]; +} + +- (IBAction)paste:(id)sender { // be 'smart' - determine if the pasted text is in hex format, such as "5F 3E 04 8E" or ascii. // what about unicode? should I paste "00 63 00 64" as "63 64" ("Paste As ASCII" submenu item)? @@ -110,7 +134,7 @@ [self editData:[[[self window] windowController] data] replaceBytesInRange:byteSelection withData:[pb dataForType:NSStringPboardType]]; } -- (void)pasteAsASCII:(id)sender +- (IBAction)pasteAsASCII:(id)sender { NSRange selection = [self rangeForUserTextChange], byteSelection; NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSGeneralPboard]; @@ -130,7 +154,7 @@ [self editData:[[[self window] windowController] data] replaceBytesInRange:byteSelection withData:[pb dataForType:NSStringPboardType]]; } -- (void)pasteAsHex:(id)sender +- (IBAction)pasteAsHex:(id)sender { NSRange selection = [self rangeForUserTextChange], byteSelection; NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSGeneralPboard]; @@ -153,7 +177,7 @@ } } -- (void)pasteAsUnicode:(id)sender +- (IBAction)pasteAsUnicode:(id)sender { NSRange selection = [self rangeForUserTextChange], byteSelection; NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSGeneralPboard]; @@ -171,23 +195,85 @@ if( [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] ) { - NSData *unicodeData = [[pb stringForType:NSStringPboardType] dataUsingEncoding:NSUnicodeStringEncoding]; + NSData *unicodeData = [[NSString stringWithUTF8String:[[pb dataForType:NSStringPboardType] bytes]] dataUsingEncoding:NSUnicodeStringEncoding]; [self editData:[[[self window] windowController] data] replaceBytesInRange:byteSelection withData:unicodeData]; } } -- (void)clear:(id)sender +- (IBAction)clear:(id)sender { NSRange selection = [self rangeForUserTextChange]; if( selection.length > 0 ) [self delete:sender]; } -- (void)delete:(id)sender +- (IBAction)delete:(id)sender { [self deleteBackward:sender]; } +/* Dragging routines */ + +- (unsigned int)_insertionGlyphIndexForDrag:(id )sender +{ + int charIndex = [super _insertionGlyphIndexForDrag:sender]; + if( self == [[self delegate] hex] ) + charIndex -= charIndex % 3; + return charIndex; +} + +- (unsigned int)draggingSourceOperationMaskForLocal:(BOOL)isLocal +{ + return NSDragOperationCopy | NSDragOperationMove | NSDragOperationGeneric; +} + +static NSRange draggedRange; + +- (void)draggedImage:(NSImage *)image beganAt:(NSPoint)point +{ + draggedRange = [self rangeForUserTextChange]; +} + +- (void)draggedImage:(NSImage *)image endedAt:(NSPoint)point operation:(NSDragOperation)operation +{ + if( operation == NSDragOperationMove ) + { + NSRange selection = [self rangeForUserTextChange]; + [self editData:[[[self window] windowController] data] replaceBytesInRange:draggedRange withData:[NSData data]]; + + // set the new selection/insertion point + if( selection.location > draggedRange.location ) + selection.location -= draggedRange.length; + [self setSelectedRange:selection]; + } +} + +- (NSDragOperation)draggingUpdated:(id )sender +{ + [super draggingUpdated:sender]; // ignore return value + if( [sender draggingSource] == [[self delegate] hex] || [sender draggingSource] == [[self delegate] ascii] ) + return NSDragOperationMove; + else return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id )sender +{ + NSRange range; + NSPasteboard *pb = [sender draggingPasteboard]; + NSData *pastedData = [pb dataForType:NSStringPboardType]; + int charIndex = [self _insertionGlyphIndexForDrag:sender]; + if( self == [[self delegate] hex] ) charIndex /= 3; + if( [sender draggingSource] == [[self delegate] hex] ) + pastedData = [[[self delegate] hexToAscii:pastedData] dataUsingEncoding:NSASCIIStringEncoding]; + [self editData:[[[self window] windowController] data] replaceBytesInRange:NSMakeRange(charIndex,0) withData:pastedData]; + return YES; +} + +- (void)concludeDragOperation:(id )sender +{ + // override and do nothing +} + /* NSResponder overrides */ - (void)insertText:(NSString *)string diff --git a/Cocoa/Plug-Ins/Hex Editor/HexWindowController.h b/Cocoa/Plug-Ins/Hex Editor/HexWindowController.h index 3182c5d..36acdea 100644 --- a/Cocoa/Plug-Ins/Hex Editor/HexWindowController.h +++ b/Cocoa/Plug-Ins/Hex Editor/HexWindowController.h @@ -5,14 +5,19 @@ #import "ResKnifePluginProtocol.h" #import "ResKnifeResourceProtocol.h" +#define kWindowStepWidthPerChar 28 +#define kWindowStepCharsPerStep 1 + @interface HexWindowController : NSWindowController { IBOutlet HexEditorDelegate *hexDelegate; - IBOutlet NSTextView *ascii; - IBOutlet NSTextView *hex; - IBOutlet NSTextView *offset; - IBOutlet NSTextField *message; - IBOutlet NSMenu *pasteSubmenu; + IBOutlet NSScrollView *asciiScroll; + IBOutlet NSScrollView *hexScroll; + IBOutlet NSTextView *ascii; + IBOutlet NSTextView *hex; + IBOutlet NSTextView *offset; + IBOutlet NSTextField *message; + IBOutlet NSMenu *pasteSubmenu; NSUndoManager *undoManager; id resource; diff --git a/Cocoa/Plug-Ins/Hex Editor/HexWindowController.m b/Cocoa/Plug-Ins/Hex Editor/HexWindowController.m index c29329a..14dff00 100644 --- a/Cocoa/Plug-Ins/Hex Editor/HexWindowController.m +++ b/Cocoa/Plug-Ins/Hex Editor/HexWindowController.m @@ -53,6 +53,8 @@ // 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 // 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(resourceNameDidChange:) name:ResourceNameDidChangeNotification object:resource]; @@ -67,6 +69,18 @@ [self showWindow:self]; } +- (void)windowDidResize:(NSNotification *)notification +{ + int width = [[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]]]; + [hexScroll setFrameSize:NSMakeSize( (bytesPerRow * 21) + 5, [hexScroll frame].size.height)]; + [asciiScroll setFrameOrigin:NSMakePoint( (bytesPerRow * 21) + 95, 20)]; + [asciiScroll setFrameSize:NSMakeSize( (bytesPerRow * 7) + 28, [asciiScroll frame].size.height)]; +} + - (void)windowDidBecomeKey:(NSNotification *)notification { // swap paste: menu item for my own paste submenu