/** <title>NSEvent</title>

   <abstract>The event class</abstract>

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Scott Christley <scottc@net-community.com>
   Author: Ovidiu Predescu <ovidiu@net-community.com>
   Date: 1996
   Author: Felipe A. Rodriguez <far@ix.netcom.com>
   Date: Sept 1998
   Updated: Richard Frith-Macdonald <richard@brainstorm.co.uk>
   Date: June 1999

   This file is part of the GNUstep GUI Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; see the file COPYING.LIB.
   If not, see <http://www.gnu.org/licenses/> or write to the 
   Free Software Foundation, 51 Franklin Street, Fifth Floor, 
   Boston, MA 02110-1301, USA.
*/

#include "config.h"
#import <Foundation/NSDictionary.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSTimer.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSThread.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSException.h>
#import <Foundation/NSDebug.h>

#import "AppKit/NSEvent.h"
#import "AppKit/NSApplication.h"
#import "AppKit/NSWindow.h"
#import "AppKit/NSGraphicsContext.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/PSOperators.h"

#import "GNUstepGUI/GSDisplayServer.h"

/*
 *        gstep-base has a faster mechanism to get the current thread.
 */
#ifndef GNUSTEP_BASE_LIBRARY
#define GSCurrentThread()           [NSThread currentThread]
#define GSCurrentThreadDictionary() [[NSThread currentThread] threadDictionary]
#endif

@implementation NSEvent

/*
 * Class variables
 */
static NSString *timerKey = @"NSEventTimersKey";
static Class dateClass;
static Class eventClass;

/*
 * Class methods
 */
+ (void) initialize
{
  if (self == [NSEvent class])
    {
      [self setVersion: 3];
      dateClass = [NSDate class];
      eventClass = [NSEvent class];
    }
}

/*
 * Creating NSEvent objects
 */
