/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Peter Hartshorn <peter@igelaus.com.au>
 *   Ken Faulkner <faulkner@igelaus.com.au>
 *   Tony Tsui <tony@igelaus.com.au>
 *   Caspian Maclean <caspian@igelaus.com.au>
 *   Roland Mainz <roland.mainz@informatik.med.uni-giessen.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or 
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <X11/Xlocale.h>

#include "nsWindow.h"
#include "nsWidget.h"
#include "nsAppShell.h"
#include "nsKeyCode.h"
#include "nsWidgetsCID.h"

#include "nsIWidget.h"
#include "nsIEventQueueService.h"
#include "nsIServiceManager.h"
#include "nsICmdLineService.h"
#include "nsIDragService.h"
#include "nsIDragSessionXlib.h"
#include "nsITimer.h"

#include "xlibrgb.h"

#define CHAR_BUF_SIZE 80

static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
static NS_DEFINE_IID(kIEventQueueServiceIID, NS_IEVENTQUEUESERVICE_IID);
static NS_DEFINE_CID(kCmdLineServiceCID, NS_COMMANDLINE_SERVICE_CID);
static NS_DEFINE_IID(kCDragServiceCID,  NS_DRAGSERVICE_CID);

/* nsAppShell static members */
Display *nsAppShell::mDisplay = nsnull;
XlibRgbHandle *nsAppShell::mXlib_rgb_handle = nsnull;
XtAppContext nsAppShell::mAppContext;
PRTime nsAppShell::mClickTime = 0;
PRInt16 nsAppShell::mClicks = 1;
PRUint16 nsAppShell::mClickedButton = 0;
PRPackedBool nsAppShell::mClicked = PR_FALSE;
PRPackedBool nsAppShell::mDragging  = PR_FALSE;
PRPackedBool nsAppShell::mAltDown   = PR_FALSE;
PRPackedBool nsAppShell::mShiftDown = PR_FALSE;
PRPackedBool nsAppShell::mCtrlDown  = PR_FALSE;
PRPackedBool nsAppShell::mMetaDown  = PR_FALSE;
PRPackedBool nsAppShell::DieAppShellDie = PR_FALSE;

static PLHashTable *sQueueHashTable = nsnull;
static PLHashTable *sCountHashTable = nsnull;
static nsVoidArray *sEventQueueList = nsnull;


// For debugging.
static const char *event_names[] = 
{
  "",
  "",
  "KeyPress",
  "KeyRelease",
  "ButtonPress",
  "ButtonRelease",
  "MotionNotify",
  "EnterNotify",
  "LeaveNotify",
  "FocusIn",
  "FocusOut",
  "KeymapNotify",
  "Expose",
  "GraphicsExpose",
  "NoExpose",
  "VisibilityNotify",
  "CreateNotify",
  "DestroyNotify",
  "UnmapNotify",
  "MapNotify",
  "MapRequest",
  "ReparentNotify",
  "ConfigureNotify",
  "ConfigureRequest",
  "GravityNotify",
  "ResizeRequest",
  "CirculateNotify",
  "CirculateRequest",
  "PropertyNotify",
  "SelectionClear",
  "SelectionRequest",
  "SelectionNotify",
  "ColormapNotify",
  "ClientMessage",
  "MappingNotify"
};

#define COMPARE_FLAG1( a,b) ((b)[0]=='-' && !strcmp((a), &(b)[1]))
#define COMPARE_FLAG2( a,b) ((b)[0]=='-' && (b)[1]=='-' && !strcmp((a), &(b)[2]))
#define COMPARE_FLAG12(a,b) ((b)[0]=='-' && !strcmp((a), (b)[1]=='-'?&(b)[2]:&(b)[1]))

#define ALL_EVENTS ( KeyPressMask | KeyReleaseMask | ButtonPressMask | \
                     ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | \
                     PointerMotionMask | PointerMotionHintMask | Button1MotionMask | \
                     Button2MotionMask | Button3MotionMask | \
                     Button4MotionMask | Button5MotionMask | ButtonMotionMask | \
                     KeymapStateMask | ExposureMask | VisibilityChangeMask | \
                     StructureNotifyMask | ResizeRedirectMask | \
                     SubstructureNotifyMask | SubstructureRedirectMask | \
                     FocusChangeMask | PropertyChangeMask | \
                     ColormapChangeMask | OwnerGrabButtonMask )

nsAppShell::nsAppShell()  
{ 
  NS_INIT_ISUPPORTS();
  mDispatchListener = 0;

  if (!sEventQueueList)
    sEventQueueList = new nsVoidArray();

  mEventQueue = nsnull;
}

NS_IMPL_ISUPPORTS1(nsAppShell, nsIAppShell)

PR_BEGIN_EXTERN_C
static 
int xerror_handler( Display *display, XErrorEvent *ev )
{
  /* this should _never_ be happen... but if this happens - debug mode or not - scream !!! */
  char errmsg[80];
  XGetErrorText(display, ev->error_code, errmsg, sizeof(errmsg));
  fprintf(stderr, "nsAppShellXlib: Warning (X Error) -  %s\n", errmsg);
  abort(); // die !!
  
  return 0;
}
PR_END_EXTERN_C

