Added toolbar to ResKnife, fixed HexEditor selection bugs.

This commit is contained in:
Nicholas Shanks 2002-02-06 20:57:56 +00:00
parent 85b7550e17
commit 734fad59e5
12 changed files with 330 additions and 69 deletions

View File

@ -1,8 +1,6 @@
#import <Foundation/Foundation.h>
#import "ResKnifeResourceProtocol.h"
extern NSString *ResourceChangedNotification;
@interface Resource : NSObject <ResKnifeResourceProtocol>
{
@private

View File

@ -12,6 +12,8 @@
HFSUniStr255 *otherFork; // name of fork to save to if not using data fork (usually 'RESOURCE_FORK' as returned from FSGetResourceForkName() -- ignored if saveToDataFork is YES )
}
- (void)setupToolbar:(NSWindowController *)controller;
- (BOOL)readResourceMap:(SInt16)fileRefNum;
- (BOOL)writeResourceMap:(SInt16)fileRefNum;

View File

@ -19,6 +19,8 @@
[super dealloc];
}
/* WINDOW DELEGATION */
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
@ -30,6 +32,7 @@
{
[super windowControllerDidLoadNib:controller];
// Add any code here that need to be executed once the windowController has loaded the document's window.
[self setupToolbar:controller];
// [controller setDocumet:self];
[dataSource setResources:resources];
}
@ -65,6 +68,145 @@
// else returnCode == NSAlertAlternateReturn, cancel
}
/* TOOLBAR MANAGMENT */
static NSString *RKToolbarIdentifier = @"com.nickshanks.resknife.toolbar";
static NSString *RKSaveItemIdentifier = @"com.nickshanks.resknife.toolbar.save";
- (void)setupToolbar:(NSWindowController *)controller
{
/* This routine should become invalid once toolbars are integrated into nib files */
NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier:RKToolbarIdentifier] autorelease];
// set toolbar properties
[toolbar setVisible:NO];
[toolbar setAutosavesConfiguration:YES];
[toolbar setAllowsUserCustomization:YES];
[toolbar setDisplayMode:NSToolbarDisplayModeDefault];
// attach toolbar to window
[toolbar setDelegate:self];
[[controller window] setToolbar:toolbar];
}
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
if( [itemIdentifier isEqual:RKSaveItemIdentifier] )
{
NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
[item setLabel:@"Save"];
[item setPaletteLabel:@"Save"];
[item setToolTip:[NSString stringWithFormat:@"Save To %@ Fork", saveToDataFork? @"Data":@"Resource"]];
[item setImage:[NSImage imageNamed:@"Save"]];
[item setTarget:self];
[item setAction:@selector(saveDocument:)];
return item;
}
else return nil;
/* // Required delegate method Given an item identifier, self method returns an item
// The toolbar will use self method to obtain toolbar items that can be displayed in the customization sheet, or in the toolbar itself
NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
if ([itemIdent isEqual: SaveDocToolbarItemIdentifier]) {
// Set the text label to be displayed in the toolbar and customization palette
[toolbarItem setLabel: @"Save"];
[toolbarItem setPaletteLabel: @"Save"];
// Set up a reasonable tooltip, and image Note, these aren't localized, but you will likely want to localize many of the item's properties
[toolbarItem setToolTip: @"Save Your Document"];
[toolbarItem setImage: [NSImage imageNamed: @"SaveDocumentItemImage"]];
// Tell the item what message to send when it is clicked
[toolbarItem setTarget: self];
[toolbarItem setAction: @selector(saveDocument:)];
} else if([itemIdent isEqual: SearchDocToolbarItemIdentifier]) {
NSMenu *submenu = nil;
NSMenuItem *submenuItem = nil, *menuFormRep = nil;
// Set up the standard properties
[toolbarItem setLabel: @"Search"];
[toolbarItem setPaletteLabel: @"Search"];
[toolbarItem setToolTip: @"Search Your Document"];
// Use a custom view, a text field, for the search item
[toolbarItem setView: searchFieldOutlet];
[toolbarItem setMinSize:NSMakeSize(30, NSHeight([searchFieldOutlet frame]))];
[toolbarItem setMaxSize:NSMakeSize(400,NSHeight([searchFieldOutlet frame]))];
// By default, in text only mode, a custom items label will be shown as disabled text, but you can provide a
// custom menu of your own by using <item> setMenuFormRepresentation]
submenu = [[[NSMenu alloc] init] autorelease];
submenuItem = [[[NSMenuItem alloc] initWithTitle: @"Search Panel" action: @selector(searchUsingSearchPanel:) keyEquivalent: @""] autorelease];
menuFormRep = [[[NSMenuItem alloc] init] autorelease];
[submenu addItem: submenuItem];
[submenuItem setTarget: self];
[menuFormRep setSubmenu: submenu];
[menuFormRep setTitle: [toolbarItem label]];
[toolbarItem setMenuFormRepresentation: menuFormRep];
} else {
// itemIdent refered to a toolbar item that is not provide or supported by us or cocoa
// Returning nil will inform the toolbar self kind of item is not supported
toolbarItem = nil;
}
return toolbarItem;*/
}
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:RKSaveItemIdentifier, NSToolbarPrintItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, nil];
}
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:RKSaveItemIdentifier, NSToolbarPrintItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier, nil];
}
/*
- (void) toolbarWillAddItem: (NSNotification *) notif {
// Optional delegate method Before an new item is added to the toolbar, self notification is posted
// self is the best place to notice a new item is going into the toolbar For instance, if you need to
// cache a reference to the toolbar item or need to set up some initial state, self is the best place
// to do it The notification object is the toolbar to which the item is being added The item being
// added is found by referencing the @"item" key in the userInfo
NSToolbarItem *addedItem = [[notif userInfo] objectForKey: @"item"];
if([[addedItem itemIdentifier] isEqual: SearchDocToolbarItemIdentifier]) {
activeSearchItem = [addedItem retain];
[activeSearchItem setTarget: self];
[activeSearchItem setAction: @selector(searchUsingToolbarTextField:)];
} else if ([[addedItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) {
[addedItem setToolTip: @"Print Your Document"];
[addedItem setTarget: self];
}
}
- (void) toolbarDidRemoveItem: (NSNotification *) notif {
// Optional delegate method After an item is removed from a toolbar the notification is sent self allows
// the chance to tear down information related to the item that may have been cached The notification object
// is the toolbar to which the item is being added The item being added is found by referencing the @"item"
// key in the userInfo
NSToolbarItem *removedItem = [[notif userInfo] objectForKey: @"item"];
if (removedItem==activeSearchItem) {
[activeSearchItem autorelease];
activeSearchItem = nil;
}
}
- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem {
// Optional method self message is sent to us since we are the target of some toolbar item actions
// (for example: of the save items action)
BOOL enable = NO;
if ([[toolbarItem itemIdentifier] isEqual: SaveDocToolbarItemIdentifier]) {
// We will return YES (ie the button is enabled) only when the document is dirty and needs saving
enable = [self isDocumentEdited];
} else if ([[toolbarItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) {
enable = YES;
}
return enable;
}*/
/* FILE HANDLING */
- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type
{
BOOL succeeded = NO;
@ -234,6 +376,8 @@
return error? NO:YES;
}
/* ACCESSORS */
- (NSOutlineView *)outlineView
{
return outlineView;

View File

@ -5,12 +5,18 @@
IBOutlet NSTextView *ascii;
IBOutlet NSTextView *hex;
IBOutlet NSTextView *offset;
IBOutlet NSTextField *message;
}
- (NSString *)offsetRepresentation:(NSData *)data;
- (NSString *)hexRepresentation:(NSData *)data;
- (NSString *)asciiRepresentation:(NSData *)data;
- (NSRange)byteRangeFromHexRange:(NSRange)hexRange;
- (NSRange)hexRangeFromByteRange:(NSRange)byteRange;
- (NSRange)byteRangeFromAsciiRange:(NSRange)asciiRange;
- (NSRange)asciiRangeFromByteRange:(NSRange)byteRange;
- (NSTextView *)hex;
- (NSTextView *)ascii;

