/*
 *  tracker/carbon/Carbon_main.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

// ------------------------ carbon ------------------------
#include <Carbon/Carbon.h>
#include "Carbon_Definitions.h"
#include "KeyTranslation.h"
#include "PreferencesDialog.h"
#include "PPMutex.h"
#include "PPMessageBox.h"
// ----------------------- POSIX --------------------------
#include <sys/stat.h>
#include <signal.h>
// ----------------------- tracker ------------------------
#include "PPUI.h"
#include "DisplayDevice_CARBON.h"
#include "Screen.h"
#include "Tracker.h"
#include "PPSystem.h"
#include "MidiReceiver_pthread.h"

// globals -------------------------------------
WindowRef					mainWindow, waitWindow, preferencesWindow;
MenuRef						mainMenu;

static PreferencesDialog*	preferencesDialog	= NULL;
static PPMutex*				globalMutex			= NULL;

static int					localMouseX,localMouseY;

static PPScreen*			myTrackerScreen		= NULL;
static Tracker*				myTracker			= NULL;
static PPDisplayDevice*		myDisplayDevice		= NULL;
static MidiReceiver*		myMidiReceiver		= NULL;

// basic OS event globals ----------------------
static pp_uint32	lmyTime;
static PPPoint		llastClickPosition		= PPPoint(0,0);
static pp_uint16	lClickCount				= 0;

static pp_uint32	rmyTime;
static PPPoint		rlastClickPosition		= PPPoint(0,0);
static pp_uint16	rClickCount				= 0;

static bool			lMouseDown				= false;
static pp_uint32	lButtonDownStartTime;

static bool			rMouseDown				= false;
static pp_uint32	rButtonDownStartTime;

static pp_uint32	timerTicker				= 0;
//static pp_int32		keyTimerTicker			= 0;
static pp_uint32	lastModifierKeyState	= 0;

static PPPoint p;

static bool			sixteenBitColorDepth	= false;

pp_uint32 PPGetTickCount()
{
	UnsignedWide microTickCount;
	Microseconds (&microTickCount);	
	pp_int64 time = (((pp_int64)microTickCount.hi) << 32) + microTickCount.lo;
	return time/1000;		
}

static void crashHandler(int signum) 
{
	// Save backup.xm
	static char buffer[1024]; // Should be enough :p
	static char message[1024];
	strncpy(buffer, getenv("HOME"), 1010);
	strcat(buffer, "/BACKUP00.XM");
	struct stat statBuf;
	int num = 1;
	while (stat(buffer, &statBuf) == 0 && num <= 100)
		snprintf(buffer, sizeof(buffer), "%s/BACKUP%02i.XM", getenv("HOME"), num++);

	if(num != 100) 
	{
		myTracker->saveModule(buffer);
		fputs("\n", stderr);
		fprintf(stderr, "\nA backup has been saved to %s\n\n", buffer);
	}

	if(signum == 15)
	{
		snprintf(message, sizeof(message), "TERM signal received.\n");
		fputs(message, stderr);
	}
	else
	{
		snprintf(message, sizeof(message), "MilkyTracker crashed with signal %i\n"
				"Please submit a bug report stating exactly what you were doing at "
				"the time of the crash, as well as the above signal number.  "
				"Also note if it is possible to reproduce this crash.\n", signum);
		fprintf(stderr, "\n");
		fputs(message, stderr);
		
		PPMessageBox infoBox(myTrackerScreen, "Error", message);
		infoBox.runModal();
	}

	exit(0);
}

static void installCrashHandler()
{
	// Initialise crash handler
	struct sigaction act;
	struct sigaction oldAct;
	memset(&act, 0, sizeof(act));
	act.sa_handler = crashHandler;
	act.sa_flags = SA_RESETHAND;
	sigaction(SIGTERM | SIGILL | SIGABRT | SIGFPE | SIGSEGV, &act, &oldAct);
	sigaction(SIGILL, &act, &oldAct);
	sigaction(SIGABRT, &act, &oldAct);
	sigaction(SIGFPE, &act, &oldAct);
	sigaction(SIGSEGV, &act, &oldAct);
}

// Some forwards
pascal OSStatus MainWindowEventHandler(EventHandlerCallRef myHandler,EventRef event,void *userData);

void RaiseEventSynchronized(PPEvent* event)
{
	if (globalMutex->tryLock())
	{
		if (myTrackerScreen)
			myTrackerScreen->raiseEvent(event);
			
		globalMutex->unlock();
	}
}

void TimerEventHandler(__EventLoopTimer*, void*)
{
	if (!myTrackerScreen)
		return;

	PPEvent myEvent(eTimer);	
	RaiseEventSynchronized(&myEvent);
	
	/*if ((keyTimerTicker > 0) && !(keyTimerTicker % 3))
	{
		if (TestForKeyDown(kVirtualCapsLockKey))
		{
			pp_uint16 chr[2];
			chr[0] = VK_CAPITAL;
			chr[1] = SC_CAPSLOCK;
			PPEvent event(eKeyDown, &chr, sizeof(chr));
			myTrackerScreen->raiseEvent(&event);
		}
	}
	
	keyTimerTicker++;*/
	
	timerTicker++;
	
	if (lMouseDown &&
		(timerTicker - lButtonDownStartTime) > 25 && !(timerTicker%3))
	{
		PPEvent myEvent(eLMouseRepeat, &p, sizeof(PPPoint));		
		RaiseEventSynchronized(&myEvent);
	}

	if (rMouseDown &&
		(timerTicker - rButtonDownStartTime) > 25)
	{
		PPEvent myEvent(eRMouseRepeat, &p, sizeof(PPPoint));		
		RaiseEventSynchronized(&myEvent);
	}

}

