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

#ifdef __GNUC__
#pragma implementation
#endif

#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

#ifdef _AIX
#include <sys/select.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>

#if defined(VMS) && defined(__DECCXX) && (_VMS_VER < 70000000)
extern "C" int gettimeofday (struct timeval *__tp, void *__tzp);
#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;

  appName = new GString(appName1);
  windows = 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(NULL));
    exit(1);
  }
  delete displayName;
  screenNum = DefaultScreen(display);
  ltkGetOtherResources(display, cmdLineDB, &resourceDB);
  pressedBtn = 0;
}

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);
}

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;

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

LTKWindow *LTKApp::findWindow(Window xwin, LTKWidget **widget) {
  LTKWindow *w;
  Window root;
  Window xparent;
  Window *children;
  unsigned int numChildren;

  root = None;
  xparent = None;
  children = NULL;
  numChildren = 0;
  XQueryTree(display, xwin, &root, &xparent, &children, &numChildren);
  if (numChildren > 0)
    XFree((void *)children);
  *widget = NULL;
  for (w = windows; w; w = w->getNext()) {
    if (w->getXWindow() == xwin)
      break;
    if (w->getXWindow() == xparent) {
      *widget = w->findWidget(xwin);
      break;
    }
  }
  return w;
}

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) {
#ifndef VMS
  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;
#ifdef __VMS
  if (XPending(display) == 0) {
     if (!wait)
       return;
     if (repeatWidget) {
         repeatWidget->repeatEvent();
         return;
       }
     }
#else /* __VMS */
  while (XPending(display) == 0) {
    if (!wait)
      return;
    FD_ZERO(&readFDs);
    FD_ZERO(&writeFDs);
    FD_ZERO(&exceptFDs);
    n = ConnectionNumber(display);
    FD_SET(n, &readFDs);
    if (!repeatWidget) {
#ifdef __hpux
      n = select(n+1, (int *)&readFDs, (int *)&writeFDs, (int *)&exceptFDs,
		 NULL);
#else
      n = select(n+1, &readFDs, &writeFDs, &exceptFDs, NULL);
#endif
    } 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 __hpux
      n = select(n+1, (int *)&readFDs, (int *)&writeFDs, (int *)&exceptFDs,
		 &timeout);
#else
      n = select(n+1, &readFDs, &writeFDs, &exceptFDs, &timeout);
#endif
    }
    if (n == 0 && repeatWidget) {
      repeatWidget->repeatEvent();
      firstRepeat = gFalse;
      gettimeofday(&lastRepeat, NULL);
      return;
    }
  }
#endif /* __VMS*/
  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((XKeyEvent *)&event, 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, length);
	XFree(bufPtr);
	i += length;
      } while (left > 0);
      win->getPasteWidget()->paste(str);
      delete str;
    }
    break;
  case PropertyNotify:
    if (win && !(grabWin && win != grabWin))
      win->propChange(event.xproperty.atom);
    break;
  default:
    break;
  }
}