View File

@ -6,17 +6,16 @@
- (NSString *)offsetRepresentation:(NSData *)data;
{
int row;
int rows = ([data length] / 16) + (([data length] % 16)? 1:0);
int row, dataLength = [data length];
int rows = (dataLength / 16) + ((dataLength % 16)? 1:0);
NSMutableString *representation = [NSMutableString string];
for( row = 0; row < rows; row++ )
{
[representation appendFormat:@"%08lX:\n", row * 16];
}
// remove last character (the return on the end)
[representation deleteCharactersInRange:NSMakeRange([representation length] -1, 1)];
if( dataLength % 16 != 0 )
[representation deleteCharactersInRange:NSMakeRange([representation length] -1, 1)];
return representation;
}
@ -63,7 +62,7 @@
// append buffer to representation
[representation appendString:[NSString stringWithCString:buffer]];
if( currentByte != dataLength )
if( currentByte != dataLength || dataLength % 16 == 0 )
[representation appendString:@"\n"];
}
@ -104,7 +103,7 @@
// append buffer to representation
[representation appendString:[NSString stringWithCString:buffer]];
if( currentByte != dataLength )
if( currentByte != dataLength || dataLength % 16 == 0 )
[representation appendString:@"\n"];
}
@ -115,7 +114,7 @@
- (NSRange)textView:(NSTextView *)textView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange
{
NSRange hexRange, asciiRange;
NSRange hexRange, asciiRange, byteRange = NSMakeRange(0,0);
// temporarilly removing the delegate stops this function being called recursivly!
id oldDelegate = [hex delegate];
@ -124,42 +123,70 @@
if( textView == hex ) // we're selecting hexadecimal
{
if( newSelectedCharRange.length == 0 ) // moving insertion point
{
asciiRange = NSMakeRange( newSelectedCharRange.location /3, 0 );
}
else // dragging a selection
{
int numReturns = (newSelectedCharRange.length /47) + (newSelectedCharRange.location % 47? 1:0);
asciiRange = NSMakeRange( newSelectedCharRange.location /3, (newSelectedCharRange.length+1) /3 );
}
NSLog( @"hex selection changed from %@ to %@", NSStringFromRange(oldSelectedCharRange), NSStringFromRange(newSelectedCharRange) );
NSLog( @"changing ascii selection to %@", NSStringFromRange(asciiRange) );
byteRange = [self byteRangeFromHexRange:newSelectedCharRange];
asciiRange = [self asciiRangeFromByteRange:byteRange];
[ascii setSelectedRange:asciiRange];
}
else if( textView == ascii ) // we're selecting ASCII
{
if( newSelectedCharRange.length == 0 ) // moving insertion point
{
hexRange = NSMakeRange( newSelectedCharRange.location *3, 0 );
}
else // dragging a selection
{
int numReturns = (newSelectedCharRange.length /17) + (newSelectedCharRange.location % 17? 1:0);
hexRange = NSMakeRange( (newSelectedCharRange.location - numReturns) *3 + numReturns, ((newSelectedCharRange.length - numReturns) *3) -1 );
}
NSLog( @"ascii selection changed from %@ to %@", NSStringFromRange(oldSelectedCharRange), NSStringFromRange(newSelectedCharRange) );
NSLog( @"changing hex selection to %@", NSStringFromRange(hexRange) );
byteRange = [self byteRangeFromAsciiRange:newSelectedCharRange];
hexRange = [self hexRangeFromByteRange:byteRange];
[hex setSelectedRange:hexRange];
}
else NSLog( @"What the hell are you selecting?" );
// put the new selection into the message bar
[message setStringValue:[NSString stringWithFormat:@"Current selection: %@", NSStringFromRange(byteRange)]];
// restore delegates
[hex setDelegate:oldDelegate];
[ascii setDelegate:oldDelegate];
return newSelectedCharRange;
}
- (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;
{
NSRange hexRange = NSMakeRange(0,0);
hexRange.location = (byteRange.location * 3);
hexRange.length = (byteRange.length * 3);
return hexRange;
}
- (NSRange)byteRangeFromAsciiRange:(NSRange)asciiRange;
{
// assumes 16 byte wide window
NSRange byteRange;
byteRange.location = asciiRange.location - (asciiRange.location / 17);
byteRange.length = asciiRange.length - ((asciiRange.location + asciiRange.length) / 17) + (asciiRange.location / 17);
return byteRange;
}
- (NSRange)asciiRangeFromByteRange:(NSRange)byteRange;
{
// assumes 16 byte wide window
NSRange asciiRange = NSMakeRange(0,0);
asciiRange.location = byteRange.location + (byteRange.location / 17);
asciiRange.length = byteRange.length + ((byteRange.location + byteRange.length) / 17) - (byteRange.location / 17);
return asciiRange;
}
/*
- (void)textViewDidChangeSelection:(NSNotification *)notification;
{

View File

@ -1,11 +1,19 @@
#import "HexTextView.h"
#import "ResKnifeResourceProtocol.h"
@implementation HexTextView
- (void)insertText:(id)insertString
{
// bug: Every time a character is typed or string pasted, the entire resource is duplicated, operated on and disposed of! Perhaps I could do this in a better way?
NSMutableData *newData = [NSMutableData dataWithData:[[[self window] windowController] data]];
NSRange selection = [self selectedRange];
NSLog( insertString );
[super insertText:insertString];
// modify resource data
// update resource data - this causes a notification to be sent out, which the plug receives and acts upon to update the text views
[(id <ResKnifeResourceProtocol>)[[[self window] windowController] resource] setData:newData];
}
- (void)setSelectedRange:(NSRange)charRange affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)flag
@ -13,13 +21,43 @@
NSRange newRange = charRange;
// select whole bytes at a time (only if selecting in hex!)
if( self == [[self delegate] hex] )
if( self == (id) [[self delegate] hex] )
{
newRange = [self selectionRangeForProposedRange:charRange
granularity:NSSelectByWord];
// newRange.location -= newRange.location % 3;
// newRange.length -= (newRange.length % 3) -2;
NSLog( @"hex selection changed to %@", NSStringFromRange(newRange) );
// NSLog( NSStringFromRange(newRange) );
// 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;
// NSLog( NSStringFromRange(newRange) );
// NSLog( @"===========" );
}
// select return character if selecting ascii
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;
}
}
// call the superclass to update the selection

View File

@ -10,12 +10,19 @@
IBOutlet NSTextView *ascii;
IBOutlet NSTextView *hex;
IBOutlet NSTextView *offset;
IBOutlet NSTextField *message;
id resource;
}
// conform to the ResKnifePluginProtocol with the inclusion of these methods
- (id)initWithResource:(id)newResource;
- (void)resourceDidChange:(NSNotification *)notification;
- (void)refreshData:(NSData *)data;
// conform to the ResKnifePluginProtocol with the inclusion of these methods
- (id)initWithResource:(id)newResource;
// accessors
- (id)resource;
- (NSData *)data;
@end

View File

@ -1,8 +1,12 @@
#import "HexWindowController.h"
#import "HexTextView.h"
#import "ResKnifeResourceProtocol.h"
@implementation HexWindowController
NSString *ResourceChangedNotification = @"ResourceChangedNotification";
+ (void)initialize
{
// causes window controller to use HexTextViews wherever it would previously use NSTextView
@ -23,23 +27,36 @@
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
// set up text boxes (this doesn't do what I think it should!)
// [hex setSelectionGranularity:NSSelectByWord];
// we don't want this notification until we have a window!
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDidChange:) name:ResourceChangedNotification object:nil];
// insert the resources' data into the text fields
[self refreshData:[(id <ResKnifeResourceProtocol>)resource data]];
// finally, show the window
[self showWindow:self];
}
}
- (void)resourceDidChange:(NSNotification *)notification
{
// see if it's our resource which got changed (we receive notifications for any resource being changed, allowing multi-resource editors)
if( [notification object] == resource )
[self refreshData:[(id <ResKnifeResourceProtocol>)resource data]];
}
- (void)refreshData:(NSData *)data;
{
// clear delegates (see HexEditorDelegate class for more info)
// clear delegates (see HexEditorDelegate class for explanation of why)
id oldDelegate = [hex delegate];
[hex setDelegate:nil];
[ascii setDelegate:nil];
@ -54,4 +71,14 @@
[ascii setDelegate:oldDelegate];
}
- (id)resource
{
return resource;
}
- (NSData *)data
{
return [(id <ResKnifeResourceProtocol>)resource data];
}
@end

View File

@ -1,17 +1,12 @@
#import <Foundation/Foundation.h>
/* Your plug-in's principle class must implement the following methods else it won't be loaded by ResKnife (so neh-neh!) */
/* 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 */
@protocol ResKnifePluginProtocol
/*! @function initWithResource:newResource
/*! @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.
*/
- (id)initWithResource:(id)newResource;
/*! @function refreshData:data
* @abstract When your data set is changed via some means other than yourslf, you receive this.
*/
- (void)refreshData:(NSData *)data;
@end
@end

View File

@ -15,8 +15,11 @@
- (NSNumber *)attributes;
- (void)setAttributes:(NSNumber *)newAttributes;
- (BOOL)dirty;
- (void)setDirty:(BOOL)newValue;
- (void)setDirty:(BOOL)newValue; // bug: may not be around forever
- (NSData *)data;
- (void)setData:(NSData *)newData;
@end
@end
// Resource notifications
extern NSString *ResourceChangedNotification; // Note: when using internal notifications in your own plug-in, DO NOT use [NSNotificationCenter defaultCenter]. This is an application-wide notificaton center, use of it by plug-ins for their own means (i.e. not interacting with ResKnife) can cause conflicts with other plug-ins. You should create your own notification center and post to that.

BIN
Cocoa/Resources/Save.tiff Normal file

Binary file not shown.

View File

@ -58,6 +58,17 @@
settings = {
};
};
F577A8F30211CFA701A80001 = {
isa = PBXFileReference;
path = Save.tiff;
refType = 4;
};
F577A8F40211CFA801A80001 = {
fileRef = F577A8F30211CFA701A80001;
isa = PBXBuildFile;
settings = {
};
};
F57CEE0B0189C95101A8010B = {
children = (
F5502C4001C579FF01C57124,
@ -149,7 +160,8 @@
buildActionMask = 2147483647;
files = (
F5EF83AF020C08E601A80001,
F5EF83C6020C162C01A80001,
F5EF83C9020C20D801A80001,
F577A8F40211CFA801A80001,
);
isa = PBXResourcesBuildPhase;
name = "Bundle Resources";
@ -586,6 +598,7 @@
F5B588460156D40B01000001,
F5B588470156D40B01000001,
F5B588480156D40B01000001,
F577A8F30211CFA701A80001,
);
isa = PBXGroup;
path = Resources;
@ -2306,8 +2319,8 @@
F5EF83A5020C08E601A80001,
F5EF83A7020C08E601A80001,
F5EF83A8020C08E601A80001,
F5EF83C7020C20D701A80001,
F5EF83A9020C08E601A80001,
F5EF83C5020C162B01A80001,
);
isa = PBXGroup;
name = "Hex Editor";
@ -2413,22 +2426,23 @@
settings = {
};
};
F5EF83C3020C10C601A80001 = {
isa = PBXFileReference;
name = HexWindow.nib;
path = English.lproj/HexWindow.nib;
refType = 4;
};
F5EF83C5020C162B01A80001 = {
F5EF83C7020C20D701A80001 = {
children = (
F5EF83C3020C10C601A80001,
F5EF83C8020C20D701A80001,
);
isa = PBXVariantGroup;
name = HexWindow.nib;
path = "";
refType = 4;
};
F5EF83C6020C162C01A80001 = {
fileRef = F5EF83C5020C162B01A80001;
F5EF83C8020C20D701A80001 = {
isa = PBXFileReference;
name = English;
path = English.lproj/HexWindow.nib;
refType = 4;
};
F5EF83C9020C20D801A80001 = {
fileRef = F5EF83C7020C20D701A80001;
isa = PBXBuildFile;
settings = {
};