+ (NSEvent*) enterExitEventWithType: (NSEventType)type
                           location: (NSPoint)location
                      modifierFlags: (unsigned int)flags
                          timestamp: (NSTimeInterval)time
                       windowNumber: (int)windowNum
                            context: (NSGraphicsContext*)context
                        eventNumber: (int)eventNum
                     trackingNumber: (int)trackingNum
                           userData: (void *)userData
{
  NSEvent *e;

  if (type == NSCursorUpdate)
    RETAIN((id)userData);
  else if ((type != NSMouseEntered) && (type != NSMouseExited))
    [NSException raise: NSInvalidArgumentException
                format: @"enterExitEvent with wrong type"];

  e = (NSEvent*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  if (self != eventClass)
    e = [e init];
  AUTORELEASE(e);

  e->event_type = type;
  e->location_point = location;
  e->modifier_flags = flags;
  e->event_time = time;
  e->window_num = windowNum;
  e->event_context = context;
  e->event_data.tracking.event_num = eventNum;
  e->event_data.tracking.tracking_num = trackingNum;
  e->event_data.tracking.user_data = userData;

  return e;
}

+ (NSEvent*) keyEventWithType: (NSEventType)type
                     location: (NSPoint)location
                modifierFlags: (unsigned int)flags
                    timestamp: (NSTimeInterval)time
                 windowNumber: (int)windowNum
                      context: (NSGraphicsContext *)context
                   characters: (NSString *)keys
  charactersIgnoringModifiers: (NSString *)ukeys
                    isARepeat: (BOOL)repeatKey
                      keyCode: (unsigned short)code
{
  NSEvent *e;

  if (!(NSEventMaskFromType(type) & GSKeyEventMask))
    [NSException raise: NSInvalidArgumentException
                format: @"keyEvent with wrong type"];

  e = (NSEvent*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  if (self != eventClass)
    e = [e init];
  AUTORELEASE(e);

  e->event_type = type;
  e->location_point = location;
  e->modifier_flags = flags;
  e->event_time = time;
  e->window_num = windowNum;
  e->event_context = context;
  RETAIN(keys);
  e->event_data.key.char_keys = keys;
  RETAIN(ukeys);
  e->event_data.key.unmodified_keys = ukeys;
  e->event_data.key.repeat = repeatKey;
  e->event_data.key.key_code = code;

  return e;
}

+ (NSEvent*) mouseEventWithType: (NSEventType)type
                       location: (NSPoint)location
                  modifierFlags: (unsigned int)flags
                      timestamp: (NSTimeInterval)time
                   windowNumber: (int)windowNum
                        context: (NSGraphicsContext*)context
                    eventNumber: (int)eventNum
                     clickCount: (int)clickNum
                       pressure: (float)pressureValue
{
  NSEvent *e;

  if (!(NSEventMaskFromType(type) & GSMouseEventMask))
    [NSException raise: NSInvalidArgumentException
                format: @"mouseEvent with wrong type"];

  e = (NSEvent*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  if (self != eventClass)
    e = [e init];
  AUTORELEASE(e);

  e->event_type = type;
  e->location_point = location;
  e->modifier_flags = flags;
  e->event_time = time;
  e->window_num = windowNum;
  e->event_context = context;
  e->event_data.mouse.event_num = eventNum;
  e->event_data.mouse.click = clickNum;
  e->event_data.mouse.pressure = pressureValue;

  return e;
}

+ (NSEvent*) mouseEventWithType: (NSEventType)type        
                       location: (NSPoint)location
                  modifierFlags: (unsigned int)flags
                      timestamp: (NSTimeInterval)time
                   windowNumber: (int)windowNum
                        context: (NSGraphicsContext*)context        
                    eventNumber: (int)eventNum
                     clickCount: (int)clickNum
                       pressure: (float)pressureValue
                   buttonNumber: (int)buttonNum
                         deltaX: (float)deltaX
                         deltaY: (float)deltaY
                         deltaZ: (float)deltaZ
{
  NSEvent *e;

  if (!(NSEventMaskFromType(type) & GSMouseEventMask))
    [NSException raise: NSInvalidArgumentException
                format: @"mouseEvent with wrong type"];

  e = (NSEvent*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  if (self != eventClass)
    e = [e init];
  AUTORELEASE(e);

  e->event_type = type;
  e->location_point = location;
  e->modifier_flags = flags;
  e->event_time = time;
  e->window_num = windowNum;
  e->event_context = context;
  e->event_data.mouse.event_num = eventNum;
  e->event_data.mouse.click = clickNum;
  e->event_data.mouse.button = buttonNum;
  e->event_data.mouse.pressure = pressureValue;
  e->event_data.mouse.deltaX = deltaX;
  e->event_data.mouse.deltaY = deltaY;
  e->event_data.mouse.deltaZ = deltaZ;

  return e;
}

/**
 * Returns the current mouse location.
 */
+ (NSPoint) mouseLocation
{
  return [GSCurrentServer() mouselocation];
}

+ (NSEvent*) otherEventWithType: (NSEventType)type
                       location: (NSPoint)location
                  modifierFlags: (unsigned int)flags
                      timestamp: (NSTimeInterval)time
                   windowNumber: (int)windowNum
                        context: (NSGraphicsContext*)context
                        subtype: (short)subType
                          data1: (int)data1
                          data2: (int)data2
{
  NSEvent *e;

  if (!(NSEventMaskFromType(type) & GSOtherEventMask))
    [NSException raise: NSInvalidArgumentException
                format: @"otherEvent with wrong type"];

  e = (NSEvent*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  if (self != eventClass)
    e = [e init];
  AUTORELEASE(e);

  e->event_type = type;
  e->location_point = location;
  e->modifier_flags = flags;
  e->event_time = time;
  e->window_num = windowNum;
  e->event_context = context;
  e->event_data.misc.sub_type = subType;
  e->event_data.misc.data1 = data1;
  e->event_data.misc.data2 = data2;

  return e;
}

/*
 * Requesting Periodic Events
 */
+ (void) startPeriodicEventsAfterDelay: (NSTimeInterval)delaySeconds
                            withPeriod: (NSTimeInterval)periodSeconds
{
  NSTimer             *timer;
  NSMutableDictionary *dict = GSCurrentThreadDictionary();

  NSDebugLLog (@"NSEvent", @"startPeriodicEventsAfterDelay: withPeriod: ");

  if ([dict objectForKey: timerKey])
    [NSException raise: NSInternalInconsistencyException
                format: @"Periodic events are already being generated for "
                        @"this thread %x", GSCurrentThread()];

  /*
   *  Register a timer that will fire in delaySeconds.
   *  This timer will fire the first event and register
   *  a repeat timer that will send the following events
   */
  timer = [NSTimer timerWithTimeInterval: delaySeconds
                   target: self
                   selector: @selector(_registerRealTimer:)
                   userInfo: [NSNumber numberWithDouble: periodSeconds]
                   repeats: NO];

  [[NSRunLoop currentRunLoop] addTimer: timer
                               forMode: NSEventTrackingRunLoopMode];
  [dict setObject: timer forKey: timerKey];
}

+ (void) _timerFired: (NSTimer*)timer
{
  NSTimeInterval timeInterval;
  NSEvent        *periodicEvent;

  timeInterval = [dateClass timeIntervalSinceReferenceDate];
  periodicEvent = [self otherEventWithType: NSPeriodic
                                  location: NSZeroPoint
                             modifierFlags: 0
                                 timestamp: timeInterval
                              windowNumber: 0
                                   context: [NSApp context]
                                   subtype: 0
                                     data1: 0
                                     data2: 0];

  NSDebugLLog (@"NSEvent", @"_timerFired: ");
  [NSApp postEvent: periodicEvent atStart: NO];
}

/*
 * This method provides a means of delaying the start of periodic events
 */
+ (void) _registerRealTimer: (NSTimer*)timer
{
  NSTimer             *realTimer;
  NSMutableDictionary *dict = GSCurrentThreadDictionary();

  NSDebugLLog (@"NSEvent", @"_registerRealTimer: ");
  {
    NSTimeInterval timeInterval;
    NSEvent        *periodicEvent;
    
    timeInterval = [dateClass timeIntervalSinceReferenceDate];
    periodicEvent = [self otherEventWithType: NSPeriodic
                          location: NSZeroPoint
                          modifierFlags: 0
                          timestamp: timeInterval
                              windowNumber: 0
                          context: [NSApp context]
                          subtype: 0
                          data1: 0
                          data2: 0];
    
    [NSApp postEvent: periodicEvent atStart: NO];
  }

  realTimer = [NSTimer timerWithTimeInterval: [[timer userInfo] doubleValue]
                                      target: self
                                    selector: @selector(_timerFired:)
                                    userInfo: nil
                                     repeats: YES];
  [dict setObject: realTimer forKey: timerKey];
  [[NSRunLoop currentRunLoop] addTimer: realTimer
                               forMode: NSEventTrackingRunLoopMode];
}

+ (void) stopPeriodicEvents
{
  NSTimer             *timer;
  NSMutableDictionary *dict = GSCurrentThreadDictionary();

  NSDebugLLog (@"NSEvent", @"stopPeriodicEvents");
  timer = [dict objectForKey: timerKey];
  [timer invalidate];
  [dict removeObjectForKey: timerKey];
}

/**
 * Returns the button number for the mouse button pressed in a mouse
 * event.  Intended primarily for the case where a mouse has three or
 * more buttons, and you want to know which button an 'other' mouse
 * event refers to.
 */
- (int) buttonNumber
{
  if (!(NSEventMaskFromType(event_type) & GSMouseEventMask))
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"buttonNumber requested for non-mouse event"];
    }
  return event_data.mouse.button;
}

/**
 * Returns the string of characters for a keyboard event.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event.
 */
- (NSString *) characters
{
  if ((event_type != NSKeyUp) && (event_type != NSKeyDown))
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"characters requested for non-keyboard event"];
    }
  return event_data.key.char_keys;
}

/**
 * Returns the string of characters for a keyboard event, as if no modifier
 * keys had been pressed when the keyboard event occirred.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event.
 */
- (NSString *) charactersIgnoringModifiers
{
  if ((event_type != NSKeyUp) && (event_type != NSKeyDown))
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"charactersIgnoringModifiers requested for "
                          @"non-keyboard event"];
    }
  return event_data.key.unmodified_keys;
}