NS_METHOD nsAppShell::Create(int* bac, char ** bav)
{
  /* Create the Xt Application context... */
  if (mAppContext == nsnull) {
    int      argc = bac ? *bac : 0;
    char   **argv = bav;
    nsresult rv;

    nsCOMPtr<nsICmdLineService> cmdLineArgs = do_GetService(kCmdLineServiceCID);
    if (cmdLineArgs) {
      rv = cmdLineArgs->GetArgc(&argc);
      if(NS_FAILED(rv))
        argc = bac ? *bac : 0;

      rv = cmdLineArgs->GetArgv(&argv);
      if(NS_FAILED(rv))
        argv = bav;
    }

    char        *displayName    = nsnull;
    Bool         synchronize    = False;
    int          i;
    XlibRgbArgs  xargs;
    memset(&xargs, 0, sizeof(xargs));
    /* Use a "well-known" name that other modules can "look-up" this handle
     * via |xxlib_find_handle| ... */
    xargs.handle_name = XXLIBRGB_DEFAULT_HANDLE;

    for (i = 0; ++i < argc-1; ) {
      /* allow both --display and -display */
      if (COMPARE_FLAG12 ("display", argv[i])) {
        displayName=argv[i+1];
        break;
      }
    }
    for (i = 0; ++i < argc-1; ) {
      if (COMPARE_FLAG1 ("visual", argv[i])) {
        xargs.xtemplate_mask |= VisualIDMask;
        xargs.xtemplate.visualid = strtol(argv[i+1], NULL, 0);
        break;
      }
    }   
    for (i = 0; ++i < argc; ) {
      if (COMPARE_FLAG1 ("sync", argv[i])) {
        synchronize = True;
        break;
      }
    }
    for (i = 0; ++i < argc; ) {
      /* allow both --no-xshm and -no-xshm */
      if (COMPARE_FLAG12 ("no-xshm", argv[i])) {
        xargs.disallow_mit_shmem = True;
        break;
      }
    }    
    for (i = 0; ++i < argc; ) {
      if (COMPARE_FLAG1 ("install_colormap", argv[i])) {
        xargs.install_colormap = True;
        break;
      }
    }
    
    /* setup locale */
    if (!setlocale (LC_ALL,""))
      NS_WARNING("locale not supported by C library");
  
    if (!XSupportsLocale ()) {
      NS_WARNING("locale not supported by Xlib, locale set to C");
      setlocale (LC_ALL, "C");
    }
  
    if (!XSetLocaleModifiers (""))
      NS_WARNING("can not set locale modifiers");

    XtToolkitInitialize();
    mAppContext = XtCreateApplicationContext();

    if (!(mDisplay = XtOpenDisplay (mAppContext, displayName, 
                                    "Mozilla5", "Mozilla5", nsnull, 0, 
                                    &argc, argv))) 
    {
      fprintf (stderr, "%s:  unable to open display \"%s\"\n", argv[0], XDisplayName(displayName));
      exit (EXIT_FAILURE);
    }
    
    // Requires XSynchronize(mDisplay, True); To stop X buffering. Use this
    // to make debugging easier. KenF
    if (synchronize)
    {
      /* Set error handler which calls abort() - only usefull with an 
       * unbuffered X connection */
      (void)XSetErrorHandler(xerror_handler);
      
      NS_WARNING("running via unbuffered X connection.");
      XSynchronize(mDisplay, True);
    }
       
    mXlib_rgb_handle = xxlib_rgb_create_handle(mDisplay, XDefaultScreenOfDisplay(mDisplay),
                                               &xargs);
    if (!mXlib_rgb_handle)
    {
      fprintf (stderr, "%s:  unable to create Xlib context\n", argv[0]);
      exit (EXIT_FAILURE);
    }
  }

  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("nsAppShell::Create(dpy=%p)\n",
         mDisplay));

  return NS_OK;
}

NS_METHOD nsAppShell::SetDispatchListener(nsDispatchListener* aDispatchListener) 
{
  mDispatchListener = aDispatchListener;
  return NS_OK;
}

NS_IMETHODIMP nsAppShell::Spinup()
{
  nsresult rv = NS_OK;

#ifdef DEBUG_APPSHELL
  printf("nsAppShell::Spinup()\n");
#endif

  /* Get the event queue service */
  nsCOMPtr<nsIEventQueueService> eventQService = do_GetService(kEventQueueServiceCID, &rv);

  if (NS_FAILED(rv)) {
    NS_WARNING("Could not obtain event queue service");
    return rv;
  }

  /* Get the event queue for the thread.*/
  rv = eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(mEventQueue));
  
  /* If we got an event queue, use it. */
  if (!mEventQueue) {
    /* otherwise create a new event queue for the thread */
    rv = eventQService->CreateThreadEventQueue();
    if (NS_FAILED(rv)) {
      NS_WARNING("Could not create the thread event queue");
      return rv;
    }

    /* Ask again nicely for the event queue now that we have created one. */
    rv = eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(mEventQueue));
    if (NS_FAILED(rv)) {
      NS_WARNING("Could not get the thread event queue");
      return rv;
    }
  }

  ListenToEventQueue(mEventQueue, PR_TRUE);

  return rv;
}

/* must be a |XtInputCallbackProc| !! */
PR_BEGIN_EXTERN_C
static
void HandleQueueXtProc(XtPointer ptr, int *source_fd, XtInputId* id)
{
  nsIEventQueue *queue = (nsIEventQueue *)ptr;
  queue->ProcessPendingEvents();
}
PR_END_EXTERN_C

