#include "Events.h"
#include "HexWindow.h"
#include "HexUtility.h"

extern globals g;
extern prefs p;

  /******************/
 /* EVENT HANDLING */
/******************/

/*** CARBON WINDOW EVENT HANDLER ***/
pascal OSStatus CarbonWindowEventHandler( EventHandlerCallRef handler, EventRef event, void *userData )
{
	#pragma unused( handler )
	OSStatus		error = eventNotHandledErr;
	Plug_PlugInRef	plugRef = (Plug_PlugInRef) userData;
	WindowRef		window = GetUserFocusWindow();
	
	// get event type
	UInt32 eventClass = GetEventClass( event );
	UInt32 eventKind = GetEventKind( event );
	
	// get event parameters
	if( eventClass == kEventClassWindow )
		GetEventParameter( event, kEventParamDirectObject, typeWindowRef, null, sizeof(WindowRef), null, &window );
	if( !window ) return error;
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	if( !plugWindow ) return error;
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	if( !hexWindow ) return error;
	
	// get window rect
	Rect windowBounds;
	GetWindowPortBounds( window, &windowBounds );
	
	// handle event
	static EventHandlerRef resizeEventHandlerRef = null;
	switch( eventClass )
	{
		case kEventClassWindow:
			switch( eventKind )
			{
				case kEventWindowClose:
					delete hexWindow;
					break;
				
				case kEventWindowActivated:
				case kEventWindowDeactivated:
					if( hexWindow->activeWindow && hexWindow->insertionPointVisable )
						BlinkInsertionPoint( null, window );			// this has to be done before the window is marked as deactivated
//					hexWindow->activeWindow = !hexWindow->activeWindow;	// bug: OS X is sending the event twice (naughty Apple!), so i shall do this more correctly as follows
					if( eventKind == kEventWindowActivated )
						hexWindow->activeWindow = true;
					else hexWindow->activeWindow = false;
					InvalidateWindowRect( window, &windowBounds );
					break;
				
				case kEventWindowBoundsChanging:
					error = hexWindow->BoundsChanging( event );
					break;
							
				case kEventWindowBoundsChanged:
					error = hexWindow->BoundsChanged( event );
					break;
							
				case kEventWindowDrawContent:
					error = hexWindow->DrawContent( event );
					break;
				
				case kEventWindowHandleContentClick:
				{	// get mouse
					Point mouse;
					error = GetEventParameter( event, kEventParamMouseLocation, typeQDPoint, null, sizeof(Point), null, &mouse );
					if( !error )
					{
						MakeLocal( window, mouse, &mouse );
						
						// get modifier keys
						UInt32 modifiers = null;
						error = GetEventParameter( event, kEventParamKeyModifiers, typeUInt32, null, sizeof(UInt32), null, &modifiers );
						if( error ) break;
						
						// identify click is in edit boxes & act accordingly
						Boolean	clickHex	= false;	// clicked in hex rect?
						Boolean	clickAscii	= false;	// clicked in ascii rect? - neither means not editing
						Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
						HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
						if( PtInRect( mouse, &hexWindow->hexRect ) )	{ clickHex = true;		hexWindow->editingHex = true;	}
						if( PtInRect( mouse, &hexWindow->asciiRect ) )	{ clickAscii = true;	hexWindow->editingHex = false;	}
						if( clickHex || clickAscii )					error = HandleEditClick( window, event, mouse, (EventModifiers) LoWord(modifiers) );
						else error = eventNotHandledErr;
					}
					else error = eventNotHandledErr;
				}	break;
			}
			break;
		
		case kEventClassKeyboard:
			switch( eventKind )
			{
				case kEventRawKeyDown:
				case kEventRawKeyRepeat:
				{	signed char charCode;	// key character pressed
					UInt32 modifiers = null;
					error = GetEventParameter( event, kEventParamKeyMacCharCodes, typeChar, null, sizeof(char), null, &charCode );
					if( error ) break;
					error = GetEventParameter( event, kEventParamKeyModifiers, typeUInt32, null, sizeof(UInt32), null, &modifiers );
					if( error ) break;
					HandleKeyDown( window, charCode, (EventModifiers) LoWord(modifiers) );
				}	break;
			}
			
			// calculate new scrollbar values & redraw window
			hexWindow->UpdateHexInfo();
			InvalidateWindowRect( window, &windowBounds );
			break;
	}
	return error;
}