/**
 * Return the number of clicks associated with the mouse down or up
 * event.  This method is not applicable for any event type other
 * than a mouse down or mouse up.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event.
 */
- (int) clickCount
{
  /* Make sure it is one of the right event types */
  if (!(NSEventMaskFromType(event_type) & GSMouseEventMask))
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"clickCount requested for non-mouse event"];
    }
  return event_data.mouse.click;
}

/**
 * Returns the graphics context for which this event was generated.
 */
- (NSGraphicsContext*) context
{
  return event_context;
}

- (id) copyWithZone: (NSZone*)zone
{
  NSEvent *e = (NSEvent*)NSCopyObject (self, 0, zone);

  if ((NSEventMaskFromType(event_type) & GSKeyEventMask))
    {
      event_data.key.char_keys = [event_data.key.char_keys copyWithZone: zone];
      event_data.key.unmodified_keys
        = [event_data.key.unmodified_keys copyWithZone: zone];
    }
  else if (event_type == NSCursorUpdate)
    {
      event_data.tracking.user_data
        = (void *)[(id)event_data.tracking.user_data copyWithZone: zone];
    }
  return e;
}

/**
 * Returns the 'data1' item associated with the event.
 * <br />Raises NSInternalInconsistencyException if the event is not
 * of type NSAppKitDefined, NSSystemDefined, NSApplicationDefined,
 * or NSPeriodic
 */