void StopMidiRecording()
{
	if (myMidiReceiver)
	{
		myMidiReceiver->stopRecording();
	}
}

class MidiEventHandler : public MidiReceiver::MidiEventHandler
{
public:
	virtual void keyDown(int note, int volume) 
	{
		globalMutex->lock();
		myTracker->sendNoteDown(note, volume);
		globalMutex->unlock();
	}

	virtual void keyUp(int note) 
	{
		globalMutex->lock();
		myTracker->sendNoteUp(note);
		globalMutex->unlock();
	}
};

MidiEventHandler midiEventHandler;

void StartMidiRecording(unsigned int devID, bool recordMidiVelocity, unsigned int velocityAmplify)
{
	if (devID == (unsigned)-1)
		return;

	StopMidiRecording();

	myMidiReceiver = new MidiReceiver(midiEventHandler);
	myMidiReceiver->setRecordVelocity(recordMidiVelocity);
	myMidiReceiver->setVelocityAmplify(velocityAmplify);	

	if (!myMidiReceiver->startRecording(devID))
	{
		// Deal with error
		PPMessageBox infoBox(myTrackerScreen, "Error","Could not enable MIDI recording.");
		infoBox.runModal();
	}
}

void InitMidi()
{
	if (!preferencesDialog)
		return;

	if (preferencesDialog->getUseMidiDeviceFlag())
		StartMidiRecording(preferencesDialog->getSelectedMidiDeviceID(),
						   preferencesDialog->getRecordVelocityFlag(),
						   preferencesDialog->getVelocityAmplify());
	else
		StopMidiRecording();
}

void ApplyPreferences()
{
	InitMidi();
	enableInsertKeyEmulation((InsertKeyShortcuts)preferencesDialog->getFakeInsertKey());
	if (preferencesDialog->getUse15BitColorDepth() != sixteenBitColorDepth)
	{
		PPMessageBox infoBox(myTrackerScreen, "Please restart",
							 "MilkyTracker needs to be restarted to apply new color depth");
		infoBox.runModal();
	}
}

void initTracker()
{
	myTracker = new Tracker();

	PPSize windowSize = myTracker->getWindowSizeFromDatabase();
	pp_int32 scaleFactor = myTracker->getScreenScaleFactorFromDatabase();
	bool fullScreen = myTracker->getFullScreenFlagFromDatabase();
#ifdef __LOWRES__
	windowSize.width = 320;
	windowSize.height = 240;
#endif

	sixteenBitColorDepth = preferencesDialog->getUse15BitColorDepth();

	myDisplayDevice = new PPDisplayDevice(mainWindow, 
										  waitWindow, 
										  windowSize.width, 
										  windowSize.height, 
										  scaleFactor,
										  sixteenBitColorDepth ? 16 : 32);
	
	myDisplayDevice->init();

	if (fullScreen)
		myDisplayDevice->goFullScreen(fullScreen);
	
	myTrackerScreen = new PPScreen(myDisplayDevice, myTracker);

	myTracker->setScreen(myTrackerScreen);
 
	// Startup procedure
	myTracker->startUp();

	ApplyPreferences();

	// install crash handler
//#ifndef __DEBUG__
//	installCrashHandler();
//#endif
}