/*** CARBON HUMAN INTERFACE EVENT HANDLER ***/
pascal OSStatus CarbonHIEventHandler( EventHandlerCallRef handler, EventRef event, void *userData )
{
	#pragma unused( handler, userData )
	OSStatus		error = eventNotHandledErr;
	WindowRef		window = GetUserFocusWindow();	// overridden below for window class events
	if( !window )	return error;
	
	// get event type
	UInt32 eventClass = GetEventClass( event );
	UInt32 eventKind = GetEventKind( event );
	
	// get event parameters
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	if( !plugWindow ) return error;
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	if( !hexWindow ) return error;
	
	// handle event
	switch( eventClass )
	{
		case kEventClassMenu:
			switch( eventKind )
			{
				case kEventMenuEnableItems:
				{	Plug_ResourceRef resource = Host_GetTargetResource( plugWindow );
					Boolean canPlaySound = false;
					if( Host_GetResourceType( resource ) == soundListRsrc )
						canPlaySound = true;
					
					// get host to set menus so we can modify them
					Host_UpdateMenus( resource );
					
					// edit menu
					EnableCommand( null, kHICommandUndo, false );
					EnableCommand( null, kHICommandRedo, false );
					EnableCommand( null, kHICommandCut, hexWindow->selStart != hexWindow->selEnd );
					EnableCommand( null, kHICommandCopy, hexWindow->selStart != hexWindow->selEnd );
					EnableCommand( null, kHICommandPaste, true );	// bug
					EnableCommand( null, kHICommandClear, hexWindow->selStart != hexWindow->selEnd );
					error = noErr;
				}	break;
			}
			break;
		
		case kEventClassCommand:		
			HICommand command;
			Plug_ResourceRef resource = Host_GetTargetResource( plugWindow );
			error = GetEventParameter( event, kEventParamDirectObject, typeHICommand, null, sizeof(HICommand), null, &command );
			if( error ) return eventNotHandledErr;
			switch( eventKind )
			{
				case kEventCommandProcess:
					switch( command.commandID )
					{
		/*				case kHICommandOK:
						case kHICommandCancel:
						case kHICommandQuit:
						case kHICommandUndo:
						case kHICommandRedo:
						case kHICommandCut:
							SendEventToWindow( copyEvent, window );
							SendEventToWindow( clearEvent, window );
							error = noErr;
							break;
		*/				
						case kHICommandCopy:
						{	// copy should be disabled if there isn't a selection, but just in caseÉ
							if( hexWindow->selStart == hexWindow->selEnd ) return error;
							
							// lock the data
							Size size;
							ScrapRef scrap;
							ClearCurrentScrap();
							error = GetCurrentScrap( &scrap );
							if( error ) return error;
							SInt8 state = HGetState( hexWindow->data );
							HLock( hexWindow->data );
							if( hexWindow->editingHex )
							{
								// copy data with hex formatting
								size = 3*(hexWindow->selEnd - hexWindow->selStart) -1;
								Ptr hex = NewPtrClear( size );
								Ptr ascii = NewPtrClear( hexWindow->selEnd - hexWindow->selStart );
								BlockMoveData( *hexWindow->data + hexWindow->selStart, ascii, hexWindow->selEnd - hexWindow->selStart );
								AsciiToHex( ascii, hex, hexWindow->selEnd - hexWindow->selStart );
								error = PutScrapFlavor( scrap, kScrapFlavorTypeText, kScrapFlavorMaskNone, size, hex );
								DisposePtr( hex );
								DisposePtr( ascii );
							}
							else
							{
								// copy raw data as byte stream
								size = hexWindow->selEnd - hexWindow->selStart;
								Ptr ascii = NewPtrClear( size );
								BlockMoveData( *hexWindow->data + hexWindow->selStart, ascii, size );
								error = PutScrapFlavor( scrap, kScrapFlavorTypeText, kScrapFlavorMaskNone, size, ascii );
								DisposePtr( ascii );
							}
							HSetState( hexWindow->data, state );
							error = noErr;
						}	break;
						
						case kHICommandPaste:
						{	Size size;
							ScrapRef scrap;
							error = GetCurrentScrap( &scrap );
							if( error ) return error;
							error = GetScrapFlavorSize( scrap, kScrapFlavorTypeText, &size );
							if( error ) return error;
							if( size > 0 )
							{
								Ptr bytes = NewPtr( size );
								error = GetScrapFlavorData( scrap, kScrapFlavorTypeText, &size, bytes );
								if( !error )
								{
									hexWindow->InsertBytes( null, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd );	// remove this when using the above
									hexWindow->InsertBytes( bytes, size, hexWindow->selStart );
									hexWindow->selStart = hexWindow->selEnd += size;
								}
								DisposePtr( bytes );
								Host_SetResourceDirty( resource, true );
							}
							error = noErr;
						}	break;
						
						case kHICommandClear:
							hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd );
							hexWindow->selEnd = hexWindow->selStart;
							Host_SetResourceDirty( resource, true );
							error = noErr;
							break;
						
						case kHICommandSelectAll:
							hexWindow->selStart = 0;
							hexWindow->selEnd = GetHandleSize( hexWindow->data );
							error = noErr;
							break;
						
		/*				case kHICommandHide:
						case kHICommandPreferences:
						case kHICommandZoomWindow:
						case kHICommandMinimizeWindow:
						case kHICommandArrangeInFront:
						case kHICommandAbout:
		*/				default:
							error = eventNotHandledErr;
							break;
					}
					break;
				
				case kEventCommandUpdateStatus:
					error = eventNotHandledErr;
					break;
				
				default:
					error = eventNotHandledErr;
					break;
			}
			break;
	}
	
	// most calls need window updating, so do it here
	Rect windowBounds;
	hexWindow->UpdateHexInfo();
	GetWindowPortBounds( window, &windowBounds );
	InvalidateWindowRect( window, &windowBounds );
	return error;
}