- (int) data1
{
  if (event_type < NSAppKitDefined || event_type > NSPeriodic)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"data1 requested for invalid event type"];
    }
  return event_data.misc.data1;
}

/**
 * Returns the 'data2' item associated with the event.
 * <br />Raises NSInternalInconsistencyException if the event is not
 * of type NSAppKitDefined, NSSystemDefined, NSApplicationDefined,
 * or NSPeriodic
 */
- (int) data2
{
  if (event_type < NSAppKitDefined || event_type > NSPeriodic)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"data2 requested for invalid event type"];
    }
  return event_data.misc.data2;
}

- (void) dealloc
{
  if ((NSEventMaskFromType(event_type) & GSKeyEventMask))
    {
      RELEASE(event_data.key.char_keys);
      RELEASE(event_data.key.unmodified_keys);
    }
  else if (event_type == NSCursorUpdate)
    {
      RELEASE((id)event_data.tracking.user_data);
    }
  [super dealloc];
}

/**
   <p>
   Returns the movement of the mouse on the X axis.
   </p>
   <p>
   This method is only valid for NSMouseMoved, NS*MouseDragged and 
   NSScrollWheel events, otherwise it will return 0.
   </p>
 */
- (float) deltaX
{
  if (!(NSEventMaskFromType(event_type) & GSMouseMovedEventMask))
    {
      return 0.0;
    }
  return event_data.mouse.deltaX;
}

/**
   <p>
   Returns the movement of the mouse on the Y axis.
   </p>
   <p>
   This method is only valid for NSMouseMoved, NS*MouseDragged and 
   NSScrollWheel events, otherwise it will return 0.
   </p>
 */
- (float) deltaY
{
  if (!(NSEventMaskFromType(event_type) & GSMouseMovedEventMask))
    {
      return 0.0;
    }
  return event_data.mouse.deltaY;
}

/**
   <p>
   Returns the movement of the mouse on the Z axis.
   </p>
   <p>
   This method is only valid for NSMouseMoved, NS*MouseDragged and 
   NSScrollWheel events, otherwise it will return 0.
   </p>
   <p>
   The value returned is 0.0 in most cases.
   </p>
 */
- (float) deltaZ
{
  if (!(NSEventMaskFromType(event_type) & GSMouseMovedEventMask))
    {
      return 0.0;
    }
  return event_data.mouse.deltaZ;
}