static Boolean gApprovedDrag = false; /* set to true if the drag is approved */
static Boolean gInIconBox = false; /* set to true if the drag is approved */

static void dropFileHandler(const FSSpec& spec)
{
	FSRef fileToOpen;
	FSpMakeFSRef(&spec, &fileToOpen);
	// gib ihm
	char buffer[PATH_MAX+1];
	
	FSRefMakePath (&fileToOpen, (UInt8*)buffer, sizeof(buffer)-1);
	PPSystemString finalFile(buffer);
	PPSystemString* strPtr = &finalFile;
	
	PPEvent event(eFileDragDropped, &strPtr, sizeof(PPSystemString*));
	RaiseEventSynchronized(&event);
}

static pascal OSErr myDragReceiveHandler(WindowPtr theWindow, void *refcon, DragReference theDragRef) 
{
	ItemReference theItem;
	HFSFlavor targetFile;
	//PromiseHFSFlavor targetPromise;
	//FSSpec targetSpec;
	Size theSize;
	OSErr err = eventNotHandledErr;
	
	if (!gApprovedDrag)
		goto bail;

	// get the first item reference 
	if ((err = GetDragItemReferenceNumber(theDragRef, 1, &theItem)) != noErr) goto bail;
		
	// try to get a  HFSFlavor
	theSize = sizeof(HFSFlavor);
	err = GetFlavorData(theDragRef, theItem, flavorTypeHFS, &targetFile, &theSize, 0);
	if (err == noErr) 
	{
		dropFileHandler(targetFile.fileSpec);
		return noErr;
	} 
	else if (err != badDragFlavorErr) 
		goto bail;
	
bail:
	return err;
}

static pascal OSErr approveDragReference(DragReference theDragRef, Boolean *approved) {
	OSErr err;
	UInt16 itemCount;
	DragAttributes dragAttrs;
	FlavorFlags flavorFlags;
	ItemReference theItem;
		
	// we cannot drag to our own window 
	if ((err = GetDragAttributes(theDragRef, &dragAttrs)) != noErr) goto bail;
	if ((dragAttrs & kDragInsideSenderWindow) != 0) { err = userCanceledErr; goto bail; }
	
	// we only accept drags containing one item 
	if ((err = CountDragItems(theDragRef, &itemCount)) != noErr) goto bail;
	if (itemCount != 1) { err = paramErr; goto bail; }
		
	// gather information about the drag & a reference to item one.
	if ((err = GetDragItemReferenceNumber(theDragRef, 1, &theItem)) != noErr) goto bail;
		
	// check for flavorTypeHFS
	err = GetFlavorFlags(theDragRef, theItem, flavorTypeHFS, &flavorFlags);
	if (err == noErr) {
		*approved = true;
		return noErr;
	} else if (err != badDragFlavorErr)
		goto bail;
		
	// check for flavorTypePromiseHFS 
	//err = GetFlavorFlags(theDragRef, theItem, flavorTypePromiseHFS, &flavorFlags);
	//if (err == noErr) {
	//	*approved = true;
	//	return noErr;
	//} else if (err != badDragFlavorErr)
	//	goto bail;
		
	// none of our flavors were found
	*approved = false;
	return noErr;
bail:
	// an error occured, clean up.  set result to false.
	*approved = false;
	return err;
}

static pascal OSErr myDragTrackingHandler(DragTrackingMessage message, WindowPtr theWindow, void *refCon, DragReference theDragRef) 
{
	// we're drawing into the image well if we hilite... 
	switch (message) {
	
		case kDragTrackingEnterWindow:
			{	
				gApprovedDrag = false;
				if (theWindow == FrontWindow()) {
					if (approveDragReference(theDragRef, &gApprovedDrag) != noErr) break;
					if ( ! gApprovedDrag ) break;
					myTrackerScreen->setShowDragHilite(true);
					gInIconBox = true;
				}
			}
			break;

		/*case kDragTrackingInWindow:
			if (gApprovedDrag) {
				Point mouse;
				SetPortWindowPort(mainWindow);
				GetMouse(&mouse);
				GlobalToLocal(&mouse);
				if (PtInRect(mouse, &gIconBox)) {
					if ( ! gInIconBox) {  // if we're entering the box, hilite... 
						SetPortWindowPort(mainWindow);
						gInIconBox = (ShowDragHiliteBox(theDragRef, &gIconBox) == noErr);
					}
				} else if (gInIconBox) {  // if we're exiting the box, unhilite... 
					HideDragHilite(theDragRef);
					gInIconBox = false;
				}
			}
			break;*/

		case kDragTrackingLeaveWindow:
			if (gApprovedDrag && gInIconBox) {
				//HideDragHilite(theDragRef);
				myTrackerScreen->setShowDragHilite(false);
			}
			gApprovedDrag = gInIconBox = false;
			break;
	}
	
	return noErr; // there's no point in confusing Drag Manager or its caller
}