#if !TARGET_API_MAC_CARBON

/*** CLASSIC WINDOW EVENT HANDLER ***/
pascal OSStatus ClassicWindowEventHandler( EventRecord *event, UInt32 eventKind, void *userData )
{
	#pragma unused( event, userData )
	OSStatus		error = eventNotHandledErr;
/*	Plug_PlugInRef	plugRef = (Plug_PlugInRef) userData;
	WindowRef		window;
	GetEventParameter( event, kEventParamDirectObject, typeWindowRef, null, sizeof(WindowRef), null, &window );
*/	
	switch( eventKind )
	{
		case kEventWindowDrawContent:
			error = DrawWindow( window );
			break;
		
		case kEventWindowHandleContentClick:
			SysBeep(0);
			break;
	}
	return error;
}

#endif

  /*****************/
 /* WINDOW EVENTS */
/*****************/

pascal OSStatus ConstrainWindowResize( EventHandlerCallRef handler, EventRef event, void *userData )
{
	#pragma unused( handler )
	Rect windowBounds;
	HexWindowPtr hexWindow = (HexWindowPtr) userData;
	OSStatus error = GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, null, sizeof(Rect), null, &windowBounds );
	if( error ) return eventNotHandledErr;
	
	// constrain window width
	UInt8 modulo = (windowBounds.right - windowBounds.left - 13*kHexCharWidth - kScrollBarWidth) % (4*kDataBlockWidth);
	if( modulo < (2*kDataBlockWidth) )	windowBounds.right -= modulo;
	else								windowBounds.right += (4*kDataBlockWidth) - modulo;
	
	// constrain window height
	modulo = (windowBounds.bottom - windowBounds.top - kHeaderHeight - 2*kTextMargin -4) % kHexLineHeight;
	if( modulo < (kHexLineHeight/2) )	windowBounds.bottom -= modulo;
	else								windowBounds.bottom += kHexLineHeight - modulo;
	
	// update window rect to constrained version
	if( (windowBounds.bottom - windowBounds.top) < kMinimumWindowHeight )	windowBounds.bottom = windowBounds.top + kMinimumWindowHeight;
	if( (windowBounds.right - windowBounds.left) < kMinimumWindowWidth )	windowBounds.right = windowBounds.left + kMinimumWindowWidth;
	error = SetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), &windowBounds );
	if( error ) error = eventNotHandledErr;
	
	// resize controls & update hex info
	hexWindow->BoundsChanged( event );
	return noErr;
}

  /***************/
 /* USER EVENTS */
/***************/