- (NSString*) description
{
static const char *eventTypes[] = {
    "nullEvent",
    "leftMouseDown",
    "leftMouseUp",
    "rightMouseDown",
    "rightMouseUp",
    "mouseMoved",
    "leftMouseDragged",
    "rightMouseDragged",
    "mouseEntered",
    "mouseExited",
    "keyDown",
    "keyUp",
    "flagsChanged",
    "appKitDefined",
    "systemDefined",
    "applicationDefined",
    "periodic",
    "cursorUpdate",
    "", "", "", "",
    "scrollWheel",
    "tabletPoint",
    "tabletProximity",
    "otherMouseDown",
    "otherMouseUp",
    "otherMouseDragged"
  };

  switch (event_type)
    {
      case NSLeftMouseDown:
      case NSLeftMouseUp:
      case NSOtherMouseDown:
      case NSOtherMouseUp:
      case NSRightMouseDown:
      case NSRightMouseUp:
        return [NSString stringWithFormat:
          @"NSEvent: eventType = %s, point = { %f, %f }, modifiers = %u,"
          @" time = %f, window = %d, dpsContext = %p,"
          @" event number = %d, click = %d, pressure = %f",
          eventTypes[event_type], location_point.x, location_point.y,
          modifier_flags, event_time, window_num, event_context,
          event_data.mouse.event_num, event_data.mouse.click,
          event_data.mouse.pressure];
        break;

      case NSMouseEntered:
      case NSMouseExited:
        return [NSString stringWithFormat:
          @"NSEvent: eventType = %s, point = { %f, %f }, modifiers = %u,"
          @" time = %f, window = %d, dpsContext = %p, "
          @" event number = %d, tracking number = %d, user data = %p",
          eventTypes[event_type], location_point.x, location_point.y,
          modifier_flags, event_time, window_num, event_context,
          event_data.tracking.event_num,
          event_data.tracking.tracking_num,
          event_data.tracking.user_data];
        break;

      case NSKeyDown:
      case NSKeyUp:
      case NSFlagsChanged:
        return [NSString stringWithFormat:
          @"NSEvent: eventType = %s, point = { %f, %f }, modifiers = %u,"
          @" time = %f, window = %d, dpsContext = %p, "
          @" repeat = %s, keys = %@, ukeys = %@, keyCode = 0x%x",
          eventTypes[event_type], location_point.x, location_point.y,
          modifier_flags, event_time, window_num, event_context,
          (event_data.key.repeat ? "YES" : "NO"),
          event_data.key.char_keys, event_data.key.unmodified_keys,
          event_data.key.key_code];
        break;

      case NSPeriodic:
      case NSCursorUpdate:
      case NSAppKitDefined:
      case NSSystemDefined:
      case NSApplicationDefined:
        return [NSString stringWithFormat:
          @"NSEvent: eventType = %s, point = { %f, %f }, modifiers = %u,"
          @" time = %f, window = %d, dpsContext = %p, "
          @" subtype = %d, data1 = %p, data2 = %p",
          eventTypes[event_type], location_point.x, location_point.y,
          modifier_flags, event_time, window_num, event_context,
          event_data.misc.sub_type, event_data.misc.data1,
          event_data.misc.data2];
        break;

      case NSScrollWheel:
      case NSMouseMoved:
      case NSLeftMouseDragged:
      case NSOtherMouseDragged:
      case NSRightMouseDragged:
        return [NSString stringWithFormat:
          @"NSEvent: eventType = %s, point = { %f, %f }, modifiers = %u,"
          @" time = %f, window = %d, dpsContext = %p,"
          @" event number = %d, click = %d, pressure = %f"
          @" button = %d, deltaX = %f, deltaY = %f, deltaZ = %f",
          eventTypes[event_type], location_point.x, location_point.y,
          modifier_flags, event_time, window_num, event_context,
          event_data.mouse.event_num, event_data.mouse.click,
          event_data.mouse.pressure, event_data.mouse.button,
          event_data.mouse.deltaX,
          event_data.mouse.deltaY,
          event_data.mouse.deltaZ];
        break;

      // FIXME: Tablet events
      case NSTabletPoint:
      case NSTabletProximity:
        break;

      default:
        return [NSString stringWithFormat:
          @"NSEvent: eventType = UNKNOWN!, point = { %f, %f }, modifiers = %u,"
          @" time = %f, window = %d",
          location_point.x, location_point.y,
          modifier_flags, event_time, window_num];
        break;
    }

  return [super description];
}