static Boolean gotRequiredParams(const AppleEvent *theEvent)
{
	DescType returnedType;
	Size 	actualSize;
	OSErr	err = AEGetAttributePtr( theEvent, keyMissedKeywordAttr, typeWildCard, &returnedType, NULL, 0, &actualSize);
	return err == errAEDescNotFound;
}

static OSStatus doOpenAppleEvent(const AppleEvent *theEvent)
{
	OSStatus	error;

	FSSpec		myFSS;
	AEDescList	theList;
	AEKeyword	aeKeyword = keyDirectObject;
	long		itemCount, i;
	DescType	actualType;
	Size		actualSize;
	
	// get event description
	error = AEGetParamDesc(theEvent, keyDirectObject, typeAEList, &theList);
	if( error != noErr )
		return error;

	// make sure event description is correct
	if (!gotRequiredParams(theEvent))
		return error;

	// count items to open
	error = AECountItems(&theList, &itemCount);
	if( error != noErr )
		return error;

	// open all items
	for (i = 1; i <= itemCount; i++)
	{
		error = AEGetNthPtr(&theList, i, typeFSS, &aeKeyword, &actualType, (Ptr) &myFSS, sizeof( FSSpec ), &actualSize);
		if (error == noErr)
			dropFileHandler(myFSS);
	}

	// event was handled successfully
	AEDisposeDesc(&theList);
	
	return noErr;
}

static pascal OSErr coreEventHandler(const AppleEvent *theEvent, AppleEvent *reply, long refCon)
{
	DescType	actualType;
	Size		actualSize;
	DescType	eventID;
	OSErr		error;

	error = AEGetAttributePtr( ( AppleEvent* ) theEvent, keyEventIDAttr, typeType, &actualType, (Ptr) &eventID, sizeof( eventID ), &actualSize);

	if (error)
		return error;
								
	switch( eventID )
	{
		case kAEOpenApplication:
			if (gotRequiredParams(theEvent))
				doOpenAppleEvent(theEvent);
			break;
				
		case kAEOpenDocuments:
			doOpenAppleEvent(theEvent);
			break;
				
	}
	return noErr;
}

static void initAppleEvents()
{
	AEEventHandlerUPP AEHandlerUPP;
	
	AEHandlerUPP = NewAEEventHandlerUPP(coreEventHandler);

	AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, AEHandlerUPP, 0, false);
	AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, AEHandlerUPP, 0, false);
}