/*** HANDLE CLICK IN HEX/ASCII REGION ***/
OSStatus HandleEditClick( WindowRef window, EventRef event, Point mouse, EventModifiers modifiers )
{
	OSStatus error = eventNotHandledErr;
	
	// get hex info
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	
	// a mouse down event has been recieved and is within hexRect or asciiRect
	UInt16 clickLine;	// from zero, the line in which the click occoured
	UInt16 clickChar;	// 0 to 16, the char after the clickloc, or at line end
	
	// get line clicked on
	clickLine = (mouse.v - kHeaderHeight - kTextMargin)/kHexLineHeight + hexWindow->topline;
	if( clickLine > hexWindow->lastline )		// beyond last line
	{
		hexWindow->selStart = hexWindow->selEnd = GetHandleSize( hexWindow->data );
		return noErr;
	}
	
	// get char clicked on	-- duplicated in dragging routine
	if( hexWindow->editingHex )	clickChar = (mouse.h - kHexCharWidth *10) / (kHexCharWidth*3);
	else									clickChar = (mouse.h - kHexCharWidth *60) /  kHexCharWidth;
	
	// check for drags
	Point globalMouse;
	MakeGlobal( window, mouse, &globalMouse );
	Boolean dragging = WaitMouseMoved( globalMouse );
	unsigned long selectFromChar = (clickLine *16) + clickChar;
	unsigned long selectToChar = selectFromChar;
	
	// define selection region
	RgnHandle hexRgn = NewRgn(), asciiRgn = NewRgn(), selectedRgn = NewRgn();
	FindSelectedRegions( window, hexRgn, asciiRgn );
	UnionRgn( hexRgn, asciiRgn, selectedRgn );

	// drag current selection
	if( PtInRgn( mouse, selectedRgn ) && dragging )
	{
		OSErr error = noErr;
		DragReference theDragRef;
		ItemReference theItemRef = 1;
		FlavorFlags theFlags = nil;
		
		short		resCounter = 0;
		Size		dataSize = hexWindow->selEnd - hexWindow->selStart;
		RgnHandle	dragRgn = NewRgn(), subtractRgn = NewRgn();
		Handle		dataToDrag = NewHandleClear( dataSize * (hexWindow->editingHex? 3:1) - (hexWindow->editingHex? 1:0) );
		
		NewDrag( &theDragRef );
		if( MemError() ) return eventNotHandledErr;
		
		// get region of dragged items
		if( hexWindow->editingHex )	dragRgn = hexRgn;
		else						dragRgn = asciiRgn;
		CopyRgn( dragRgn, subtractRgn );				// duplicate region
		InsetRgn( subtractRgn, 2, 2 );					// inset it by 2 pixels
		DiffRgn( dragRgn, subtractRgn, dragRgn );		// subtract subRgn from dragRgn
		
		// get the drag data
		EventRecord eventRec;
		Boolean eventValid = ConvertEventRefToEventRecord( event, &eventRec );
		if( !eventValid ) eventValid = true;	// bug: for some reason the event converter is not returning valid events, but the drag still works
		if( dataToDrag && eventValid )
		{
			// I can't tell what the previous state of editingHex was, so cannot restore it.
			//	as such, i will redraw the window with the selection flipped if necessary
			hexWindow->DrawContent();
			
			SInt8 dataState = HGetState( hexWindow->data );
			SInt8 dragState = HGetState( dataToDrag );
			HLock( hexWindow->data );
			HLock( dataToDrag );
			if( hexWindow->editingHex )	AsciiToHex( *hexWindow->data + hexWindow->selStart, *dataToDrag, dataSize );
			else						BlockMoveData( *hexWindow->data + hexWindow->selStart, *dataToDrag, dataSize );
			HSetState( hexWindow->data, dataState );
			HSetState( dataToDrag, dragState );
			
			// do the drag
			SetPoint( &globalMouse, 0, 0 );
			MakeGlobal( window, globalMouse, &globalMouse );
			OffsetRgn( dragRgn, globalMouse.h, globalMouse.v );
			error = AddDragItemFlavor( theDragRef, theItemRef, kScrapFlavorTypeText, *dataToDrag, GetHandleSize(dataToDrag), theFlags );
			error = TrackDrag( theDragRef, &eventRec, dragRgn );
			
			// when dragging from the ACSII pane, drag will contain ÔasciiÕ, when dragging from the HEX pane, drag will contain Ô61 73 63 69 69Õ 
		}
		
		// clear up
		if( dataToDrag )	DisposeHandle( dataToDrag );
		if( theDragRef )	DisposeDrag( theDragRef );
		if( subtractRgn )	DisposeRgn( subtractRgn );
		if( dragRgn )		DisposeRgn( dragRgn );
	}
	
	// dragging new selection
	else if( dragging )
	{
		// remove insetion point if visable
		if( hexWindow->activeWindow && hexWindow->insertionPointVisable )
			BlinkInsertionPoint( null, window );
		
		Point localMouse;
		while( WaitMouseUp() )
		{
			// get local mouse co-ords
			GetMouse( &mouse );
			MakeLocal( window, mouse, &localMouse );
			
			// get line clicked on
			clickLine = (localMouse.v - kHeaderHeight - kTextMargin)/kHexLineHeight + hexWindow->topline;
			
			// get char clicked on
			if( hexWindow->editingHex )	clickChar = (localMouse.h - kHexCharWidth *10) / (kHexCharWidth*3);
			else						clickChar = (localMouse.h - kHexCharWidth *60) /  kHexCharWidth;
			
			// update selection according to mouse position and scroll to maintain visibility
			selectToChar = (clickLine *16) + clickChar;
			if( selectToChar < 0 )									selectToChar = 0;
			if( selectToChar > GetHandleSize(hexWindow->data) -1 )	selectToChar = GetHandleSize(hexWindow->data) -1;
			if( selectFromChar < selectToChar )
			{
				hexWindow->selStart = selectFromChar;
				hexWindow->selEnd = selectToChar +1;
//				hexScrollToLine( theWindow, clickLine, kBottomOfWindow );
			}
			else if( selectFromChar > selectToChar )
			{
				hexWindow->selStart = selectToChar;
				hexWindow->selEnd = selectFromChar;
//				hexScrollToLine( theWindow, clickLine, kTopOfWindow );
			}
			else
			{
				hexWindow->selStart = selectFromChar;
				hexWindow->selEnd = selectToChar +1;
			}
			
			// draw window
			hexWindow->DrawContent();
		}
	}
	
	// change cursor position
	else
	{
		// remove insertion point if visable
		if( hexWindow->activeWindow && hexWindow->insertionPointVisable )
			BlinkInsertionPoint( null, window );
		
		if( modifiers & shiftKey )	// extend selection
		{
			if( selectFromChar < hexWindow->selStart )	hexWindow->selStart = selectFromChar;
			else										hexWindow->selEnd = selectFromChar +1;
		}
		else						// normal click
		{
			if( mouse.v < GetHandleSize(hexWindow->data) )	hexWindow->selStart = selectFromChar;
			else											hexWindow->selStart = GetHandleSize(hexWindow->data);
															hexWindow->selEnd = selectToChar;
		}
		
		if( hexWindow->selStart < 0 )								hexWindow->selStart = 0;
		if( hexWindow->selEnd > GetHandleSize(hexWindow->data) )	hexWindow->selEnd = GetHandleSize(hexWindow->data);
		
		// invalidate window incase there was a selection
		Rect windowBounds;
		GetWindowPortBounds( window, &windowBounds );
		InvalidateWindowRect( window, &windowBounds );
	}
	
	DisposeRgn( hexRgn );
	DisposeRgn( asciiRgn );
	DisposeRgn( selectedRgn );
	return noErr;
}