- (void) encodeWithCoder: (NSCoder*)aCoder
{
  [aCoder encodeValueOfObjCType: @encode(int) at: &event_type];
  [aCoder encodePoint: location_point];
  [aCoder encodeValueOfObjCType: @encode(unsigned) at: &modifier_flags];
  [aCoder encodeValueOfObjCType: @encode(NSTimeInterval) at: &event_time];
  [aCoder encodeValueOfObjCType: @encode(unsigned) at: &window_num];

  switch (event_type)
    {
      case NSLeftMouseDown:
      case NSLeftMouseUp:
      case NSOtherMouseDown:
      case NSOtherMouseUp:
      case NSRightMouseDown:
      case NSRightMouseUp:
      case NSScrollWheel:
      case NSMouseMoved:
      case NSLeftMouseDragged:
      case NSOtherMouseDragged:
      case NSRightMouseDragged:
        [aCoder encodeValuesOfObjCTypes: "iififff", &event_data.mouse.event_num,
          &event_data.mouse.click, &event_data.mouse.pressure,
          &event_data.mouse.button, &event_data.mouse.deltaX,
          &event_data.mouse.deltaY, &event_data.mouse.deltaZ];
        break;

      case NSMouseEntered:
      case NSMouseExited:
      case NSCursorUpdate:
        // Can't do anything with the user_data!?
        [aCoder encodeValuesOfObjCTypes: "ii", &event_data.tracking.event_num,
          &event_data.tracking.tracking_num];
        break;

      case NSKeyDown:
      case NSKeyUp:
      case NSFlagsChanged:
        [aCoder encodeValueOfObjCType: @encode(BOOL)
                                   at: &event_data.key.repeat];
        [aCoder encodeObject: event_data.key.char_keys];
        [aCoder encodeObject: event_data.key.unmodified_keys];
        [aCoder encodeValueOfObjCType: "S" at: &event_data.key.key_code];
        break;

      case NSPeriodic:
      case NSAppKitDefined:
      case NSSystemDefined:
      case NSApplicationDefined:
        [aCoder encodeValuesOfObjCTypes: "sii", &event_data.misc.sub_type,
          &event_data.misc.data1, &event_data.misc.data2];
        break;

      case NSTabletPoint:
      case NSTabletProximity:
        // FIXME: Tablet events
        break;
    }
}

/**
 * Returns the event number associated with any mouse event or tracking
 * event.  Event numbers are allocated sequentially when the system
 * creates these events.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event.
 */
- (int) eventNumber
{
  /* Make sure it is one of the right event types */
  if (!(NSEventMaskFromType(event_type) & GSMouseEventMask) && 
      !(NSEventMaskFromType(event_type) & GSEnterExitEventMask))
    [NSException raise: NSInternalInconsistencyException
                format: @"eventNumber requested for non-tracking event"];

  if ((event_type == NSMouseEntered) || (event_type == NSMouseExited))
    return event_data.tracking.event_num;
  else
    return event_data.mouse.event_num;
}

