//========================================================================
//
// LTKApp.cc
//
// Copyright 1996 Derek B. Noonburg
//
//========================================================================

#ifdef __GNUC__
#pragma implementation
#endif

#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#ifdef HAVE_STRINGS_H
// needed by AIX for bzero() declaration for FD_ZERO
#include <strings.h>
#endif
#ifdef HAVE_BSTRING_H
// needed by IRIX for bzero() declaration for FD_ZERO
#include <bstring.h>
#endif
#ifdef HAVE_SYS_SELECT_H
// needed by some systems for fd_set
#include <sys/select.h>
#endif
#ifdef HAVE_SYS_BSDTYPES_H
// needed by some systems for fd_set
#include <sys/bsdtypes.h>
#endif
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include "gtypes.h"
#include "LTKApp.h"
#include "LTKResources.h"
#include "LTKWindow.h"
#include "LTKMenu.h"
#include "LTKMisc.h"
#include "LTKWidget.h"

#ifdef XlibSpecificationRelease
#if XlibSpecificationRelease < 5
typedef char *XPointer;
#endif
#else
typedef char *XPointer;
#endif

#ifdef VMS
extern "C" int XMultiplexInput(int num_displays,
			       Display *displays[],
			       unsigned long ef_mask,
			       unsigned long timeout,
			       unsigned long options,
			       long *retval_pointer);
#if defined(__DECCXX) && (_VMS_VER < 70000000)
extern "C" int gettimeofday (struct timeval *__tp, void *__tzp);
#endif
#endif

//------------------------------------------------------------------------

#define ltkSingleClickTime 200	// max time from press to release for
				//   a single click (in ms)

#define ltkDoubleClickTime 200	// max time from press to press for a
				//   double click (in ms)

//------------------------------------------------------------------------
// LTKApp
//------------------------------------------------------------------------

LTKApp::LTKApp(char *appName1, XrmOptionDescRec *opts,
	       int *argc, char *argv[]) {
  int numOpts;
  XrmDatabase cmdLineDB;
  GString *displayName;
  int h;

  appName = new GString(appName1);
  windows = NULL;
  for (h = 0; h < ltkWinTabSize; ++h)
    winTab[h] = NULL;
  grabWin = NULL;
  activeMenu = NULL;
  repeatWidget = NULL;
  repeatDelay = 0;
  repeatPeriod = 0;
  firstRepeat = gTrue;
  cmdLineDB = NULL;
  resourceDB = NULL;
  XrmInitialize();
  for (numOpts = 0; opts[numOpts].option; ++numOpts) ;
  ltkGetCmdLineResources(&cmdLineDB, opts, numOpts, appName, argc, argv);
  displayName = ltkGetStringResource(cmdLineDB, appName, "display", "");
  if (!(display = XOpenDisplay(displayName->getCString()))) {
    ltkError("Cannot connect to X server %s\n",
	     XDisplayName(displayName->getCString()));
    exit(1);
  }
  delete displayName;
  screenNum = DefaultScreen(display);
  ltkGetOtherResources(display, cmdLineDB, &resourceDB);
  wmDeleteWinAtom = XInternAtom(display, "WM_DELETE_WINDOW", False);
  pressedBtn = 0;
  killCbk = NULL;
}

LTKApp::~LTKApp() {
  LTKWindow *w1;

  while (windows) {
    w1 = windows;
    windows = windows->getNext();
    delete w1;
  }
  XCloseDisplay(display);
  delete appName;
}

GString *LTKApp::getStringResource(char *inst, char *def) {
  return ltkGetStringResource(resourceDB, appName, inst, def);
}

int LTKApp::getIntResource(char *inst, int def) {
  return ltkGetIntResource(resourceDB, appName, inst, def);
}

GBool LTKApp::getBoolResource(char *inst, GBool def) {
  return ltkGetBoolResource(resourceDB, appName, inst, def);
}

unsigned long LTKApp::getColorResource(char *inst,
				       char *def1, unsigned long def2,
				       XColor *xcol) {
  return ltkGetColorResource(resourceDB, appName, inst,
			     display, screenNum, def1, def2, xcol);
}

XFontStruct *LTKApp::getFontResource(char *inst,  char *def) {
  return ltkGetFontResouce(resourceDB, appName, inst, display, screenNum, def);
}

void LTKApp::getGeometryResource(char *inst, int *x, int *y,
				 Guint *width, Guint *height) {
  ltkGetGeometryResource(resourceDB, appName, inst, display, screenNum,
			 x, y, width, height);
}

LTKWindow *LTKApp::addWindow(LTKWindow *w) {
  w->setNext(windows);
  windows = w;
  return w;
}