/*** HANDLE EDIT DRAG ***/
OSStatus HandleEditDrag( WindowRef window, EventRef event, Point mouse, EventModifiers modifiers )
{
	#pragma unused( window, event, mouse, modifiers )
	return eventNotHandledErr;
}

/*** HANDLE KEY DOWN ***/
OSStatus HandleKeyDown( WindowRef window, unsigned char charCode, EventModifiers modifiers )
{
	OSStatus error = eventNotHandledErr;
	
	// get hex info
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	Plug_ResourceRef resource = Host_GetTargetResource( plugWindow );
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	
	// handle arrow keys
	Boolean resEdited = false;
	switch( charCode )
	{
		case kTabCharCode:
		case kEnterCharCode:
		case kReturnCharCode:
			hexWindow->editingHex = !hexWindow->editingHex;
			break;
		
		case kBackspaceCharCode:		// delete
			if( hexWindow->selStart == hexWindow->selEnd ) {
				if( hexWindow->selStart != 0 ) {
					hexWindow->InsertBytes( nil, -1, hexWindow->selEnd );									// delete prev char
					hexWindow->selStart = hexWindow->selEnd -= 1; } }
			else {
				hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd );	// remove selection
				hexWindow->selEnd = hexWindow->selStart; }
			
			Host_SetResourceDirty( resource, true );
			break;

		case kDeleteCharCode:			// forward delete
			if( hexWindow->selStart == hexWindow->selEnd )
			{
				if( hexWindow->selStart != GetHandleSize( hexWindow->data ) )
					hexWindow->InsertBytes( nil, -1, hexWindow->selEnd +1 );								// delete next char
			}
			else
			{
				hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd );	// remove selection
				hexWindow->selEnd = hexWindow->selStart;
			}
			Host_SetResourceDirty( resource, true );
			break;

		case kLeftArrowCharCode:
		case kRightArrowCharCode:
		case kUpArrowCharCode:
		case kDownArrowCharCode:
			error = HandleArrowKeyDown( window, charCode, modifiers );
			break;
		
		default:
			if( hexWindow->editingHex )		// editing in hexadecimal
			{
				Boolean deletePrev = false;	// delete prev typing to add new one
				if( hexWindow->editedHigh )	// edited high bits already
				{
					// shift typed char into high bits and add new low char
					if( charCode >= 0x30 && charCode <= 0x39 )		charCode -= 0x30;		// 0 to 9
					else if( charCode >= 0x41 && charCode <= 0x46 )	charCode -= 0x37;		// A to F
					else if( charCode >= 0x61 && charCode <= 0x66 )	charCode -= 0x57;		// a to f
					else break;
					hexWindow->hexChar <<=  4;				// store high bit
				 	hexWindow->hexChar += charCode & 0x0F;	// add low bit
					hexWindow->selStart += 1;
					hexWindow->selEnd = hexWindow->selStart;
					hexWindow->editedHigh = false;
					deletePrev = true;
				}
				else				// editing low bits
				{
					// put typed char into low bits
					if( charCode >= 0x30 && charCode <= 0x39 )		charCode -= 0x30;		// 0 to 9
					else if( charCode >= 0x41 && charCode <= 0x46 )	charCode -= 0x37;		// A to F
					else if( charCode >= 0x61 && charCode <= 0x66 )	charCode -= 0x57;		// a to f
					else break;
					hexWindow->hexChar = charCode & 0x0F;
					hexWindow->editedHigh = true;
				}
				hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd );	// remove selection
				hexWindow->selEnd = hexWindow->selStart;
				if( deletePrev )
				{
					hexWindow->InsertBytes( nil, -1, hexWindow->selStart );									// remove previous hex char
					hexWindow->InsertBytes( &hexWindow->hexChar, 1, hexWindow->selStart -1 );				// insert typed char (bug fix hack)
				}
				else hexWindow->InsertBytes( &hexWindow->hexChar, 1, hexWindow->selStart );					// insert typed char
			}
			else					// editing in ascii
			{
				hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd );	// remove selection
				hexWindow->selEnd = hexWindow->selStart;
				hexWindow->InsertBytes( &charCode, 1, hexWindow->selStart );								// insert typed char
				hexWindow->selStart += 1;
				hexWindow->selEnd = hexWindow->selStart;
			}
			Host_SetResourceDirty( resource, true );
			break;
	}
	
	// check selection is within resource
	if( hexWindow->selStart > hexWindow->selEnd )				hexWindow->selStart = hexWindow->selEnd;
	if( hexWindow->selStart > GetHandleSize(hexWindow->data) )	hexWindow->selStart = GetHandleSize(hexWindow->data);
	if( hexWindow->selEnd > GetHandleSize(hexWindow->data) )	hexWindow->selEnd   = GetHandleSize(hexWindow->data);
	
	// modify key pressed so scroller interperets it correctly
	if( modifiers & controlKey ) switch( charCode )
	{
		case kUpArrowCharCode:
			charCode = kDownArrowCharCode;
			break;
		
		case kDownArrowCharCode:
			charCode = kUpArrowCharCode;
			break;
		
		case kLeftArrowCharCode:
			charCode = kRightArrowCharCode;
			break;
		
		case kRightArrowCharCode:
			charCode = kLeftArrowCharCode;
			break;
	}

	// get scrollbar