nsresult nsAppShell::Run()
{
  if (mEventQueue == nsnull)
    Spinup();

  if (mEventQueue == nsnull) {
    NS_WARNING("Cannot initialize the Event Queue");
    return NS_ERROR_NOT_INITIALIZED;
  }
               
  XEvent xevent;
  
  /* process events. */
  while (!DieAppShellDie) 
  {   
    XtAppNextEvent(mAppContext, &xevent);
  
    if (XtDispatchEvent(&xevent) == False)
      DispatchXEvent(&xevent);
    
    if (XEventsQueued(mDisplay, QueuedAlready) == 0)
    {
      /* Flush the nsWindow's drawing queue */
      nsWindow::UpdateIdle(nsnull);
    }
  }
  
  Spindown();
  return NS_OK;
}

NS_METHOD nsAppShell::Spindown()
{
  if (mEventQueue) {
    ListenToEventQueue(mEventQueue, PR_FALSE);
    mEventQueue->ProcessPendingEvents();
    mEventQueue = nsnull;
  }

  return NS_OK;
}

#define NUMBER_HASH_KEY(_num) ((PLHashNumber) _num)

static PLHashNumber
IntHashKey(PRInt32 key)
{
  return NUMBER_HASH_KEY(key);
}
// wrapper so we can call a macro
PR_BEGIN_EXTERN_C
static unsigned long getNextRequest (void *aClosure) {
  return XNextRequest(nsAppShell::mDisplay);
}
PR_END_EXTERN_C

NS_IMETHODIMP nsAppShell::ListenToEventQueue(nsIEventQueue *aQueue,
                                             PRBool aListen)
{
  if (!mEventQueue) {
    NS_WARNING("nsAppShell::ListenToEventQueue(): No event queue available.");
    return NS_ERROR_NOT_INITIALIZED;
  }
  
#ifdef DEBUG_APPSHELL
  printf("ListenToEventQueue(%p, %d) this=%p\n", aQueue, aListen, this);
#endif
  if (!sQueueHashTable) {
    sQueueHashTable = PL_NewHashTable(3, (PLHashFunction)IntHashKey,
                                      PL_CompareValues, PL_CompareValues, 0, 0);
  }
  if (!sCountHashTable) {
    sCountHashTable = PL_NewHashTable(3, (PLHashFunction)IntHashKey,
                                      PL_CompareValues, PL_CompareValues, 0, 0);
  }    

  int   queue_fd = aQueue->GetEventQueueSelectFD();
  void *key      = aQueue;
  if (aListen) {
    /* Add listener -
     * but only if we arn't already in the table... */
    if (!PL_HashTableLookup(sQueueHashTable, key)) {
      long tag;
        
      /* set up our fds callbacks */
      tag = (long)XtAppAddInput(mAppContext,
                                queue_fd,
                                (XtPointer)(long)(XtInputReadMask),
                                HandleQueueXtProc,
                                (XtPointer)mEventQueue);

/* This hack would not be neccesary if we would have a hashtable function
 * which returns success/failure in a seperate var ...
 */
#define NEVER_BE_ZERO_MAGIC (54321) 
      tag += NEVER_BE_ZERO_MAGIC; /* be sure that |tag| is _never_ 0 */
      NS_ASSERTION(tag!=0, "tag is 0 while adding");
      
      if (tag) {
        PL_HashTableAdd(sQueueHashTable, key, (void *)tag);
      }
      
      PLEventQueue *plqueue;
      aQueue->GetPLEventQueue(&plqueue);
      PL_RegisterEventIDFunc(plqueue, getNextRequest, 0);
      sEventQueueList->AppendElement(plqueue);
    }
  } else {
    /* Remove listener... */   
    PLEventQueue *plqueue;
    aQueue->GetPLEventQueue(&plqueue);
    PL_UnregisterEventIDFunc(plqueue);
    sEventQueueList->RemoveElement(plqueue);

    int tag = long(PL_HashTableLookup(sQueueHashTable, key));
    if (tag) {
      tag -= NEVER_BE_ZERO_MAGIC;
      XtRemoveInput((XtInputId)tag);
      PL_HashTableRemove(sQueueHashTable, key);
    }  
  }

  return NS_OK;
}

/* Does nothing. Used by xp code with non-gtk expectations.
 * this method will be removed once xp eventloops are working.
 */
NS_METHOD
nsAppShell::GetNativeEvent(PRBool &aRealEvent, void *&aEvent)
{
  aRealEvent = PR_FALSE;
  aEvent     = nsnull;

  return NS_OK;
}

nsresult nsAppShell::DispatchNativeEvent(PRBool aRealEvent, void *aEvent)
{
  XEvent xevent;
  
  if (!mEventQueue)
    return NS_ERROR_NOT_INITIALIZED;

#if 1
  /* gisburn: Why do we have to call this explicitly ?
   * I have registered a callback via XtAddAppInput() above... 
   */  
  mEventQueue->ProcessPendingEvents();  
#endif

  XtAppNextEvent(mAppContext, &xevent);
    
  if (XtDispatchEvent(&xevent) == False)
    DispatchXEvent(&xevent);
   
  if (XEventsQueued(mDisplay, QueuedAlready) == 0)
  {
    /* Flush the nsWindow's drawing queue */
    nsWindow::UpdateIdle(nsnull);
  }
    
  return NS_OK;
}

NS_METHOD nsAppShell::Exit()
{
  DieAppShellDie = PR_TRUE;
  return NS_OK;
}