LTKWindow *LTKApp::delWindow(LTKWindow *w) {
  LTKWindow *w1, *w2;
  int h;
  LTKWinHash *p1, *p2, *p3;

  for (w1 = NULL, w2 = windows; w2 && w2 != w; w1 = w2, w2 = w2->getNext()) ;
  if (w2 == w) {

    // remove window from window list
    if (w1)
      w1->setNext(w2->getNext());
    else
      windows = w2->getNext();
    w2->setNext(NULL);

    // remove window and widgets from hash table
    for (h = 0; h < ltkWinTabSize; ++h) {
      p1 = NULL;
      p2 = winTab[h];
      while (p2) {
	if (p2->win == w) {
	  p3 = p2;
	  if (p1)
	    p2 = p1->next = p2->next;
	  else
	    p2 = winTab[h] = p2->next;
	  delete p3;
	} else {
	  p1 = p2;
	  p2 = p2->next;
	}
      }
    }

    return w2;
  }
  return NULL;
}

void LTKApp::registerXWindow(Window xwin, LTKWindow *win, LTKWidget *widget) {
  int h;
  LTKWinHash *p;

  h = (int)xwin % ltkWinTabSize;
  p = new LTKWinHash;
  p->xwin = xwin;
  p->win = win;
  p->widget = widget;
  p->next = winTab[h];
  winTab[h] = p;
}

LTKWindow *LTKApp::findWindow(Window xwin, LTKWidget **widget) {
  int h;
  LTKWinHash *p;

  h = (int)xwin % ltkWinTabSize;
  for (p = winTab[h]; p; p = p->next) {
    if (p->xwin == xwin) {
      *widget = p->widget;
      return p->win;
    }
  }
  *widget = NULL;
  return NULL;
}

void LTKApp::setRepeatEvent(LTKWidget *repeatWidget1, int repeatDelay1,
			    int repeatPeriod1) {
  repeatWidget = repeatWidget1;
  repeatDelay = repeatDelay1;
  repeatPeriod = repeatPeriod1;
  firstRepeat = gTrue;
  gettimeofday(&lastRepeat, NULL);
}