#if TARGET_API_MAC_CARBON
	ControlRef scrollbar;
	ControlID id = { kScrollbarSignature, 0 };
	GetControlByID( window, &id, &scrollbar );
#else
	ControlRef scrollbar = hexWindow->scrollbar;
#endif
	
	// scroll to selection
	switch( charCode )
	{
		case kUpArrowCharCode:
			if( hexWindow->selStart /16 < hexWindow->topline +1 )
				SetControlValue( scrollbar, hexWindow->selStart /16 -1 );
			break;
		
		case kDownArrowCharCode:
			if( hexWindow->selEnd /16 > hexWindow->bottomline -1 )
				SetControlValue( scrollbar, hexWindow->selEnd /16 - (hexWindow->bottomline - hexWindow->topline) +1 );
			break;
		
		case kBackspaceCharCode:
		case kLeftArrowCharCode:
			if( hexWindow->selStart /16 < hexWindow->topline +1 )
				SetControlValue( scrollbar, hexWindow->selStart /16 -1 );
			break;
		
		case kRightArrowCharCode:
		default:
			if( hexWindow->selEnd /16 > hexWindow->bottomline -1 )
				SetControlValue( scrollbar, hexWindow->selEnd /16 - (hexWindow->bottomline - hexWindow->topline) +1 );
			break;
	}
	return error;
}

/*** HANDLE ARROW KEY DOWN ***/
OSStatus HandleArrowKeyDown( WindowRef window, unsigned char charCode, EventModifiers modifiers )
{
	OSStatus error = noErr;
	
	// get hex info
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	Plug_ResourceRef resource = Host_GetTargetResource( plugWindow );
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	
	if( modifiers & optionKey ) switch( charCode )	// move selection
	{
		case kLeftArrowCharCode:
			hexWindow->selStart	-= 1;
			hexWindow->selEnd	-= 1;
			break;
		case kRightArrowCharCode:
			hexWindow->selStart	+= 1;
			hexWindow->selEnd	+= 1;
			break;
		case kUpArrowCharCode:
			hexWindow->selStart	-= 16;
			hexWindow->selEnd	-= 16;
			break;
		case kDownArrowCharCode:
			hexWindow->selStart	+= 16;
			hexWindow->selEnd	+= 16;
			break;
	}
	
	else if( modifiers & shiftKey ) switch( charCode )	// extend selection
	{
		case kLeftArrowCharCode:
			hexWindow->selStart	-= 1;
			break;
		case kRightArrowCharCode:
			hexWindow->selEnd	+= 1;
			break;
		case kUpArrowCharCode:
			hexWindow->selStart	-= 16;
			break;
		case kDownArrowCharCode:
			hexWindow->selEnd	+= 16;
			break;
	}
	
	else if( modifiers & controlKey ) switch( charCode )	// reduce selection
	{
		case kLeftArrowCharCode:
			hexWindow->selEnd	-= 1;
			break;
		case kRightArrowCharCode:
			hexWindow->selStart	+= 1;
			break;
		case kUpArrowCharCode:
			hexWindow->selEnd	-= 16;
			break;
		case kDownArrowCharCode:
			hexWindow->selStart	+= 16;
			break;
	}
	
	else switch( charCode )									// move cursor
	{
		case kLeftArrowCharCode:
			if( hexWindow->selStart == hexWindow->selEnd )
			{
				if( hexWindow->selStart >= 1 )	hexWindow->selStart	-= 1;
				if( hexWindow->selEnd >= 1 )	hexWindow->selEnd	-= 1;
			}
			else hexWindow->selEnd = hexWindow->selStart;
			hexWindow->editedHigh = false;
			break;
		
		case kRightArrowCharCode:
			if( hexWindow->selStart == hexWindow->selEnd )
			{
				hexWindow->selStart += 1;
				hexWindow->selEnd += 1;
			}
			else hexWindow->selStart = hexWindow->selEnd;
			hexWindow->editedHigh = false;
			break;
		
		case kUpArrowCharCode:
			if( hexWindow->selStart == hexWindow->selEnd )
			{
				if( hexWindow->selStart >= 16 )	hexWindow->selStart	-= 16;
				else							hexWindow->selStart	= 0;
				if( hexWindow->selEnd >= 16 )	hexWindow->selEnd	-= 16;
				else							hexWindow->selEnd	= 0;
			}
			else hexWindow->selEnd = hexWindow->selStart;
			hexWindow->editedHigh = false;
			break;
		
		case kDownArrowCharCode:
			if( hexWindow->selStart == hexWindow->selEnd ) {
				hexWindow->selStart += 16;
				hexWindow->selEnd += 16; }
			else hexWindow->selStart = hexWindow->selEnd;
			hexWindow->editedHigh = false;
			break;
	}
	
	return error;
}