nsAppShell::~nsAppShell()
{
}

void* nsAppShell::GetNativeData(PRUint32 aDataType)
{
  return nsnull;
}

void
nsAppShell::DispatchXEvent(XEvent *event)
{
  nsWidget *widget;
  widget = nsWidget::GetWidgetForWindow(event->xany.window);

  // did someone pass us an x event for a window we don't own?
  // bad! bad!
  if (widget == nsnull)
    return;

  // switch on the type of event
  switch (event->type) 
  {
  case Expose:
    HandleExposeEvent(event, widget);
    break;

  case ConfigureNotify:
    // we need to make sure that this is the LAST of the
    // config events.
    PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("DispatchEvent: ConfigureNotify event for window 0x%lx %d %d %d %d\n",
                                         event->xconfigure.window,
                                         event->xconfigure.x, 
                                         event->xconfigure.y,
                                         event->xconfigure.width, 
                                         event->xconfigure.height));

    HandleConfigureNotifyEvent(event, widget);

    break;

  case ButtonPress:
  case ButtonRelease:
    HandleFocusInEvent(event, widget);
    HandleButtonEvent(event, widget);
    break;

  case MotionNotify:
    HandleMotionNotifyEvent(event, widget);
    break;

  case KeyPress:
    HandleKeyPressEvent(event, widget);
    break;
  case KeyRelease:
    HandleKeyReleaseEvent(event, widget);
    break;

  case FocusIn:
    HandleFocusInEvent(event, widget);
    break;

  case FocusOut:
    HandleFocusOutEvent(event, widget);
    break;

  case EnterNotify:
    HandleEnterEvent(event, widget);
    break;

  case LeaveNotify:
    HandleLeaveEvent(event, widget);
    break;

  case NoExpose:
    // these annoy me.
    break;
  case VisibilityNotify:
    HandleVisibilityNotifyEvent(event, widget);
    break;
  case ClientMessage:
    HandleClientMessageEvent(event, widget);
    break;
  case SelectionRequest:
    HandleSelectionRequestEvent(event, widget);
    break;
  default:
    PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Unhandled window event: Window 0x%lx Got a %s event\n",
                                         event->xany.window, event_names[event->type]));

    break;
  }
}

void
nsAppShell::HandleMotionNotifyEvent(XEvent *event, nsWidget *aWidget)
{
  nsMouseEvent mevent;
  XEvent aEvent;

  if (mDragging) {
    HandleDragMotionEvent(event, aWidget);
  }

  mevent.widget = aWidget;
  mevent.time = 0;
  mevent.point.x = event->xmotion.x;
  mevent.point.y = event->xmotion.y;

  mevent.isShift = mShiftDown;
  mevent.isControl = mCtrlDown;
  mevent.isAlt = mAltDown;
  mevent.isMeta = mMetaDown;
  
  Display * dpy = (Display *)aWidget->GetNativeData(NS_NATIVE_DISPLAY);
  Window win = (Window)aWidget->GetNativeData(NS_NATIVE_WINDOW);
  // We are only interested in the LAST (newest) location of the pointer
  while(XCheckWindowEvent(dpy,
                          win,
                          ButtonMotionMask,
                          &aEvent)) {
    mevent.point.x = aEvent.xmotion.x;
    mevent.point.y = aEvent.xmotion.y;
  }
  mevent.message = NS_MOUSE_MOVE;
  mevent.eventStructType = NS_MOUSE_EVENT;
  NS_ADDREF(aWidget);
  aWidget->DispatchMouseEvent(mevent);
  NS_RELEASE(aWidget);
}