int main(int argc, char* argv[])
{
    IBNibRef 		nibRef;
	EventLoopRef	loopRef;
    
    OSStatus		err;
  
	globalMutex = new PPMutex();
  
    EventTypeSpec mainSpec[] = 
	{
		{kEventClassCommand,kEventCommandProcess},
		{kEventClassKeyboard,kEventRawKeyDown},
		{kEventClassKeyboard,kEventRawKeyRepeat},
		{kEventClassKeyboard,kEventRawKeyUp},
		{kEventClassKeyboard,kEventRawKeyModifiersChanged},
		{kEventClassMouse,kEventMouseDown},
		{kEventClassMouse,kEventMouseUp},
		{kEventClassMouse,kEventMouseMoved},
		{kEventClassMouse,kEventMouseExited},
		{kEventClassMouse,kEventMouseDragged},
		{kEventClassMouse,kEventMouseWheelMoved},
		{kEventClassTextInput,kEventUnicodeForKeyEvent},
		{kEventClassWindow,kEventWindowDrawContent},
		{kEventClassWindow,kEventWindowBoundsChanged},
		{kEventClassWindow,kEventWindowClose}
	};
	
    // Create a Nib reference passing the name of the nib file (without the .nib extension)
    // CreateNibReference only searches into the application bundle.
    err = CreateNibReference(CFSTR("main"), &nibRef);
    require_noerr( err, CantGetNibRef );
    
    // Once the nib reference is created, set the menu bar. "MainMenu" is the name of the menu bar
    // object. This name is set in InterfaceBuilder when the nib is created.
	err = CreateMenuFromNib (nibRef, CFSTR("MenuBar"), &mainMenu);
	require_noerr( err, CantSetMenuBar );

	err = SetRootMenu (mainMenu);
	require_noerr( err, CantSetMenuBar );

    // Then create a window. "MainWindow" is the name of the window object. This name is set in 
    // InterfaceBuilder when the nib is created.
    err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &mainWindow);
    require_noerr( err, CantCreateWindow );

    // Create wait progress bar window from nib
	err = CreateWindowFromNib(nibRef, CFSTR("WaitWindow"), &waitWindow);
    require_noerr( err, CantCreateWindow );

    // Create preferences window from nib
	err = CreateWindowFromNib(nibRef, CFSTR("PreferencesWindow"), &preferencesWindow);
    require_noerr( err, CantCreateWindow );

    // We don't need the nib reference anymore.
    DisposeNibReference(nibRef);

	// Install handler for dropping files on application icon
	initAppleEvents();

	// Install drag receive handler
	err = InstallReceiveHandler(myDragReceiveHandler, mainWindow, NULL);
	// Install drag tracking handler
	err = InstallTrackingHandler(myDragTrackingHandler, mainWindow, NULL);

    // Install our own handler for main window
	InstallWindowEventHandler(mainWindow,NewEventHandlerUPP(MainWindowEventHandler),
							  sizeof(mainSpec)/sizeof(EventTypeSpec),(EventTypeSpec*)&mainSpec,(void*)mainWindow,NULL);

	// Create preferences window
	preferencesDialog = new PreferencesDialog(preferencesWindow, mainWindow);
    
	InitKeyCodeTranslation();
	
	// tracker-init 
	initTracker();
	
	loopRef = GetMainEventLoop();	    
	InstallEventLoopTimer(loopRef, 0.0f, 20.0f/1000.0f, TimerEventHandler, NULL, NULL);

    // Call the event loop
    RunApplicationEventLoop();

	globalMutex->lock();
	delete myMidiReceiver;
	globalMutex->unlock();
	delete myTracker;
	delete myTrackerScreen;
	delete myDisplayDevice;
	delete preferencesDialog;

CantCreateWindow:
CantSetMenuBar:
CantGetNibRef:
	delete globalMutex;

	return err;
}

void ProcessKeyEvent(EventRef event, EEventDescriptor targetEvent)
{
    UInt32	keyCode;
	
	GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(keyCode), NULL, &keyCode);
	
	UniChar* text = NULL;
	UInt32 size;
	
	/* request the size of the Unicode text */
	if (noErr != GetEventParameter(event, kEventParamKeyUnicodes,
								   typeUnicodeText, NULL, 0, &size, NULL))
		goto bail;
	
	if (size % sizeof(UniChar) != 0)
		goto bail;
	
	text = new UniChar[size];
	
	/* fetch the Unicode chars */
	if (noErr != GetEventParameter(event, kEventParamKeyUnicodes,
								   typeUnicodeText, NULL, size, NULL, text))
		goto bail;
	
	
	{
		pp_uint16 chr[3] = {KeyCodeToVK(keyCode), KeyCodeToSC(keyCode), *text};
		PPEvent event(targetEvent, &chr, sizeof(chr));
		RaiseEventSynchronized(&event);
	}
bail:
	if (text)
		delete[] text;
}