/*** FIND SELECTED REGIONS ***/
OSStatus FindSelectedRegions( WindowRef window, RgnHandle hexRgn, RgnHandle asciiRgn )
{
	// get hex info structure
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	
	// get window bounds
	Rect windowBounds, subtractRect;
	RgnHandle subtractRgn = NewRgn();
	GetWindowPortBounds( window, &windowBounds );
	
	// start and end points of selection relative to first visable char
	Boolean clipTop, clipBottom;
	signed long startLine = hexWindow->selStart/16 - hexWindow->topline;
	signed long endLine = (hexWindow->selEnd - hexWindow->selEnd %16)/16 - hexWindow->topline +1;
	
	// find hex region
	Rect hexSelRect;
	SetRect( &hexSelRect, kHexCharWidth *11 -1, startLine * kHexLineHeight, kHexCharWidth *58 +1, endLine * kHexLineHeight );
	OffsetRect( &hexSelRect, 0, kHeaderHeight + kTextMargin + 2 );
	clipTop				= hexSelRect.top < kHeaderHeight + kTextMargin +2;
	clipBottom			= hexSelRect.bottom > windowBounds.bottom - kTextMargin -2;
	if( clipTop )		hexSelRect.top = kHeaderHeight + kTextMargin +2;
	if( clipBottom )	hexSelRect.bottom = windowBounds.bottom - kTextMargin -2;
	RectRgn( hexRgn, &hexSelRect );
	
	if( !clipTop )
	{
		SetRect( &subtractRect, hexSelRect.left, hexSelRect.top, hexSelRect.left + (hexWindow->selStart % 16) * (kHexCharWidth *3), hexSelRect.top + kHexLineHeight );
		RectRgn( subtractRgn, &subtractRect );
		DiffRgn( hexRgn, subtractRgn, hexRgn );
	}
	
	if( !clipBottom )
	{
		SetRect( &subtractRect, hexSelRect.right - (16 - hexWindow->selEnd % 16) * (kHexCharWidth *3), hexSelRect.bottom - kHexLineHeight, hexSelRect.right, hexSelRect.bottom );
		RectRgn( subtractRgn, &subtractRect );
		DiffRgn( hexRgn, subtractRgn, hexRgn );
	}
		
	// find ascii region
	Rect asciiSelRect;
	SetRect( &asciiSelRect, kHexCharWidth *60 -1, startLine * kHexLineHeight, kHexCharWidth *76 +1, endLine * kHexLineHeight );
	OffsetRect( &asciiSelRect, 0, kHeaderHeight + kTextMargin + 2 );
	if( clipTop )		asciiSelRect.top = kHeaderHeight + kTextMargin +2;
	if( clipBottom )	asciiSelRect.bottom = windowBounds.bottom - kTextMargin -2;
	RectRgn( asciiRgn, &asciiSelRect );
	
	if( !clipTop )
	{
		SetRect( &subtractRect, asciiSelRect.left, asciiSelRect.top, asciiSelRect.left + (hexWindow->selStart % 16) * kHexCharWidth, asciiSelRect.top + kHexLineHeight );
		RectRgn( subtractRgn, &subtractRect );
		DiffRgn( asciiRgn, subtractRgn, asciiRgn );
	}
	
	if( !clipBottom )
	{
		SetRect( &subtractRect, asciiSelRect.right - (16 - hexWindow->selEnd % 16) * kHexCharWidth, asciiSelRect.bottom - kHexLineHeight, asciiSelRect.right, asciiSelRect.bottom );
		RectRgn( subtractRgn, &subtractRect );
		DiffRgn( asciiRgn, subtractRgn, asciiRgn );
	}
	
	// clear up
	DisposeRgn( subtractRgn );
	return noErr;
}