void
nsAppShell::HandleButtonEvent(XEvent *event, nsWidget *aWidget)
{
  nsMouseEvent mevent;
  mevent.isShift = mShiftDown;
  mevent.isControl = mCtrlDown;
  mevent.isAlt = mAltDown;
  mevent.isMeta = mMetaDown;
  PRUint32 eventType = 0;
  PRBool currentlyDragging = mDragging;
  nsMouseScrollEvent scrollEvent;

  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Button event for window 0x%lx button %d type %s\n",
                                       event->xany.window,
                                       event->xbutton.button,
                                       (event->type == ButtonPress ? "ButtonPress" : "ButtonRelease")));
  switch(event->type) {
  case ButtonPress:
    switch(event->xbutton.button) {
    case 1:
      eventType = NS_MOUSE_LEFT_BUTTON_DOWN;
      mDragging = PR_TRUE;
      break;
    case 2:
      eventType = NS_MOUSE_MIDDLE_BUTTON_DOWN;
      break;
    case 3:
      /* look back into this in case anything actually needs a
       * NS_MOUSE_RIGHT_BUTTON_DOWN */
      eventType = NS_CONTEXTMENU;
      break;
    case 4:
    case 5:
      scrollEvent.delta = (event->xbutton.button == 4) ? -3 : 3;
      scrollEvent.scrollFlags = nsMouseScrollEvent::kIsVertical;
      scrollEvent.message = NS_MOUSE_SCROLL;
      scrollEvent.widget = aWidget;
      scrollEvent.eventStructType = NS_MOUSE_SCROLL_EVENT;

      scrollEvent.point.x = event->xbutton.x;
      scrollEvent.point.y = event->xbutton.y;

      scrollEvent.isShift = mShiftDown;
      scrollEvent.isControl = mCtrlDown;
      scrollEvent.isAlt = mAltDown;
      scrollEvent.isMeta = mMetaDown;
      scrollEvent.time = PR_Now();
      NS_IF_ADDREF(aWidget);
      aWidget->DispatchWindowEvent(scrollEvent);
      NS_IF_RELEASE(aWidget);
      return;
    }
    break;
  case ButtonRelease:
    switch(event->xbutton.button) {
    case 1:
      eventType = NS_MOUSE_LEFT_BUTTON_UP;
      mDragging = PR_FALSE;
      break;
    case 2:
      eventType = NS_MOUSE_MIDDLE_BUTTON_UP;
      break;
    case 3:
      eventType = NS_MOUSE_RIGHT_BUTTON_UP;
      break;
    case 4:
    case 5:
      return;
    }
    break;
  }

  mevent.widget = aWidget;
  mevent.point.x = event->xbutton.x;
  mevent.point.y = event->xbutton.y;
  mevent.time = PR_Now();
  
  // If we are waiting longer than 1 sec for the second click, this is not a
  // double click.
  if (PR_Now() - mClickTime > 1000000)
    mClicked = PR_FALSE;               

  if (event->type == ButtonPress) {
    if (!mClicked) {
      mClicked = PR_TRUE;
      mClickTime = PR_Now();
      mClicks = 1;
      mClickedButton = event->xbutton.button;
    } else {
      mClickTime = PR_Now() - mClickTime;
      if ((mClickTime < 500000) && (mClickedButton == event->xbutton.button))
        mClicks = 2;
      else
        mClicks = 1;
      mClicked = PR_FALSE;
    }
  }

  if (currentlyDragging && !mDragging)
    HandleDragDropEvent(event, aWidget);

  mevent.message = eventType;
  mevent.eventStructType = NS_MOUSE_EVENT;
  mevent.clickCount = mClicks;
  NS_IF_ADDREF(aWidget);
  aWidget->DispatchMouseEvent(mevent);
  NS_IF_RELEASE(aWidget);
}

void
nsAppShell::HandleExposeEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Expose event for window 0x%lx %d %d %d %d\n", event->xany.window,
                                       event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height));

  nsRect dirtyRect(event->xexpose.x, event->xexpose.y, 
                   event->xexpose.width, event->xexpose.height);

  /* compress expose events...
   */
  if (event->xexpose.count!=0) {
     XEvent txe;
     do {
        XWindowEvent(event->xany.display, event->xany.window, ExposureMask, (XEvent *)&txe);
        dirtyRect.UnionRect(dirtyRect, nsRect(txe.xexpose.x, txe.xexpose.y, 
                                              txe.xexpose.width, txe.xexpose.height));
     } while (txe.xexpose.count>0);
  }

  aWidget->Invalidate(dirtyRect, PR_FALSE);
}

void
nsAppShell::HandleConfigureNotifyEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("ConfigureNotify event for window 0x%lx %d %d %d %d\n",
                                       event->xconfigure.window,
                                       event->xconfigure.x, event->xconfigure.y,
                                       event->xconfigure.width, event->xconfigure.height));

  XEvent    config_event;
  while (XCheckTypedWindowEvent(event->xany.display, 
                                event->xany.window, 
                                ConfigureNotify,
                                &config_event) == True) {
    // make sure that we don't get other types of events.  
    // StructureNotifyMask includes other kinds of events, too.
    if (config_event.type == ConfigureNotify) 
      {
        *event = config_event;
        
        PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("DispatchEvent: Extra ConfigureNotify event for window 0x%lx %d %d %d %d\n",
                                             event->xconfigure.window,
                                             event->xconfigure.x, 
                                             event->xconfigure.y,
                                             event->xconfigure.width, 
                                             event->xconfigure.height));
      }
    else {
      PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("EVENT LOSSAGE\n"));
    }
  }

  nsSizeEvent sevent;
  sevent.message = NS_SIZE;
  sevent.widget = aWidget;
  sevent.eventStructType = NS_SIZE_EVENT;
  sevent.windowSize = new nsRect (event->xconfigure.x, event->xconfigure.y,
                                  event->xconfigure.width, event->xconfigure.height);
  sevent.point.x = event->xconfigure.x;
  sevent.point.y = event->xconfigure.y;
  sevent.mWinWidth = event->xconfigure.width;
  sevent.mWinHeight = event->xconfigure.height;
  // XXX fix this
  sevent.time = 0;
  NS_ADDREF(aWidget);
  aWidget->OnResize(sevent);
  NS_RELEASE(aWidget);
  delete sevent.windowSize;
}

PRUint32 nsConvertCharCodeToUnicode(XKeyEvent* xkey)
{
  // The only Unicode specific at the moment is casting to PRUint32.

  // For control characters convert from the event ascii code (e.g. 1 for
  // control-a) to the ascii code for the key, e.g., 'a' for
  // control-a.

  KeySym         keysym;
  XComposeStatus compose;
  unsigned char  string_buf[CHAR_BUF_SIZE];
  int            len = 0;

  len = XLookupString(xkey, (char *)string_buf, CHAR_BUF_SIZE-1, &keysym, &compose);
  if (0 == len)
    return 0;

  if (xkey->state & ControlMask) {
    if (xkey->state & ShiftMask) {
      return (PRUint32)(string_buf[0] + 'A' - 1);
    }
    else {
      return (PRUint32)(string_buf[0] + 'a' - 1);
    }
  }
  if (!isprint(string_buf[0])) {
    return 0;
  }
  else {
    return (PRUint32)(string_buf[0]);
  }
}