void LTKApp::doEvent(GBool wait) {
#ifdef VMS
  long delay, retval;
#else
  fd_set readFDs, writeFDs, exceptFDs;
#endif
  struct timeval curTime, timeout;
  int timeout1;
  XEvent event;
  XEvent event2;
  Window pointerRoot, pointerChild;
  LTKWindow *win;
  LTKWidget *widget;
  LTKMenu *menu;
  KeySym key;
  GString *str;
  char buf[20];
  Atom typeRet;
  int formatRet;
  unsigned long length, left;
  unsigned char *bufPtr;
  GBool click, dblClick;
  int x, y, rx, ry;
  unsigned int mask;
  int n, i;

  while (XPending(display) == 0) {
    if (!wait)
      return;
#ifndef VMS
    FD_ZERO(&readFDs);
    FD_ZERO(&writeFDs);
    FD_ZERO(&exceptFDs);
    n = ConnectionNumber(display);
    FD_SET(n, &readFDs);
#endif
    if (!repeatWidget) {
#ifdef VMS
      n = XMultiplexInput(1, &display, 0, 0, 0, &retval);
#else // VMS
#ifdef SELECT_TAKES_INT
      n = select(n+1, (int *)&readFDs, (int *)&writeFDs, (int *)&exceptFDs,
		 NULL);
#else
      n = select(n+1, &readFDs, &writeFDs, &exceptFDs, NULL);
#endif
#endif // VMS
    } else {
      gettimeofday(&curTime, NULL);
      timeout.tv_sec = curTime.tv_sec - lastRepeat.tv_sec;
      if (curTime.tv_usec < lastRepeat.tv_usec) {
	--timeout.tv_sec;
	timeout.tv_usec = (1000000 + curTime.tv_usec) - lastRepeat.tv_usec;
      } else {
	timeout.tv_usec = curTime.tv_usec - lastRepeat.tv_usec;
      }
      timeout1 = firstRepeat ? repeatDelay : repeatPeriod;
      if (timeout.tv_sec > 0 || timeout.tv_usec > timeout1)
	timeout.tv_usec = 0;
      else
	timeout.tv_usec = timeout1 - timeout.tv_usec;
      timeout.tv_sec = 0;
#ifdef VMS
      if ((delay = timeout.tv_usec / 1000) == 0)
	delay = 1;
      n = XMultiplexInput(1, &display, 0, delay, 0, &retval);
#else // VMS
#ifdef SELECT_TAKES_INT
      n = select(n+1, (int *)&readFDs, (int *)&writeFDs, (int *)&exceptFDs,
		 &timeout);
#else
      n = select(n+1, &readFDs, &writeFDs, &exceptFDs, &timeout);
#endif
#endif // VMS
    }
    if (n == 0 && repeatWidget) {
      repeatWidget->repeatEvent();
      firstRepeat = gFalse;
      gettimeofday(&lastRepeat, NULL);
      return;
    }
  }

  XNextEvent(display, &event);
  win = findWindow(event.xany.window, &widget);
  if (activeMenu && event.xany.window == activeMenu->getXWindow())
    menu = activeMenu;
  else
    menu = NULL;
  switch (event.type) {
  case Expose:
    // redraw the window or widget, ignoring all but the last
    // Expose event for that window
    if (event.xexpose.count == 0) {
      if (widget)
	widget->redraw();
      else if (win)
	win->redrawBackground();
      else if (menu)
	menu->redraw();
    }
    break;
  case GraphicsExpose:
    // redraw the window or widget, ignoring all but the last
    // GraphicsExpose event for that window
    if (event.xgraphicsexpose.count == 0) {
      if (widget)
	widget->redraw();
      else if (win)
	win->redrawBackground();
      else if (menu)
	menu->redraw();
    }
    break;
  case ConfigureNotify:
    if (win && !widget) {
      if (event.xconfigure.width != win->getWidth() ||
	  event.xconfigure.height != win->getHeight()) {
	XClearWindow(display, win->getXWindow());
	win->layout(-1, -1, event.xconfigure.width, event.xconfigure.height);
      }
    }
    break;
  case VisibilityNotify:
    if (event.xvisibility.state == VisibilityUnobscured &&
	grabWin && win == grabWin->getOverWin())
      XRaiseWindow(display, grabWin->getXWindow());
    break;
  case ButtonPress:
    dblClick = event.xbutton.time - buttonPressTime < ltkDoubleClickTime;
    buttonPressTime = event.xbutton.time;
    pressedBtn = event.xbutton.button - Button1 + 1;
    if (menu) {
      menu->buttonPress(event.xbutton.x, event.xbutton.y,
			pressedBtn, dblClick);
    } else if (win && !(grabWin && win != grabWin)) {
      if (event.xbutton.button == Button3) {
	win->postMenu(event.xbutton.x_root, event.xbutton.y_root);
      } else {
	if (widget != win->getKeyWidget()) {
	  if (win->getKeyWidget())
	    win->getKeyWidget()->activate(gFalse);
	  if (widget)
	    widget->activate(gTrue);
	}
	if (widget) {
	  widget->buttonPress(event.xbutton.x, event.xbutton.y,
			      pressedBtn, dblClick);
	}
      }
    }
    break;
  case ButtonRelease:
    pressedBtn = 0;
    click = event.xbutton.time - buttonPressTime < ltkSingleClickTime;
    if (menu) {
      menu->buttonRelease(event.xbutton.x, event.xbutton.y,
			  event.xbutton.button - Button1 + 1, click);
    } else if (win && !(grabWin && win != grabWin) && widget) {
      widget->buttonRelease(event.xbutton.x, event.xbutton.y,
			    event.xbutton.button - Button1 + 1, click);
    }
    break;
  case MotionNotify:
    // this assumes PointerMotionHintMask was selected, so we need
    // to call XQueryPointer
    XQueryPointer(display, event.xany.window, &pointerRoot, &pointerChild,
		  &rx, &ry, &x, &y, &mask);
    if (menu) {
      menu->mouseMove(x, y, pressedBtn);
    } else if (win && !(grabWin && win != grabWin) && widget) {
      widget->mouseMove(x, y, pressedBtn);
    }
    break;
  case KeyPress:
    if (win && !(grabWin && win != grabWin)) {
      n = XLookupString(&event.xkey, buf, sizeof(buf)-1,
			&key, NULL);
      buf[n] = '\0';
      win->keyPress(key, event.xkey.state, buf, n);
    }
    break;
  case SelectionRequest:
    event2.xselection.type = SelectionNotify;
    event2.xselection.display = display;
    event2.xselection.requestor = event.xselectionrequest.requestor;
    event2.xselection.selection = event.xselectionrequest.selection;
    event2.xselection.target = event.xselectionrequest.target;
    event2.xselection.property = None;
    event2.xselection.time = event.xselectionrequest.time;
    if (event.xselectionrequest.target == XA_STRING &&
	win && (str = win->getSelection())) {
      XChangeProperty(display, event.xselectionrequest.requestor,
		      event.xselectionrequest.property, XA_STRING, 8,
		      PropModeReplace,
		      (Guchar *)str->getCString(), str->getLength());
      event2.xselection.property = event.xselectionrequest.property;
    }
    XSendEvent(display, event.xselectionrequest.requestor,
	       False, 0, &event2);
    break;
  case SelectionClear:
    if (win && win->getSelectionWidget()) {
      win->getSelectionWidget()->clearSelection();
    }
    break;
  case SelectionNotify:
    if (win && !(grabWin && win != grabWin) && win->getPasteWidget() &&
	event.xselection.target == XA_STRING &&
	event.xselection.property != None) {
      i = 0;
      str = new GString();
      do {
	if (XGetWindowProperty(display, win->getXWindow(),
			       event.xselection.property,
			       i/4, 256/4, True, XA_STRING,
			       &typeRet, &formatRet, &length, &left,
			       &bufPtr) != Success)
	  break;
	str->append((char *)bufPtr, (int)length);
	XFree((XPointer)bufPtr);
	i += (int)length;
      } while (left > 0);
      win->getPasteWidget()->paste(str);
      delete str;
    }
    break;
  case PropertyNotify:
    if (win && !(grabWin && win != grabWin))
      win->propChange(event.xproperty.atom);
    break;
  case ClientMessage:
    if ((Atom)event.xclient.data.l[0] == wmDeleteWinAtom && win) {
      if (killCbk)
	(*killCbk)(win);
      else
	exit(0);
    }
    break;
  default:
    break;
  }
}
