mirror of
https://github.com/nickshanks/ResKnife.git
synced 2024-09-01 10:28:57 +00:00
331 lines
12 KiB
Objective-C
331 lines
12 KiB
Objective-C
#import "HexWindowController.h"
|
||
#import "HexTextView.h"
|
||
#import "FindSheetController.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 self;
|
||
|
||
// 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];
|
||
backup = [newResource copy];
|
||
}
|
||
else
|
||
{
|
||
resource = [newResource copy];
|
||
backup = [newResource retain];
|
||
}
|
||
bytesPerRow = 16;
|
||
|
||
// load the window from the nib file and set it's title
|
||
[self window]; // implicitly loads nib
|
||
[[self window] setTitle:[resource nameForEditorWindow]];
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void)dealloc
|
||
{
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
[(id)resource autorelease];
|
||
[undoManager release];
|
||
[super dealloc];
|
||
}
|
||
|
||
- (void)windowDidLoad
|
||
{
|
||
[super windowDidLoad];
|
||
|
||
// swap text views to instances of my class instead
|
||
[offset swapForHexTextView];
|
||
[hex swapForHexTextView];
|
||
[ascii swapForHexTextView];
|
||
|
||
// turn off the background for the offset column (IB is broken when it comes to this)
|
||
[offset setDrawsBackground:NO];
|
||
[[offset enclosingScrollView] setDrawsBackground:NO];
|
||
|
||
// set up tab, shift-tab and enter behaviour
|
||
[hex setFieldEditor:YES];
|
||
[ascii setFieldEditor:YES];
|
||
|
||
// 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];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:resource];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceWasSaved:) name:ResourceWasSavedNotification object:resource];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceWasSaved:) name:ResourceWasSavedNotification object:backup];
|
||
|
||
// put other notifications here too, just for togetherness
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:[[offset enclosingScrollView] contentView]];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:[[hex enclosingScrollView] contentView]];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:[[ascii enclosingScrollView] contentView]];
|
||
|
||
// finally, show the window
|
||
[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
|
||
NSMenu *editMenu = [[[NSApp mainMenu] itemAtIndex:2] submenu];
|
||
NSMenuItem *pasteItem = [editMenu itemAtIndex:[editMenu indexOfItemWithTarget:nil andAction:@selector(paste:)]];
|
||
[NSBundle loadNibNamed:@"PasteMenu" owner:self];
|
||
[pasteItem setEnabled:YES];
|
||
[pasteItem setKeyEquivalent:@"\0"]; // clear key equiv. (yes, really!)
|
||
[pasteItem setKeyEquivalentModifierMask:0];
|
||
[editMenu setSubmenu:pasteSubmenu forItem:pasteItem];
|
||
}
|
||
|
||
- (void)windowDidResignKey:(NSNotification *)notification
|
||
{
|
||
// swap my submenu for plain paste menu item
|
||
NSMenu *editMenu = [[[NSApp mainMenu] itemAtIndex:2] submenu];
|
||
NSMenuItem *pasteItem = [editMenu itemAtIndex:[editMenu indexOfItemWithSubmenu:pasteSubmenu]];
|
||
[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 save the changes you made to this resource?", @"Save", @"Don<EFBFBD>t Save", @"Cancel", sender, self, @selector(saveSheetDidClose:returnCode:contextInfo:), nil, nil, @"Your changes will be lost if you don't save them." );
|
||
return NO;
|
||
}
|
||
else return YES;
|
||
}
|
||
|
||
- (void)saveSheetDidClose:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
|
||
{
|
||
switch( returnCode )
|
||
{
|
||
case NSAlertDefaultReturn: // save
|
||
[self saveResource];
|
||
[[self window] close];
|
||
break;
|
||
|
||
case NSAlertAlternateReturn: // don't save
|
||
[self revertResource];
|
||
[[self window] close];
|
||
break;
|
||
|
||
case NSAlertOtherReturn: // cancel
|
||
break;
|
||
}
|
||
}
|
||
|
||
- (void)saveResource
|
||
{
|
||
if( liveEdit )
|
||
{
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:ResourceWillBeSavedNotification object:resource];
|
||
[backup setData:[resource data]];
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:ResourceWasSavedNotification object:resource];
|
||
}
|
||
else
|
||
{
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:ResourceWillBeSavedNotification object:backup];
|
||
[backup setData:[resource data]];
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:ResourceWasSavedNotification object:backup];
|
||
}
|
||
}
|
||
|
||
- (void)revertResource
|
||
{
|
||
[resource setData:[backup data]];
|
||
}
|
||
|
||
- (IBAction)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)viewDidScroll:(NSNotification *)notification
|
||
{
|
||
// get object refs for increased speed
|
||
NSClipView *object = (NSClipView *) [notification object];
|
||
NSClipView *offsetClip = [[offset enclosingScrollView] contentView];
|
||
NSClipView *hexClip = [[hex enclosingScrollView] contentView];
|
||
NSClipView *asciiClip = [[ascii enclosingScrollView] contentView];
|
||
|
||
// due to a bug in -[NSView setPostsBoundsChangedNotifications:] (it basically doesn't work), I am having to work around it by removing myself from the notification center and restoring things later on!
|
||
// update, Apple say this isn't their bug. Yeah, right!
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewBoundsDidChangeNotification object:nil];
|
||
|
||
// when a view scrolls, update the other two
|
||
if( object != offsetClip )
|
||
{
|
||
// [offsetClip setPostsBoundsChangedNotifications:NO];
|
||
[offsetClip setBoundsOrigin:[object bounds].origin];
|
||
// [offsetClip setPostsBoundsChangedNotifications:YES];
|
||
}
|
||
|
||
if( object != hexClip )
|
||
{
|
||
// [hexClip setPostsBoundsChangedNotifications:NO];
|
||
[hexClip setBoundsOrigin:[object bounds].origin];
|
||
// [hexClip setPostsBoundsChangedNotifications:YES];
|
||
}
|
||
|
||
if( object != asciiClip )
|
||
{
|
||
// [asciiClip setPostsBoundsChangedNotifications:NO];
|
||
[asciiClip setBoundsOrigin:[object bounds].origin];
|
||
// [asciiClip setPostsBoundsChangedNotifications:YES];
|
||
}
|
||
|
||
// restore notifications
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:offsetClip];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:hexClip];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidScroll:) name:NSViewBoundsDidChangeNotification object:asciiClip];
|
||
}
|
||
|
||
- (void)resourceNameDidChange:(NSNotification *)notification
|
||
{
|
||
[[self window] setTitle:[(id <ResKnifeResourceProtocol>)[notification object] nameForEditorWindow]];
|
||
}
|
||
|
||
- (void)resourceDataDidChange:(NSNotification *)notification
|
||
{
|
||
// ensure it's our resource which got changed (should always be true, we don't register for other resource notifications)
|
||
if( [notification object] == (id)resource )
|
||
{
|
||
[self refreshData:[resource data]];
|
||
[self setDocumentEdited:YES];
|
||
}
|
||
}
|
||
|
||
- (void)resourceWasSaved:(NSNotification *)notification
|
||
{
|
||
NSLog( @"%@; %@; %@", [notification object], resource, backup );
|
||
if( [notification object] == (id)resource )
|
||
{
|
||
// if resource gets saved, liveEdit is true and this resource is saving
|
||
[backup setData:[resource data]];
|
||
[self setDocumentEdited:NO];
|
||
}
|
||
else if( [notification object] == (id)backup && !liveEdit )
|
||
{
|
||
// backup will get saved by this resource if liveEdit is false, rather than the 'resource' variable
|
||
// but really the data to preserve is in the resource variable
|
||
[backup setData:[resource data]];
|
||
// [self refreshData:[resource data]];
|
||
[self setDocumentEdited:NO];
|
||
}
|
||
else if( [notification object] == (id)backup )
|
||
{
|
||
// backup will get saved by another editor too if liveEdit is false
|
||
[resource setData:[backup data]];
|
||
// [self refreshData:[resource data]];
|
||
[self setDocumentEdited:NO];
|
||
}
|
||
}
|
||
|
||
- (void)refreshData:(NSData *)data;
|
||
{
|
||
NSDictionary *dictionary;
|
||
NSMutableParagraphStyle *paragraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||
|
||
// 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
|
||
[paragraph setLineBreakMode:NSLineBreakByCharWrapping];
|
||
dictionary = [NSDictionary dictionaryWithObject:paragraph forKey:NSParagraphStyleAttributeName];
|
||
|
||
// do stuff with data
|
||
[offset setString:[hexDelegate offsetRepresentation:data]];
|
||
[hex setString:[hexDelegate hexRepresentation:data]];
|
||
[ascii setString:[hexDelegate asciiRepresentation:data]];
|
||
|
||
// 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 *)pasteSubmenu
|
||
{
|
||
return pasteSubmenu;
|
||
}
|
||
|
||
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)sender
|
||
{
|
||
return undoManager;
|
||
}
|
||
|
||
@end |