void
nsAppShell::HandleKeyPressEvent(XEvent *event, nsWidget *aWidget)
{
  char      string_buf[CHAR_BUF_SIZE];
  int       len = 0;
  Window    focusWindow = None;
  nsWidget *focusWidget = 0;

  // figure out where the real focus should go...
  focusWindow = nsWidget::GetFocusWindow();
  if (focusWindow != None) {
    focusWidget = nsWidget::GetWidgetForWindow(focusWindow);
  }

  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("KeyPress event for window 0x%lx ( 0x%lx focus window )\n",
                                       event->xkey.window,
                                       focusWindow));

  // don't even bother...
  if (focusWidget == 0) {
    return;
  }

  KeySym     keysym = nsKeyCode::ConvertKeyCodeToKeySym(event->xkey.display,
                                                        event->xkey.keycode);

  switch (keysym) {
    case XK_Alt_L:
    case XK_Alt_R:
      mAltDown = PR_TRUE;
      break;
    case XK_Control_L:
    case XK_Control_R:
      mCtrlDown = PR_TRUE;
      break;
    case XK_Shift_L:
    case XK_Shift_R:
      mShiftDown = PR_TRUE;
      break;
    case XK_Meta_L:
    case XK_Meta_R:
      mMetaDown = PR_TRUE;
      break;
    default:
      break;
  }

  // Dont dispatch events for modifier keys pressed ALONE
  if (nsKeyCode::KeyCodeIsModifier(event->xkey.keycode))
  {
    return;
  }

  nsKeyEvent keyEvent;

  XComposeStatus compose;

  len = XLookupString(&event->xkey, string_buf, CHAR_BUF_SIZE-1, &keysym, &compose);
  string_buf[len] = '\0';

  keyEvent.keyCode = nsKeyCode::ConvertKeySymToVirtualKey(keysym);
  keyEvent.charCode = 0;
  keyEvent.time = event->xkey.time;
  keyEvent.isShift = (event->xkey.state & ShiftMask) ? PR_TRUE : PR_FALSE;
  keyEvent.isControl = (event->xkey.state & ControlMask) ? 1 : 0;
  keyEvent.isAlt = (event->xkey.state & Mod1Mask) ? 1 : 0;
  // I think 'meta' is the same as 'alt' in X11. Is this OK for other systems?
  keyEvent.isMeta = (event->xkey.state & Mod1Mask) ? 1 : 0;
  keyEvent.point.x = 0;
  keyEvent.point.y = 0;
  keyEvent.message = NS_KEY_DOWN;
  keyEvent.widget = focusWidget;
  keyEvent.eventStructType = NS_KEY_EVENT;

  //  printf("keysym = %x, keycode = %x, vk = %x\n",
  //         keysym,
  //         event->xkey.keycode,
  //         keyEvent.keyCode);

  focusWidget->DispatchKeyEvent(keyEvent);

  keyEvent.keyCode = nsKeyCode::ConvertKeySymToVirtualKey(keysym);
  keyEvent.charCode = nsConvertCharCodeToUnicode(&event->xkey);
  keyEvent.time = event->xkey.time;
  keyEvent.isShift = (event->xkey.state & ShiftMask) ? PR_TRUE : PR_FALSE;
  keyEvent.isControl = (event->xkey.state & ControlMask) ? 1 : 0;
  keyEvent.isAlt = (event->xkey.state & Mod1Mask) ? 1 : 0;
  keyEvent.isMeta = (event->xkey.state & Mod1Mask) ? 1 : 0;
  keyEvent.point.x = 0;
  keyEvent.point.y = 0;
  keyEvent.message = NS_KEY_PRESS;
  keyEvent.widget = focusWidget;
  keyEvent.eventStructType = NS_KEY_EVENT;

  if (keyEvent.charCode)
  {
    /* This is the comment from the GTK code. Hope it makes more sense to you 
     * than it did for me.                                                    
     *  
     * if the control, meta, or alt key is down, then we should leave
     * the isShift flag alone (probably not a printable character)
     * if none of the other modifier keys are pressed then we need to
     * clear isShift so the character can be inserted in the editor
     */
    if (!keyEvent.isControl && !keyEvent.isAlt && !keyEvent.isMeta)
      keyEvent.isShift = PR_FALSE;
  }

  focusWidget->DispatchKeyEvent(keyEvent);

}