- (id) initWithCoder: (NSCoder*)aDecoder
{
  int version = [aDecoder versionForClassName: @"NSEvent"];

  [aDecoder decodeValueOfObjCType: @encode(int) at: &event_type];
  location_point = [aDecoder decodePoint];
  [aDecoder decodeValueOfObjCType: @encode(unsigned) at: &modifier_flags];
  [aDecoder decodeValueOfObjCType: @encode(NSTimeInterval) at: &event_time];
  [aDecoder decodeValueOfObjCType: @encode(unsigned) at: &window_num];

  if (version == 1)
    {
      // For the unlikely case that old events have been stored, convert them.
      switch (event_type)
        {
          case 0: event_type = NSLeftMouseDown; break;
          case 1: event_type = NSLeftMouseUp; break;
          case 2: event_type = NSOtherMouseDown; break;
          case 3: event_type = NSOtherMouseUp; break;
          case 4: event_type = NSRightMouseDown; break;
          case 5: event_type = NSRightMouseUp; break;
          case 6: event_type = NSMouseMoved; break;
          case 7: event_type = NSLeftMouseDragged; break;
          case 8: event_type = NSOtherMouseDragged; break;
          case 9: event_type = NSRightMouseDragged; break;
          case 10: event_type = NSMouseEntered; break;
          case 11: event_type = NSMouseExited; break;
          case 12: event_type = NSKeyDown; break;
          case 13: event_type = NSKeyUp; break;
          case 14: event_type = NSFlagsChanged; break;
          case 15: event_type = NSAppKitDefined; break;
          case 16: event_type = NSSystemDefined; break;
          case 17: event_type = NSApplicationDefined; break;
          case 18: event_type = NSPeriodic; break;
          case 19: event_type = NSCursorUpdate; break;
          case 20: event_type = NSScrollWheel; break;
          default: break;
        }  
    }

  // Previously flag change events where encoded wrongly
  if ((version == 2) && (event_type == NSFlagsChanged))
    {
        [aDecoder decodeValuesOfObjCTypes: "sii", &event_data.misc.sub_type,
          &event_data.misc.data1, &event_data.misc.data2];
        return self;
    }

  // Decode the event date based upon the event type
  switch (event_type)
    {
      case NSLeftMouseDown:
      case NSLeftMouseUp:
      case NSOtherMouseDown:
      case NSOtherMouseUp:
      case NSRightMouseDown:
      case NSRightMouseUp:
      case NSScrollWheel:
      case NSMouseMoved:
      case NSLeftMouseDragged:
      case NSOtherMouseDragged:
      case NSRightMouseDragged:
        [aDecoder decodeValuesOfObjCTypes: "iififff",
          &event_data.mouse.event_num, &event_data.mouse.click,
          &event_data.mouse.pressure, &event_data.mouse.button,
          &event_data.mouse.deltaX, &event_data.mouse.deltaY,
          &event_data.mouse.deltaZ];
        break;

      case NSMouseEntered:
      case NSMouseExited:
      case NSCursorUpdate:
        // Can't do anything with the user_data!?
        [aDecoder decodeValuesOfObjCTypes: "ii", &event_data.tracking.event_num,
          &event_data.tracking.tracking_num];
        break;

      case NSKeyDown:
      case NSKeyUp:
      case NSFlagsChanged:
        [aDecoder decodeValueOfObjCType: @encode(BOOL)
                                     at: &event_data.key.repeat];
        event_data.key.char_keys = [aDecoder decodeObject];
        event_data.key.unmodified_keys = [aDecoder decodeObject];
        [aDecoder decodeValueOfObjCType: "S" at: &event_data.key.key_code];
        break;

      case NSPeriodic:
      case NSAppKitDefined:
      case NSSystemDefined:
      case NSApplicationDefined:
        [aDecoder decodeValuesOfObjCTypes: "sii", &event_data.misc.sub_type,
          &event_data.misc.data1, &event_data.misc.data2];
        break;

      case NSTabletPoint:
      case NSTabletProximity:
        // FIXME: Tablet events
        break;
    }

  return self;
}

/**
 * Returns a flag to say if this is a keyboard repeat event.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event than an NSKeyUp or NSKeyDown.
 */
- (BOOL) isARepeat
{
  if ((event_type != NSKeyUp) && (event_type != NSKeyDown))
    [NSException raise: NSInternalInconsistencyException
                format: @"isARepeat requested for non-keyboard event"];

  return event_data.key.repeat;
}

/**
 * Returns the numeric key code of a keyboard event.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event than an NSKeyUp or NSKeyDown.
 */
- (unsigned short) keyCode
{
  if (!(NSEventMaskFromType(event_type) & GSKeyEventMask))
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"keyCode requested for non-keyboard event"];
    }
  return event_data.key.key_code;
}

/**
 * Returns the window location for which this event was generated (in the
 * base coordinate system of the window).
 */
- (NSPoint) locationInWindow
{
  return location_point;
}

/**
 * Returns the modifier flag bits associated with the event.
 */
- (unsigned int) modifierFlags
{
  return modifier_flags;
}

/**
 * Returns the pressure associated with a mouse event.  This is a value
 * in the range 0.0 to 1.0 and for mormal mouse events should be set to
 * one of those extremes.  This is used by pressure sensitive input devices.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event than a mouse event.
 */
