mirror of
https://github.com/nickshanks/ResKnife.git
synced 2024-08-30 13:28:58 +00:00
503 lines
16 KiB
Objective-C
503 lines
16 KiB
Objective-C
#import "HexTextView.h"
|
|
|
|
@implementation HexTextView
|
|
|
|
- (void)drawRect:(NSRect)rect
|
|
{
|
|
[super drawRect:rect];
|
|
/* if( [[self window] isKeyWindow] && [[self window] firstResponder] == self )
|
|
{
|
|
NSSetFocusRingStyle( NSFocusRingOnly );
|
|
[self setKeyboardFocusRingNeedsDisplayInRect:rect];
|
|
}*/
|
|
|
|
/* [super drawRect:rect];
|
|
if( [[self window] isKeyWindow] )
|
|
{
|
|
NSResponder *responder = [[self window] firstResponder];
|
|
if( [responder isKindOfClass:[NSView class]] && [(NSView *)responder isDescendantOf:self])
|
|
{
|
|
NSSetFocusRingStyle( NSFocusRingOnly );
|
|
NSRectFill( rect );
|
|
}
|
|
}
|
|
[self setKeyboardFocusRingNeedsDisplayInRect:rect];*/
|
|
}
|
|
|
|
- (void)setSelectedRange:(NSRange)charRange affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)flag
|
|
{
|
|
NSRange newRange = charRange;
|
|
|
|
// select whole bytes at a time (only if selecting in hex!)
|
|
if( self == (id) [[self delegate] hex] )
|
|
{
|
|
// move selection offset to beginning of byte
|
|
newRange.location -= (charRange.location % 3);
|
|
newRange.length += (charRange.location % 3);
|
|
|
|
// set selection length to whole number of bytes
|
|
if( charRange.length != 0 )
|
|
newRange.length -= (newRange.length % 3) -2;
|
|
else newRange.length = 0;
|
|
|
|
// move insertion point to next byte if needs be
|
|
if( newRange.location == charRange.location -1 && newRange.length == 0 )
|
|
newRange.location += 3;
|
|
}
|
|
|
|
// select return character if selecting ascii
|
|
#if( 0 ) // no longer necessary as there's a one-to-one for ascii, and the thing wraps properly instead :-)
|
|
else if( self == (id) [[self delegate] ascii] )
|
|
{
|
|
// if ascii selection goes up to sixteenth byte on last line, select return character too
|
|
if( (charRange.length + charRange.location) % 17 == 16)
|
|
{
|
|
// if selection is zero bytes long, move insertion point to character after return
|
|
if( charRange.length == 0 )
|
|
{
|
|
// if moving back from first byte of line to previous line, skip return char
|
|
NSRange selected = [self selectedRange];
|
|
if( (selected.length + selected.location) % 17 == 0 )
|
|
newRange.location -= 1;
|
|
else newRange.location += 1;
|
|
}
|
|
else newRange.length += 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// call the superclass to update the selection
|
|
[super setSelectedRange:newRange affinity:affinity stillSelecting:NO];
|
|
}
|
|
|
|
/* NSText overrides */
|
|
|
|
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
|
{
|
|
// paste submenu
|
|
if( [item action] == @selector(paste:) )
|
|
{
|
|
NSMenu *editMenu = [[item menu] supermenu];
|
|
[[editMenu itemAtIndex:[editMenu indexOfItemWithSubmenu:[item menu]]] setEnabled:[super validateMenuItem:item]];
|
|
}
|
|
return [super validateMenuItem:item];
|
|
}
|
|
|
|
- (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)?
|
|
|
|
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;
|
|
}
|
|
|
|
if( [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] )
|
|
[self editData:[[[self window] windowController] data] replaceBytesInRange:byteSelection withData:[pb dataForType:NSStringPboardType]];
|
|
}
|
|
|
|
- (IBAction)pasteAsASCII:(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;
|
|
}
|
|
|
|
if( [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] )
|
|
[self editData:[[[self window] windowController] data] replaceBytesInRange:byteSelection withData:[pb dataForType:NSStringPboardType]];
|
|
}
|
|
|
|
- (IBAction)pasteAsHex:(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;
|
|
}
|
|
|
|
if( [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] )
|
|
{
|
|
NSString *hexString = [[self delegate] hexRepresentation:[pb dataForType:NSStringPboardType]];
|
|
[self editData:[[[self window] windowController] data] replaceBytesInRange:byteSelection withData:[NSData dataWithBytes:[hexString cString] length:[hexString cStringLength]]];
|
|
}
|
|
}
|
|
|
|
- (IBAction)pasteAsUnicode:(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;
|
|
}
|
|
|
|
if( [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] )
|
|
{
|
|
NSData *unicodeData = [[NSString stringWithUTF8String:[[pb dataForType:NSStringPboardType] bytes]] dataUsingEncoding:NSUnicodeStringEncoding];
|
|
[self editData:[[[self window] windowController] data] replaceBytesInRange:byteSelection withData:unicodeData];
|
|
}
|
|
}
|
|
|
|
- (IBAction)clear:(id)sender
|
|
{
|
|
NSRange selection = [self rangeForUserTextChange];
|
|
if( selection.length > 0 )
|
|
[self delete:sender];
|
|
}
|
|
|
|
- (IBAction)delete:(id)sender
|
|
{
|
|
[self deleteBackward:sender];
|
|
}
|
|
|
|
/* Dragging routines */
|
|
|
|
- (unsigned int)_insertionGlyphIndexForDrag:(id <NSDraggingInfo>)sender
|
|
{
|
|
unsigned int charIndex = [super _insertionGlyphIndexForDrag:sender];
|
|
if( self == [[self delegate] hex] )
|
|
charIndex -= charIndex % 3;
|
|
return charIndex;
|
|
}
|
|
|
|
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
|
|
{
|
|
/* if( isLocal ) return NSDragOperationEvery;
|
|
else return NSDragOperationCopy;
|
|
*/ 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
|
|
selection.location -= draggedRange.length;
|
|
selection.length = draggedRange.length;
|
|
if( selection.location > draggedRange.location )
|
|
selection.location -= draggedRange.length;
|
|
[self setSelectedRange:selection];
|
|
}*/
|
|
}
|
|
|
|
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)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 <NSDraggingInfo>)sender
|
|
{
|
|
// get the insertion point location
|
|
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] )
|
|
pastedData = [[[self delegate] hexToAscii:pastedData] dataUsingEncoding:NSASCIIStringEncoding];
|
|
|
|
if( [sender draggingSource] == [[self delegate] hex] || [sender draggingSource] == [[self delegate] ascii] )
|
|
// if( operation == NSDragOperationMove )
|
|
{
|
|
NSRange deleteRange = draggedRange;
|
|
if( self == [[self delegate] hex] )
|
|
{
|
|
deleteRange.location /= 3;
|
|
deleteRange.length += 1;
|
|
deleteRange.length /= 3;
|
|
}
|
|
|
|
// if moving the data, remove the selection from the data
|
|
[self editData:[[[self window] windowController] data] replaceBytesInRange:deleteRange withData:[NSData data]];
|
|
|
|
// compensate for already removing the dragged data
|
|
if( charIndex > draggedRange.location )
|
|
charIndex -= draggedRange.length;
|
|
}
|
|
|
|
// insert data at insertion point
|
|
if( self == [[self delegate] hex] ) charIndex /= 3;
|
|
[self editData:[[[self window] windowController] data] replaceBytesInRange:NSMakeRange(charIndex,0) withData:pastedData];
|
|
|
|
// set the new selection/insertion point
|
|
selection = [self rangeForUserTextChange];
|
|
selection.location -= draggedRange.length;
|
|
selection.length = draggedRange.length;
|
|
[self setSelectedRange:selection];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
|
|
{
|
|
// override and do nothing
|
|
}
|
|
|
|
/* NSResponder overrides */
|
|
|
|
- (void)insertText:(NSString *)string
|
|
{
|
|
NSRange selection = [self rangeForUserTextChange], byteSelection;
|
|
NSMutableData *data = [[[[self window] windowController] data] mutableCopy];
|
|
NSData *replaceData = [NSData dataWithBytes:[string cString] length:[string cStringLength]];
|
|
|
|
// 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( @"Inserting text into illegal object: %@", self );
|
|
return;
|
|
}
|
|
|
|
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( i = 0; i < [string cStringLength]; i++ )
|
|
{
|
|
char typedChar = [string characterAtIndex:i];
|
|
if( typedChar >= 0x30 && typedChar <= 0x39 ) typedChar -= 0x30; // 0 to 9
|
|
else if( typedChar >= 0x41 && typedChar <= 0x46 ) typedChar -= 0x37; // A to F
|
|
else if( typedChar >= 0x61 && typedChar <= 0x66 ) typedChar -= 0x57; // a to f
|
|
else return;
|
|
|
|
if( [[self delegate] editedLow] ) // edited low bits already
|
|
{
|
|
// select & retrieve old byte so it gets replaced
|
|
char prevByte;
|
|
byteSelection = NSMakeRange(byteSelection.location -1, 1);
|
|
[data getBytes:&prevByte range:byteSelection];
|
|
|
|
// shift typed char into high bits and add new low char
|
|
prevByte <<= 4; // store high bit
|
|
prevByte += typedChar & 0x0F; // add low bit
|
|
replaceData = [NSData dataWithBytes:&prevByte length:1];
|
|
[[self delegate] setEditedLow:NO];
|
|
}
|
|
else // editing low bits
|
|
{
|
|
// put typed char into low bits
|
|
typedChar &= 0x0F;
|
|
replaceData = [NSData dataWithBytes:&typedChar length:1];
|
|
[[self delegate] setEditedLow:YES];
|
|
}
|
|
}
|
|
}
|
|
|
|
// replace bytes (updates views implicitly, records an undo)
|
|
[self editData:data replaceBytesInRange:byteSelection withData:replaceData];
|
|
[data release];
|
|
|
|
// set the new selection/insertion point
|
|
byteSelection.location++;
|
|
byteSelection.length = 0;
|
|
if( self == (id) [[self delegate] hex] )
|
|
selection = [[self delegate] hexRangeFromByteRange:byteSelection];
|
|
else if( self == (id) [[self delegate] ascii] )
|
|
selection = [[self delegate] asciiRangeFromByteRange:byteSelection];
|
|
[self setSelectedRange:selection];
|
|
}
|
|
|
|
- (IBAction)deleteBackward:(id)sender
|
|
{
|
|
NSRange selection = [self rangeForUserTextChange], byteSelection;
|
|
NSMutableData *data = [[[[self window] windowController] data] mutableCopy];
|
|
|
|
// 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( @"Inserting text into illegal object: %@", self );
|
|
return;
|
|
}
|
|
|
|
// adjust selection if is insertion point
|
|
if( byteSelection.length == 0 && selection.location > 0 )
|
|
{
|
|
byteSelection.location -= 1;
|
|
byteSelection.length = 1;
|
|
}
|
|
|
|
// replace bytes (updates views implicitly)
|
|
[self editData:data replaceBytesInRange:byteSelection withData:[NSData data]];
|
|
[data release];
|
|
|
|
// set the new selection/insertion point
|
|
if( selection.length == 0 )
|
|
selection.location -= 1;
|
|
else selection.length = 0;
|
|
[self setSelectedRange:selection];
|
|
}
|
|
|
|
- (IBAction)deleteForward:(id)sender
|
|
{
|
|
NSRange selection = [self rangeForUserTextChange], byteSelection;
|
|
NSMutableData *data = [[[[self window] windowController] data] mutableCopy];
|
|
|
|
// 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( @"Inserting text into illegal object: %@", self );
|
|
return;
|
|
}
|
|
|
|
// adjust selection if is insertion point
|
|
if( byteSelection.length == 0 && selection.location < [[self string] length] -1 )
|
|
byteSelection.length = 1;
|
|
|
|
// replace bytes (updates views implicitly)
|
|
[self editData:data replaceBytesInRange:byteSelection withData:[NSData data]];
|
|
[data release];
|
|
|
|
// set the new selection/insertion point
|
|
selection.length = 0;
|
|
[self setSelectedRange:selection];
|
|
}
|
|
|
|
- (IBAction)transpose:(id)sender
|
|
{
|
|
;
|
|
}
|
|
|
|
- (IBAction)deleteWordBackward:(id)sender
|
|
{
|
|
[self deleteBackward:sender];
|
|
}
|
|
|
|
- (IBAction)deleteWordForward:(id)sender
|
|
{
|
|
[self deleteForward:sender];
|
|
}
|
|
|
|
- (IBAction)transposeWords:(id)sender
|
|
{
|
|
[self transpose:sender];
|
|
}
|
|
|
|
- (void)editData:(NSData *)data replaceBytesInRange:(NSRange)range withData:(NSData *)newBytes
|
|
{
|
|
// save data we're about to replace so we can restore it in an undo
|
|
NSRange newRange = NSMakeRange( range.location, [newBytes length] );
|
|
NSMutableData *newData = [NSMutableData dataWithData:data];
|
|
NSMutableData *oldBytes = [[data subdataWithRange:range] retain]; // bug: memory leak, need to release somewhere (call -[NSInvocation retainArguments] instead?)
|
|
|
|
// manipulate undo stack to concatenate multiple undos
|
|
BOOL closeUndoGroup = NO;
|
|
id undoStack = nil; // object of class _NSUndoStack
|
|
if( ![[[self window] undoManager] isUndoing] )
|
|
undoStack = [[[self window] undoManager] _undoStack];
|
|
|
|
if( undoStack && (int)[undoStack count] > 0 && [[[self window] undoManager] groupingLevel] == 0 )
|
|
{
|
|
[undoStack popUndoObject]; // pop endUndoGrouping item
|
|
closeUndoGroup = YES;
|
|
}
|
|
|
|
// replace bytes, save new data, updates views & selection and mark doc as edited
|
|
[newData replaceBytesInRange:range withBytes:[newBytes bytes] length:[newBytes length]];
|
|
[[(HexWindowController *)[[self window] windowController] resource] setData:newData];
|
|
[self setSelectedRange:NSMakeRange(range.location + [newBytes length], 0)];
|
|
// [[self window] setDocumentEdited:YES]; // moved to window controller's -resourceDataDidChange: notification method
|
|
|
|
// record undo with new data object
|
|
[[[[self window] undoManager] prepareWithInvocationTarget:self] editData:newData replaceBytesInRange:newRange withData:oldBytes];
|
|
[[[self window] undoManager] setActionName:NSLocalizedString(@"Typing", nil)];
|
|
if( closeUndoGroup )
|
|
[[[self window] undoManager] endUndoGrouping];
|
|
// NSLog( @"%@", undoStack );
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSTextView (HexTextView)
|
|
|
|
- (void)swapForHexTextView
|
|
{
|
|
isa = [HexTextView class];
|
|
}
|
|
|
|
@end |