void
nsAppShell::HandleKeyReleaseEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("KeyRelease event for window 0x%lx\n",
                                       event->xkey.window));

  KeySym     keysym = nsKeyCode::ConvertKeyCodeToKeySym(event->xkey.display,
                                                        event->xkey.keycode);

  switch (keysym) {
    case XK_Alt_L:
    case XK_Alt_R:
      mAltDown = PR_FALSE;
      break;
    case XK_Control_L:
    case XK_Control_R:
      mCtrlDown = PR_FALSE;
      break;
    case XK_Shift_L:
    case XK_Shift_R:
      mShiftDown = PR_FALSE;
      break;
    case XK_Meta_L:
    case XK_Meta_R:
      mMetaDown = PR_FALSE;
      break;
    default:
      break;
  }

  // Dont dispatch events for modifier keys pressed ALONE
  if (nsKeyCode::KeyCodeIsModifier(event->xkey.keycode))
  {
    return;
  }

  nsKeyEvent keyEvent;

  keyEvent.keyCode = nsKeyCode::ConvertKeySymToVirtualKey(keysym);
  keyEvent.charCode = 0;
  keyEvent.time = event->xkey.time;
  keyEvent.isShift = event->xkey.state & ShiftMask;
  keyEvent.isControl = (event->xkey.state & ControlMask) ? 1 : 0;
  keyEvent.isAlt = (event->xkey.state & Mod1Mask) ? 1 : 0;
  keyEvent.isMeta = (event->xkey.state & Mod1Mask) ? 1 : 0;
  keyEvent.point.x = event->xkey.x;
  keyEvent.point.y = event->xkey.y;
  keyEvent.message = NS_KEY_UP;
  keyEvent.widget = aWidget;
  keyEvent.eventStructType = NS_KEY_EVENT;

  NS_ADDREF(aWidget);

  aWidget->DispatchKeyEvent(keyEvent);

  NS_RELEASE(aWidget);
}

void
nsAppShell::HandleFocusInEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("FocusIn event for window 0x%lx\n",
                                       event->xfocus.window));
  nsGUIEvent focusEvent;
  
  focusEvent.message = NS_GOTFOCUS;
  focusEvent.widget  = aWidget;
  
  focusEvent.eventStructType = NS_GUI_EVENT;
  
  focusEvent.time = 0;
  focusEvent.point.x = 0;
  focusEvent.point.y = 0;
  
  NS_ADDREF(aWidget);
  aWidget->DispatchWindowEvent(focusEvent);
  NS_RELEASE(aWidget);
}

void
nsAppShell::HandleFocusOutEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("FocusOut event for window 0x%lx\n",
                                       event->xfocus.window));
  nsGUIEvent focusEvent;
  
  focusEvent.message = NS_LOSTFOCUS;
  focusEvent.widget  = aWidget;
  
  focusEvent.eventStructType = NS_GUI_EVENT;
  
  focusEvent.time = 0;
  focusEvent.point.x = 0;
  focusEvent.point.y = 0;
  
  NS_ADDREF(aWidget);
  aWidget->DispatchWindowEvent(focusEvent);
  NS_RELEASE(aWidget);
}

void
nsAppShell::HandleEnterEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Enter event for window 0x%lx\n",
                                       event->xcrossing.window));
  nsMouseEvent enterEvent;

  if (mDragging) {
    HandleDragEnterEvent(event, aWidget);
  }

  enterEvent.widget  = aWidget;
  
  enterEvent.time = event->xcrossing.time;
  enterEvent.point.x = nscoord(event->xcrossing.x);
  enterEvent.point.y = nscoord(event->xcrossing.y);
  
  enterEvent.message = NS_MOUSE_ENTER;
  enterEvent.eventStructType = NS_MOUSE_EVENT;

  // make sure this is in focus. This will do until I rewrite all the 
  // focus routines. KenF
  aWidget->SetFocus();

  NS_ADDREF(aWidget);
  aWidget->DispatchWindowEvent(enterEvent);
  NS_RELEASE(aWidget);
}

void
nsAppShell::HandleLeaveEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Leave event for window 0x%lx\n",
                                       event->xcrossing.window));

  nsMouseEvent leaveEvent;
  
  if (mDragging) {
    HandleDragLeaveEvent(event, aWidget);
  }

  leaveEvent.widget  = aWidget;
  
  leaveEvent.time = event->xcrossing.time;
  leaveEvent.point.x = nscoord(event->xcrossing.x);
  leaveEvent.point.y = nscoord(event->xcrossing.y);
  
  leaveEvent.message = NS_MOUSE_EXIT;
  leaveEvent.eventStructType = NS_MOUSE_EVENT;
  
  NS_ADDREF(aWidget);
  aWidget->DispatchWindowEvent(leaveEvent);
  NS_RELEASE(aWidget);
}

void nsAppShell::HandleVisibilityNotifyEvent(XEvent *event, nsWidget *aWidget)
{
#ifdef DEBUG
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("VisibilityNotify event for window 0x%lx ",
                                       event->xfocus.window));
  switch(event->xvisibility.state) {
  case VisibilityFullyObscured:
    PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Fully Obscured\n"));
    break;
  case VisibilityPartiallyObscured:
    PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Partially Obscured\n"));
    break;
  case VisibilityUnobscured:
    PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("Unobscured\n"));
  }
#endif
  aWidget->SetVisibility(event->xvisibility.state);
}

void nsAppShell::HandleMapNotifyEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("MapNotify event for window 0x%lx\n",
                                       event->xmap.window));
  // XXX set map status is gone now..
  //  aWidget->SetMapStatus(PR_TRUE);
}

void nsAppShell::HandleUnmapNotifyEvent(XEvent *event, nsWidget *aWidget)
{
  PR_LOG(XlibWidgetsLM, PR_LOG_DEBUG, ("UnmapNotifyEvent for window 0x%lx\n",
                                       event->xunmap.window));
  // XXX set map status is gone now..
  //aWidget->SetMapStatus(PR_FALSE);
}