/*** UPDATE SELECTION ***/
OSStatus UpdateSelection( WindowRef window, Boolean editingHex )
{
	// get controls
	ControlRef root, hex, ascii;
	GetRootControl( window, &root );
	GetIndexedSubControl( root, 3, &hex );
	GetIndexedSubControl( root, 4, &ascii );
	
	// reflect selection
	Size actualSize;
	ControlEditTextSelectionRec selection;
	GetControlData( editingHex? hex:ascii, kControlEditTextPart, kControlEditTextSelectionTag, sizeof(ControlEditTextSelectionRec), &selection, &actualSize );
	if( editingHex )
	{
		selection.selStart /= 3;
		selection.selEnd = (selection.selEnd +1) /3;
	}
	else
	{
		selection.selStart *= 3;
		selection.selEnd = (selection.selEnd *3) -1;
	}
	SetControlData( editingHex? ascii:hex, kControlEditTextPart, kControlEditTextSelectionTag, actualSize, &selection );
	return noErr;
}

  /********************/
 /* CONTROL HANDLING */
/********************/

/*** TRACK SCROLL BAR ***/
pascal void TrackScrollbar( ControlRef control, short part )
{
	WindowRef window = GetControlOwner(control);
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	
	// decide how many lines to scroll ( and in which direction )
	short startValue, delta, endValue, max;
	if( !part ) return;
	switch( part )
	{
		case kControlUpButtonPart:			// up (20)
			delta = -1;
		  	break;
			
		case kControlDownButtonPart:		// down (21)
			delta = 1;
		  	break;
			
		case kControlPageUpPart:			// page up (22)
			delta = hexWindow->topline - hexWindow->bottomline +1;
		  	break;
			
		case kControlPageDownPart:			// page down (23)
			delta = hexWindow->bottomline - hexWindow->topline -1;
		  	break;
			
		default:
		 	delta = 0;
		 	break;
	}

	// calculate and correct control value
	max			= GetControlMaximum( control );
	startValue	= GetControlValue( control );
	endValue	= startValue + delta;
	if( endValue < 0 )		endValue = 0;
	if( endValue > max )	endValue = max;
	SetControlValue( control, endValue );
	hexWindow->UpdateHexInfo();	
	
	// update the window
//	InvalidateWindowRect( window, &windowBounds );	// doesn't update live scroll immediatly anyway, so may as well not call it
	hexWindow->DrawContent();
}

  /******************/
 /* TIMER ROUTINES */
/******************/

/*** BLINK INSERTION POINT ***/
pascal void BlinkInsertionPoint( EventLoopTimerRef inTimer, void *inUserData )
{
	#pragma unused( inTimer )	// inTimer can be null (if routine was not called from a timer)
	
	// get window's info structure
	WindowRef window = (WindowRef) inUserData;
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow );
	
	// if not focus window, don't flash cursor
	if( !hexWindow->activeWindow ) return;
	
	// if there's a selection, don't flash cursor
	if( hexWindow->selStart != hexWindow->selEnd ) return;
	
	// if insertion point is not within visable region, don't flash cursor
	if( hexWindow->selStart < hexWindow->topline * 16 ) return;
	if( hexWindow->selEnd >= (hexWindow->bottomline +1) * 16 ) return;
	
	GrafPtr oldPort;
	GetPort( &oldPort );
	SetPortWindowPort( window );
	Rect blinkRect;
	
	long theStart = hexWindow->selStart - hexWindow->topline * 16;
	long theEnd = hexWindow->selEnd - hexWindow->topline * 16;
	if( hexWindow->editingHex )	SetRect( &blinkRect,	kHexCharWidth *11 + (theStart %16) * (kHexCharWidth *3) -1,	(theStart /16) * kHexLineHeight + kTextMargin +2,
														kHexCharWidth *11 + (theStart %16) * (kHexCharWidth *3) +1,	(theStart /16) * kHexLineHeight + kTextMargin +2 + kHexLineHeight );
	else						SetRect( &blinkRect,	kHexCharWidth *60 + (theStart %16) * kHexCharWidth -1,		(theStart /16) * kHexLineHeight + kTextMargin +2,
														kHexCharWidth *60 + (theStart %16) * kHexCharWidth +1,		(theStart /16) * kHexLineHeight + kTextMargin +2 + kHexLineHeight );
	OffsetRect( &blinkRect, 0, kHeaderHeight );
	InvertRect( &blinkRect );
	hexWindow->insertionPointVisable = !hexWindow->insertionPointVisable;
	
	SetPort( oldPort );
}