- (float) pressure
{
  /* Make sure it is one of the right event types */
  if (!(NSEventMaskFromType(event_type) & GSMouseEventMask))
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"pressure requested for non-mouse event"];
    }
  return event_data.mouse.pressure;
}

/**
 * Returns the 'subtype' item associated with the event.
 * <br />Raises NSInternalInconsistencyException if the event is not
 * of type NSAppKitDefined, NSSystemDefined, NSApplicationDefined,
 * NSPeriodic or a mouve event.
 */
- (short) subtype
{
  if (!(NSEventMaskFromType(event_type) & GSOtherEventMask) && 
      !(NSEventMaskFromType(event_type) & GSMouseEventMask))
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"subtype requested for invalid event type"];
    }

  if (NSEventMaskFromType(event_type) & GSOtherEventMask)
    return event_data.misc.sub_type;
  else
    {
      if (event_type == NSTabletPoint)
        return NSTabletPointEventSubtype;
      else if (event_type == NSTabletProximity)
        return NSTabletProximityEventSubtype;
      else
        return NSMouseEventSubtype;
    }
}

/**
 * Returns the time interval since system startup at which this
 * event was generated.
 */
- (NSTimeInterval) timestamp
{
  return event_time;
}

/**
 * Returns a number identifying the tracking rectangle entered or exited.
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event than a mouse entered or exited event.
 */
- (int) trackingNumber
{
  if (event_type != NSMouseEntered && event_type != NSMouseExited
    &&  event_type != NSCursorUpdate)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"trackingNumber requested for non-tracking event"];
    }
  return event_data.tracking.tracking_num;
}

/**
 * returns the type of this event.
 */
- (NSEventType) type
{
  return event_type;
}

/**
 * Returns usder data associated with a tracking event... the data assigned to
 * the tracking rectangle concerned when it was created..
 * <br />Raises an NSInternalInconsistencyException if applied to any
 * other type of event than a mouse entered or exited event.
 */
- (void *) userData
{
  if (event_type != NSMouseEntered && event_type != NSMouseExited
    &&  event_type != NSCursorUpdate)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"userData requested for non-tracking event"];
    }
  return event_data.tracking.user_data;
}

/**
 * Returns the window for which this event was generated.<br />
 * Periodic events have no associated window, and you should not call
 * this method for those events.
 */
- (NSWindow *) window
{
  return GSWindowWithNumber(window_num);
}

/**
 * Returns the window number of the window for which this event was generated.
 * <br />Periodic events have no associated window, and you should not call
 * this method for those events.
 */
- (int) windowNumber
{
  return window_num;
}

/*
 * Methods for tablet events
 */
- (int) absoluteX
{
  // FIXME
  return 0;
}

- (int) absoluteY
{
  // FIXME
  return 0;
}

- (int) absoluteZ
{
  // FIXME
  return 0;
}

- (unsigned int) buttonMask
{
  // FIXME
  return 0;
}

- (unsigned int) capabilityMask
{
  // FIXME
  return 0;
}

- (unsigned int) deviceID
{
  // FIXME
  return 0;
}

- (BOOL) isEnteringProximity
{
  // FIXME
  return NO;
}

- (unsigned int) pointingDeviceID
{
  // FIXME
  return 0;
}

- (unsigned int) pointingDeviceSerialNumber
{
  // FIXME
  return 0;
}

- (NSPointingDeviceType) pointingDeviceType
{
  // FIXME
  return NSUnknownPointingDevice;
}

- (float) rotation
{
  // FIXME
  return 0.0;
}

- (unsigned int) systemTabletID
{
  // FIXME
  return 0;
}

- (unsigned int) tabletID
{
  // FIXME
  return 0;
}

- (float) tangentialPressure
{
  // FIXME
  return 0.0;
}

- (NSPoint) tilt
{
  // FIXME
  return NSMakePoint(0.0, 0.0);
}

- (unsigned long long) uniqueID
{
  // FIXME
  return 0;
}

- (id) vendorDefined
{
  // FIXME
  return nil;
}

- (unsigned int) vendorID
{
  // FIXME
  return 0;
}

- (unsigned int) vendorPointingDeviceType
{
  // FIXME
  return 0;
}

@end