void nsAppShell::HandleClientMessageEvent(XEvent *event, nsWidget *aWidget)
{
  // check to see if it's a WM_DELETE message
#if defined(DEBUG_warren) || defined(DEBUG_quy)
  printf("handling client message\n");
#endif
  if (nsWidget::WMProtocolsInitialized) {
    if ((Atom)event->xclient.data.l[0] == nsWidget::WMDeleteWindow) {
#ifdef DEBUG
      printf("got a delete window event\n");
#endif /* DEBUG */      
      aWidget->OnDeleteWindow();
    }
  }
}

void nsAppShell::HandleSelectionRequestEvent(XEvent *event, nsWidget *aWidget)
{
  nsGUIEvent ev;

  ev.widget = (nsIWidget *)aWidget;
  ev.nativeMsg = (void *)event;

  aWidget->DispatchWindowEvent(ev);
}

void nsAppShell::HandleDragMotionEvent(XEvent *event, nsWidget *aWidget) {
  nsMouseEvent mevent;
  PRBool currentlyDragging = PR_FALSE;

  nsresult rv;
  nsCOMPtr<nsIDragService> dragService( do_GetService(kCDragServiceCID, &rv) );
  nsCOMPtr<nsIDragSessionXlib> dragServiceXlib;
  if (NS_SUCCEEDED(rv)) {
    dragServiceXlib = do_QueryInterface(dragService);
    if (dragServiceXlib) {
      dragServiceXlib->IsDragging(&currentlyDragging);
    }
  }

  if (currentlyDragging) {
    dragServiceXlib->UpdatePosition(event->xmotion.x, event->xmotion.y);
    mevent.widget = aWidget;
    mevent.point.x = event->xmotion.x;
    mevent.point.y = event->xmotion.y;

    mevent.message = NS_DRAGDROP_OVER;
    mevent.eventStructType = NS_DRAGDROP_EVENT;

    NS_ADDREF(aWidget);
    aWidget->DispatchMouseEvent(mevent);
    NS_RELEASE(aWidget);
  }
}

void nsAppShell::HandleDragEnterEvent(XEvent *event, nsWidget *aWidget) {
  nsMouseEvent enterEvent;
  PRBool currentlyDragging = PR_FALSE;

  nsresult rv;
  nsCOMPtr<nsIDragService> dragService( do_GetService(kCDragServiceCID, &rv) );
  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIDragSessionXlib> dragServiceXlib;
    dragServiceXlib = do_QueryInterface(dragService);
    if (dragServiceXlib) {
      dragServiceXlib->IsDragging(&currentlyDragging);
    }
  }

  if (currentlyDragging) {
    enterEvent.widget  = aWidget;
  
    enterEvent.point.x = event->xcrossing.x;
    enterEvent.point.y = event->xcrossing.y;
  
    enterEvent.message = NS_DRAGDROP_ENTER;
    enterEvent.eventStructType = NS_DRAGDROP_EVENT;

    NS_ADDREF(aWidget);
    aWidget->DispatchWindowEvent(enterEvent);
    NS_RELEASE(aWidget);
  }
}

void nsAppShell::HandleDragLeaveEvent(XEvent *event, nsWidget *aWidget) {
  nsMouseEvent leaveEvent;
  PRBool currentlyDragging = PR_FALSE;
  
  nsresult rv;
  nsCOMPtr<nsIDragService> dragService( do_GetService(kCDragServiceCID, &rv) );

  // FIXME: Not sure if currentlyDragging is required. KenF
  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIDragSessionXlib> dragServiceXlib;
    dragServiceXlib = do_QueryInterface(dragService);
    if (dragServiceXlib) {
      dragServiceXlib->IsDragging(&currentlyDragging);
    }
  }

  if (currentlyDragging) {
    leaveEvent.widget  = aWidget;
  
    leaveEvent.point.x = event->xcrossing.x;
    leaveEvent.point.y = event->xcrossing.y;
  
    leaveEvent.message = NS_DRAGDROP_EXIT;
    leaveEvent.eventStructType = NS_DRAGDROP_EVENT;

    NS_ADDREF(aWidget);
    aWidget->DispatchWindowEvent(leaveEvent);
    NS_RELEASE(aWidget);
  }
}

void nsAppShell::HandleDragDropEvent(XEvent *event, nsWidget *aWidget) {
  nsMouseEvent mevent;
  PRBool currentlyDragging = PR_FALSE;

  nsresult rv;
  nsCOMPtr<nsIDragService> dragService( do_GetService(kCDragServiceCID, &rv) );

  // FIXME: Dont think the currentlyDragging check is required. KenF
  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIDragSessionXlib> dragServiceXlib;
    dragServiceXlib = do_QueryInterface(dragService);
    if (dragServiceXlib) {
      dragServiceXlib->IsDragging(&currentlyDragging);
    }
  }

  if (currentlyDragging) {
    mevent.widget = aWidget;
    mevent.point.x = event->xbutton.x;
    mevent.point.y = event->xbutton.y;
  
    mevent.message = NS_DRAGDROP_DROP;
    mevent.eventStructType = NS_DRAGDROP_EVENT;

    NS_IF_ADDREF(aWidget);
    aWidget->DispatchMouseEvent(mevent);
    NS_IF_RELEASE(aWidget);

    dragService->EndDragSession();
  }
}

void nsAppShell::ForwardEvent(XEvent *event, nsWidget *aWidget)
{
  nsGUIEvent ev;
  ev.widget = (nsIWidget *)aWidget;
  ev.nativeMsg = (void *)event;

  aWidget->DispatchWindowEvent(ev);
}