pascal OSStatus MainWindowEventHandler(EventHandlerCallRef myHandler,EventRef event,void *userData)
{
    OSStatus	result = eventNotHandledErr;
    
	if (!myTrackerScreen)
		return result;
	
	Rect	windowRect;
    UInt32	eventClass, eventKind;
    
    eventClass = GetEventClass(event);
    eventKind = GetEventKind(event);
	
	// -----------------------------------------
	if (lClickCount > 4)
	{
		lClickCount = 0;
	}	
	  
    switch (eventClass) 
	{
        case kEventClassCommand: 
		{
			switch (eventKind) 
			{
				case kEventCommandProcess: 
				{
					HICommand command;
					GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command);
					
					switch (command.commandID)
					{
						case 'quit':
						{
							// Shouldn't really handle this event, handle AppQuit instead
							PPEvent event(eAppQuit);
							RaiseEventSynchronized(&event);
							bool res = myTracker->shutDown();
							// shutdown was aborted 
							if (!res)
								result = noErr;
							break;
						}
						
						case kFullscreenCommand:
						{
							PPEvent myEvent(eFullScreen);
							RaiseEventSynchronized(&myEvent);
							result = noErr;
							break;
						}

						case kInvokePreferences:
						{
							if (preferencesDialog)
								preferencesDialog->show();
							result = noErr;
							break;
						}
						
						case kConfirmPreferences:
						{
							if (preferencesDialog)
								preferencesDialog->hide();
							
							ApplyPreferences();
							
							SelectWindow(mainWindow);
							result = noErr;
							break;
						}
							
						case kDiscardPreferences:
						{
							if (preferencesDialog)
								preferencesDialog->hide();
							
							SelectWindow(mainWindow);
							result = noErr;
							break;
						}
					}
					
					break;
				}
			}
			break;
		}
		
        case kEventClassWindow: 
		{
			switch (eventKind) 
			{
				case kEventWindowBoundsChanged:
				case kEventWindowDrawContent:
				{
					myTrackerScreen->update();
					break;
				}
				case kEventWindowClose:
				{
					// This is a single window app, so quit when the window is closed.
					// This could be simplified if we handled AppQuit instead of hooking the
					// quit command above.
					PPEvent event(eAppQuit);
					RaiseEventSynchronized(&event);
					bool res = myTracker->shutDown();
					if (res)
					{
						EventRef quitEvent;
						CreateEvent(NULL, kEventClassApplication, kEventAppQuit, 0, kEventAttributeNone, &quitEvent);
						PostEventToQueue(GetMainEventQueue(), quitEvent, kEventPriorityStandard);
					}
					result = noErr;
					break;
				}
			}
			break;
		}
			
		case kEventClassTextInput:
		{
			switch (eventKind) 
			{
				case kEventUnicodeForKeyEvent:
				{
					UniChar* text = NULL;
					UInt32	size;
					
					/* request the size of the Unicode text */
					if (noErr != GetEventParameter(event, kEventParamTextInputSendText,
												   typeUnicodeText, NULL, 0, &size, NULL))
						goto bail;

					if (size % sizeof(UniChar) != 0)
						goto bail;

					text = new UniChar[size];
					
					/* fetch the Unicode chars */
					if (noErr != GetEventParameter(event, kEventParamTextInputSendText,
												   typeUnicodeText, NULL, size, NULL, text))
						goto bail;
					
					{	
						pp_uint16 chr = *text;
						
						switch (chr)
						{
							// delete
							case 127:
								chr = VK_BACK;
								break;
							// backspace
							case 8:
								chr = VK_BACK;
								break;
							// escape (is 27 already?)
							case 27:
								chr = VK_ESCAPE;
								break;
							// return (is 13 already?)
							case 13:
								chr = VK_RETURN;
								break;
						}
						
						if (chr >= 32)
						{
							PPEvent event2(eKeyChar, &chr, sizeof(chr));
							RaiseEventSynchronized(&event2);
						}
					}
					
						
				bail:
					if (text)
						delete[] text;
				}
			}
			break;
		}
	
        case kEventClassMouse: 
		{
			Point mousePoint;
			GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(mousePoint), NULL, &mousePoint);
			SetPortWindowPort(mainWindow);
			short mouseButton;
			GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), NULL, &mouseButton);
			UInt32 keyModifiers;
			GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(keyModifiers), NULL, &keyModifiers);
			
			GetPortBounds(GetWindowPort(mainWindow),&windowRect);
			//double xScale = (double)myDisplayDevice->getSize().width / (windowRect.right - windowRect.left);
			//double yScale = (double)myDisplayDevice->getSize().height / (windowRect.bottom - windowRect.top);

			GlobalToLocal(&mousePoint);
			//localMouseX = (int)(mousePoint.h*xScale);
			//localMouseY = (int)(mousePoint.v*yScale);
			
			localMouseX = mousePoint.h;
			localMouseY = mousePoint.v;
			
			switch (eventKind) 
			{
				case kEventMouseWheelMoved:
				{
					long wheelDelta;
					GetEventParameter(event, kEventParamMouseWheelDelta, typeLongInteger, NULL, sizeof(wheelDelta), NULL, &wheelDelta);
					
					TMouseWheelEventParams mouseWheelParams;
					mouseWheelParams.pos.x = localMouseX;
					mouseWheelParams.pos.y = localMouseY;
					mouseWheelParams.delta = wheelDelta;
					
					PPEvent myEvent(eMouseWheelMoved, &mouseWheelParams, sizeof(mouseWheelParams));						
					RaiseEventSynchronized(&myEvent);				
					break;
				}
				
				case kEventMouseDown: 
				{
					if (mouseButton > 2 || !mouseButton)
						break;
						
					if (mouseButton == 1 && (keyModifiers & MacKeyModifierCtrl))
						mouseButton++;
						
					// -----------------------------
					p.x = localMouseX;
					p.y = localMouseY;
					
					if (mouseButton == 1)
					{
						//if (rMouseDown)
						//	break;
							
						PPEvent myEvent(eLMouseDown, &p, sizeof(PPPoint));						
						RaiseEventSynchronized(&myEvent);
						
						lMouseDown = true;
						lButtonDownStartTime = timerTicker;
						
						if (!lClickCount)
						{
							lmyTime = ::PPGetTickCount();
							llastClickPosition.x = localMouseX;
							llastClickPosition.y = localMouseY;
						}
						else if (lClickCount == 2)
						{
							pp_uint32 deltat = ::PPGetTickCount() - lmyTime;
							
							if (deltat > 500)
							{
								lClickCount = 0;
								lmyTime = ::PPGetTickCount();
								llastClickPosition.x = localMouseX;
								llastClickPosition.y = localMouseY;
							}
						}
						
						lClickCount++;	
						
					}
					else if (mouseButton == 2)
					{
						//if (lMouseDown)
						//	break;

						PPEvent myEvent(eRMouseDown, &p, sizeof(PPPoint));						
						RaiseEventSynchronized(&myEvent);
						
						rMouseDown = true;
						rButtonDownStartTime = timerTicker;
						
						if (!rClickCount)
						{
							rmyTime = ::PPGetTickCount();
							rlastClickPosition.x = localMouseX;
							rlastClickPosition.y = localMouseY;
						}
						else if (rClickCount == 2)
						{
							pp_uint32 deltat = ::PPGetTickCount() - rmyTime;
							
							if (deltat > 500)
							{
								rClickCount = 0;
								rmyTime = ::PPGetTickCount();
								rlastClickPosition.x = localMouseX;
								rlastClickPosition.y = localMouseY;
							}
						}
						
						rClickCount++;	
					}
					

					break;
				}

				case kEventMouseUp:
				{
					if (mouseButton > 2 || !mouseButton)
						break;

					if (mouseButton == 1 && (keyModifiers & MacKeyModifierCtrl))
						mouseButton++;

					if (!myTrackerScreen)
						break;					
					// -----------------------------
					
					if (mouseButton == 1)
					{
						//if (!lMouseDown)
						//	break;
							
						lClickCount++;
						
						if (lClickCount == 4)
						{
							pp_uint32 deltat = ::PPGetTickCount() - lmyTime;
							
							if (deltat < 500)
							{
								p.x = localMouseX;
								p.y = localMouseY;
								
								if (abs(p.x - llastClickPosition.x) < 4 &&
									abs(p.y - llastClickPosition.y) < 4)
								{
									
									PPEvent myEvent(eLMouseDoubleClick, &p, sizeof(PPPoint));									
									RaiseEventSynchronized(&myEvent);
								}
							}
							
							lClickCount = 0;							
						}
						
						p.x = localMouseX;
						p.y = localMouseY;
						
						PPEvent myEvent(eLMouseUp, &p, sizeof(PPPoint));						
						RaiseEventSynchronized(&myEvent);
						
						lMouseDown = false;
					}
					else if (mouseButton == 2)
					{
						//if (!rMouseDown)
						//	break;

						rClickCount++;
						
						if (rClickCount == 4)
						{
							pp_uint32 deltat = ::PPGetTickCount() - rmyTime;
							
							if (deltat < 500)
							{
								p.x = localMouseX;
								p.y = localMouseY;
								
								if (abs(p.x - rlastClickPosition.x) < 4 &&
									abs(p.y - rlastClickPosition.y) < 4)
								{
									
									PPEvent myEvent(eRMouseDoubleClick, &p, sizeof(PPPoint));									
									RaiseEventSynchronized(&myEvent);
								}
							}
							
							rClickCount = 0;
						}
						
						p.x = localMouseX;
						p.y = localMouseY;
						
						PPEvent myEvent(eRMouseUp, &p, sizeof(PPPoint));						
						RaiseEventSynchronized(&myEvent);
						
						rMouseDown = false;
					}
					
				} 

				case kEventMouseMoved: 
				{
					p.x = localMouseX;
					p.y = localMouseY;

					PPEvent myEvent(eMouseMoved, &p, sizeof(PPPoint));
					RaiseEventSynchronized(&myEvent);
					break;
				}
				
				case kEventMouseExited:
				{
					p.x = -1;
					p.y = -1;

					PPEvent myEvent(eMouseMoved, &p, sizeof(PPPoint));
					RaiseEventSynchronized(&myEvent);
					break;
				};
				
				case kEventMouseDragged: 
				{
					if (mouseButton > 2 || !mouseButton)
						break;

					p.x = localMouseX;
					p.y = localMouseY;

					if (mouseButton == 1 && lMouseDown)
					{
						PPEvent myEvent(eLMouseDrag, &p, sizeof(PPPoint));				
						RaiseEventSynchronized(&myEvent);
					}
					else if (rMouseDown)
					{
						PPEvent myEvent(eRMouseDrag, &p, sizeof(PPPoint));
						RaiseEventSynchronized(&myEvent);
					}
					break;
				}
			}
			
			break;
			
		}
		
		case kEventClassKeyboard: 
		{
			switch (eventKind) 
			{
				case kEventRawKeyDown: 
				case kEventRawKeyRepeat: 
				{
					ProcessKeyEvent(event, eKeyDown);
					break;
				}
				
				case kEventRawKeyUp: 
				{
					ProcessKeyEvent(event, eKeyUp);
					break;
				}

				case kEventRawKeyModifiersChanged: 
				{				
					UInt32 keyModifiers;
				
					pp_uint16 chr[3];
				
					GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(keyModifiers), NULL, &keyModifiers);

					if ((keyModifiers & MacKeyModifierShift) != (lastModifierKeyState & MacKeyModifierShift))
					{
						chr[0] = VK_SHIFT;
						chr[1] = 0;
						chr[2] = 0;
						if (keyModifiers & MacKeyModifierShift)
						{
							::setKeyModifier(KeyModifierSHIFT);
							PPEvent event(eKeyDown, &chr, sizeof(chr));
							RaiseEventSynchronized(&event);						
						}
						else
						{
							PPEvent event(eKeyUp, &chr, sizeof(chr));
							RaiseEventSynchronized(&event);						
							::clearKeyModifier(KeyModifierSHIFT);
						}
					}

					/*if ((keyModifiers & eCapsLockKey) != (lastModifierKeyState & eCapsLockKey))
					{
						chr[0] = VK_CAPITAL;
						chr[1] = SC_CAPSLOCK;
						
						//::setKeyModifier(KeyModifierCTRL);
						//keyTimerTicker = -15;
						PPEvent event(eKeyDown, &chr, sizeof(chr));
						RaiseEventSynchronized(&event);						
						PPEvent event2(eKeyUp, &chr, sizeof(chr));
						RaiseEventSynchronized(&event2);						
					}*/

					if ((keyModifiers & MacKeyModifierAlt) != (lastModifierKeyState & MacKeyModifierAlt))
					{
						chr[0] = VK_ALT;
						chr[1] = 0;
						chr[2] = 0;
						if (keyModifiers & MacKeyModifierAlt)
						{
							::setKeyModifier(KeyModifierALT);
							PPEvent event(eKeyDown, &chr, sizeof(chr));
							RaiseEventSynchronized(&event);						
						}
						else
						{
							PPEvent event(eKeyUp, &chr, sizeof(chr));
							RaiseEventSynchronized(&event);						
							::clearKeyModifier(KeyModifierALT);
						}
					}

					if ((keyModifiers & MacKeyModifierCommand) != (lastModifierKeyState & MacKeyModifierCommand))
					{
						chr[0] = VK_CONTROL;
						chr[1] = 0;
						chr[2] = 0;
						if (keyModifiers & MacKeyModifierCommand)
						{
							::setKeyModifier(KeyModifierCTRL);
							PPEvent event(eKeyDown, &chr, sizeof(chr));
							RaiseEventSynchronized(&event);						
						}
						else
						{
							PPEvent event(eKeyUp, &chr, sizeof(chr));
							RaiseEventSynchronized(&event);						
							::clearKeyModifier(KeyModifierCTRL);
						}
					}

					lastModifierKeyState = keyModifiers;

				}
			}
			break;
		}
    }
	
    return result;
    
}
