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

#ifdef __GNUC__
#pragma implementation
#endif

#include <stdlib.h>
#include <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#ifndef NO_XPM
#include "X11/xpm.h"
#endif
#include <LTKConfig.h>
#include <LTKApp.h>
#include <LTKMenu.h>
#include <LTKWindow.h>
#include <LTKWidget.h>
#include <LTKBox.h>
#include <LTKBorder.h>

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

static Bool isExposeEvent(Display *display, XEvent *e, XPointer win);

LTKWindow::LTKWindow(LTKApp *app1, GBool dialog1, char *title1,
		     char **iconData1, char *defaultWidgetName,
		     LTKBox *box1) {
  app = app1;
  dialog = dialog1;
  title = new GString(title1);
  iconData = iconData1;
  width = 0;
  height = 0;
  widgets = NULL;
  box = box1;
  box->setParent(this);
  selection = NULL;
  savedCursor = None;
  busyCursorNest = 0;
  menu = NULL;
  keyCbk = NULL;
  propCbk = NULL;
  layoutCbk = NULL;
  if (defaultWidgetName)
    defaultWidget = findWidget(defaultWidgetName);
  else
    defaultWidget = NULL;
  keyWidget = NULL;
  selectionWidget = NULL;
  pasteWidget = NULL;
  overWin = NULL;
  xwin = None;
  eventMask = ExposureMask | StructureNotifyMask | VisibilityChangeMask |
              ButtonPressMask | ButtonReleaseMask | KeyPressMask;
  fgGC = bgGC = brightGC = darkGC = xorGC = None;
  fontStruct = NULL;
  next = NULL;
  app->addWindow(this);
}

LTKWindow::~LTKWindow() {
  XEvent event;

  if (xwin) {
    XFreeFont(display, fontStruct);
    XFreeGC(display, fgGC);
    XFreeGC(display, bgGC);
    XFreeGC(display, brightGC);
    XFreeGC(display, darkGC);
    XFreeGC(display, xorGC);
    XUnmapWindow(display, xwin);
    XDestroyWindow(display, xwin);
    XSync(display, False);
    while (XCheckWindowEvent(display, xwin, 0xffffffff, &event)) ;
  }
  app->setGrabWin(NULL);
  app->delWindow(this);
  delete title;
  delete box;
  if (selection)
    delete selection;
  if (menu)
    delete menu;
}

LTKWidget *LTKWindow::addWidget(LTKWidget *widget) {
  widget->setNext(widgets);
  widgets = widget;
  return widget;
}

LTKWidget *LTKWindow::delWidget(LTKWidget *widget) {
  LTKWidget *w1, *w2;

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

LTKWidget *LTKWindow::findWidget(Window xwin1) {
  LTKWidget *widget;

  for (widget = widgets; widget; widget = widget->getNext()) {
    if (widget->getXWindow() == xwin1)
      break;
  }
  return widget;
}

LTKWidget *LTKWindow::findWidget(char *name) {
  LTKWidget *widget;

  for (widget = widgets; widget; widget = widget->getNext()) {
    if (widget->getName() && !strcmp(widget->getName(), name))
      break;
  }
  return widget;
}

void LTKWindow::setPropChangeCbk(LTKWindowPropCbk cbk) {
  propCbk = cbk;
  if (propCbk)
    eventMask |= PropertyChangeMask;
  else
    eventMask &= ~PropertyChangeMask;
  if (xwin != None)
    XSelectInput(display, xwin, eventMask);
}

GBool LTKWindow::checkFills(char **err) {
  // outer box must either have both xfill > 0 and yfill > 0
  // (i.e., the window is resizable), or xfill = yfill = 0
  // (i.e., the window is not resizable)
  if ((box->getXFill() > 0) != (box->getYFill() > 0)) {
    *err = "outer box must have matched xfill and yfill";
    return gFalse;
  }

  // check consistency of box hierarchy
  if (!box->checkFills(err))
    return gFalse;

  // everything checked out ok
  *err = "";
  return gTrue;
}

void LTKWindow::layout(int x, int y, int width1, int height1) {
  XColor bgXcol;
  XGCValues gcValues;
  int minWidth, minHeight;
  int width2, height2;
  char *title1;
  XSizeHints *sizeHints;
  XWMHints *wmHints;
  XClassHint *classHints;
  XTextProperty windowName, iconName;
  GBool newWin;

  // create window and GC's so widgets can use font info, etc.
  if (xwin == None) {
    newWin = gTrue;
    display = app->getDisplay();
    screenNum = app->getScreenNum();
    fgColor = app->getColorResource("foreground", LTK_FOREGROUND,
				    BlackPixel(display, screenNum), NULL);
    bgColor = app->getColorResource("background", LTK_BACKGROUND,
				    WhitePixel(display, screenNum), &bgXcol);
    xwin = XCreateSimpleWindow(display, RootWindow(display, screenNum),
			       (x < 0) ? 0 : x, (y < 0) ? 0 : y, 1, 1, 0,
			       fgColor, bgColor);
    XSelectInput(display, xwin, eventMask);
    gcValues.foreground = fgColor;
    gcValues.background = bgColor;
    gcValues.line_width = 0;
    gcValues.line_style = LineSolid;
    gcValues.graphics_exposures = True;
    fontStruct = app->getFontResource("font", LTK_WIN_FONT);
    gcValues.font = fontStruct->fid;
    fgGC = XCreateGC(display, xwin,
		     GCForeground | GCBackground | GCLineWidth | GCLineStyle |
		     GCGraphicsExposures | GCFont,
		     &gcValues);
    gcValues.foreground = bgColor;
    bgGC = XCreateGC(display, xwin,
		     GCForeground | GCBackground | GCLineWidth | GCLineStyle |
		     GCGraphicsExposures,
		     &gcValues);
    gcValues.foreground = ltkGetBrightColor(display, screenNum,
					    &bgXcol, fgColor);
    brightGC = XCreateGC(display, xwin,
			 GCForeground | GCBackground | GCLineWidth |
			 GCLineStyle | GCGraphicsExposures,
			 &gcValues);
    gcValues.foreground = ltkGetDarkColor(display, screenNum,
					  &bgXcol, fgColor);
    darkGC = XCreateGC(display, xwin,
		       GCForeground | GCBackground | GCLineWidth |
		       GCLineStyle | GCGraphicsExposures,
		       &gcValues);
    gcValues.foreground = fgColor ^ bgColor;
    gcValues.function = GXxor;
    xorGC = XCreateGC(display, xwin,
		      GCForeground | GCBackground | GCLineWidth | GCLineStyle |
		      GCGraphicsExposures | GCFunction,
		      &gcValues);
  } else {
    newWin = gFalse;
  }

  // do layout
  box->layout1();
  minWidth = box->getWidth();
  minHeight = box->getHeight();
  if (box->getXFill() > 0 && box->getYFill() > 0) {
    width2 = (width1 < minWidth) ? minWidth : width1;
    height2 = (height1 < minHeight) ? minHeight : height1;
    box->layout2(0, 0, width2, height2);
  } else {
    box->layout2(0, 0, minWidth, minHeight);
  }
  width = box->getWidth();
  height = box->getHeight();

  // set the real window size
  if (newWin)
    XResizeWindow(display, xwin, width, height);

  // finish the layout and create the widget subwindows
  box->layout3();

  // set window properties
  if (newWin) {
    sizeHints = XAllocSizeHints();
    wmHints = XAllocWMHints();
    classHints = XAllocClassHint();
    if (sizeHints && wmHints && classHints) {
      title1 = title->getCString();
      XStringListToTextProperty(&title1, 1, &windowName);
      XStringListToTextProperty(&title1, 1, &iconName);
      sizeHints->flags = PMinSize;
      sizeHints->flags |= (x >= 0 || y >= 0) ? USPosition : PPosition;
      sizeHints->flags |= (width1 >= 0 || height1 >= 0) ? USSize : PSize;
      sizeHints->min_width = minWidth;
      sizeHints->min_height = minHeight;
      if (!(box->getXFill() > 0 && box->getYFill() > 0)) {
	sizeHints->flags |= PMaxSize;
	sizeHints->max_width = minWidth;
	sizeHints->max_height = minHeight;
      }
      wmHints->input = True;
      wmHints->initial_state = NormalState;
      wmHints->flags = InputHint | StateHint;
#ifndef NO_XPM
      if (iconData) {
	if (XpmCreatePixmapFromData(display, xwin, iconData,
				    &wmHints->icon_pixmap, NULL, NULL) ==
	    XpmSuccess)
	  wmHints->flags |= IconPixmapHint;
      }
#endif
      classHints->res_name = app->getAppName()->getCString();
      classHints->res_class = app->getAppName()->getCString();
      XSetWMProperties(display, xwin, &windowName, &iconName,
		       NULL, 0, sizeHints, wmHints, classHints);
    }
  }

  // call layout callback
  if (layoutCbk)
    (*layoutCbk)(this);
}

void LTKWindow::layoutDialog(LTKWindow *overWin1, int width1, int height1) {
  Window w1, w2, root;
  Window *children;
  Guint numChildren;
  int x, y;
  Guint w, h, bw, depth;

  // save the over-window
  overWin = overWin1;

  // layout the dialog
  layout(0, 0, width1, height1);

  // find the window manager's outermost parent of <overWin>
  w2 = overWin->getXWindow();
  do {
    w1 = w2;
    if (!XQueryTree(display, w2, &root, &w2, &children, &numChildren))
      break;
    if (numChildren > 0)
      XFree((void *)children);
  } while (w2 != root);

  // center the dialog over <overWin>
  XGetGeometry(display, w1, &root, &x, &y, &w, &h, &bw, &depth);
  if ((Guint)width < w)
    x += (w - width) / 2;
  if ((Guint)height < h)
    y += (h - height) / 2;
  XMoveWindow(display, xwin, x, y);
}

void LTKWindow::map() {
  XEvent event;

  box->map();
  XMapWindow(display, xwin);
  XPeekIfEvent(display, &event, &isExposeEvent, (XPointer)this);
  if (dialog)
    app->setGrabWin(this);
}

static Bool isExposeEvent(Display *display, XEvent *e, XPointer w) {
  LTKWindow *win;
  Window xwin;

  win = (LTKWindow *)w;
  xwin = e->xexpose.window;
  return e->type == Expose &&
	 (win->getXWindow() == xwin || win->findWidget(xwin));
}

void LTKWindow::redraw() {
  box->redraw();
}

void LTKWindow::redrawBackground() {
  box->redrawBackground();
}

void LTKWindow::setSelection(LTKWidget *widget, GString *text) {
  selectionWidget = widget;
  if (selection)
    delete selection;
  selection = text;
  //~ this shouldn't use CurrentTime (?)
  XSetSelectionOwner(display, XA_PRIMARY, xwin, CurrentTime);
}

void LTKWindow::requestPaste(LTKWidget *widget) {
  Atom prop;

  prop = XInternAtom(display, "XPDF_SELECTION", False);
  //~ this shouldn't use CurrentTime (?)
  XConvertSelection(display, XA_PRIMARY, XA_STRING, prop, xwin, CurrentTime);
  pasteWidget = widget;
}

void LTKWindow::postMenu(int mx, int my) {
  if (menu)
    menu->post(this, mx, my, NULL);
}

void LTKWindow::keyPress(KeySym key, Guint modifiers, char *s, int n) {
  if (keyWidget)
    keyWidget->keyPress(key, modifiers, s, n);
  else if (keyCbk)
    (*keyCbk)(this, key, modifiers, s, n);
  else if (defaultWidget && n > 0 && (s[0] == '\n' || s[0] == '\r'))
    defaultWidget->activateDefault();
}

void LTKWindow::propChange(Atom atom) {
  if (propCbk)
    (*propCbk)(this, atom);
}

GC LTKWindow::makeGC(unsigned long color, int lineWidth, int lineStyle) {
  XGCValues gcValues;
  GC gc;

  gcValues.foreground = color;
  gcValues.line_width = lineWidth;
  gcValues.line_style = lineStyle;
  gcValues.graphics_exposures = True;
  gc = XCreateGC(display, xwin,
		 GCForeground | GCLineWidth | GCLineStyle |
		 GCGraphicsExposures,
		 &gcValues);
  return gc;
}

void LTKWindow::setTitle(GString *title1) {
  XTextProperty windowName, iconName;
  char *title2;

  delete title;
  title = title1;
  title2 = title->getCString();
  if (xwin != None) {
    XStringListToTextProperty(&title2, 1, &windowName);
    XStringListToTextProperty(&title2, 1, &iconName);
    XSetWMName(display, xwin, &windowName);
    XSetWMIconName(display, xwin, &iconName);
  }
}

void LTKWindow::setCursor(Guint cursor) {
  Cursor c;

  if (busyCursorNest == 0) {
    c = XCreateFontCursor(display, cursor);
    XDefineCursor(display, xwin, c);
    XFreeCursor(display, c);
  }
  savedCursor = cursor;
}

void LTKWindow::setDefaultCursor() {
  if (busyCursorNest == 0)
    XUndefineCursor(display, xwin);
  savedCursor = None;
}

void LTKWindow::setBusyCursor(GBool busy) {
  Cursor c;

  if (busy) {
    if (busyCursorNest == 0) {
      c = XCreateFontCursor(display, XC_watch);
      XDefineCursor(display, xwin, c);
      XFreeCursor(display, c);
      XFlush(display);
    }
    ++busyCursorNest;
  } else if (busyCursorNest > 0) {
    --busyCursorNest;
    if (busyCursorNest == 0) {
      if (savedCursor == None) {
	XUndefineCursor(display, xwin);
      } else {
	c = XCreateFontCursor(display, savedCursor);
	XDefineCursor(display, xwin, c);
	XFreeCursor(display, c);
	XFlush(display);
      }
    }
  }
}

void LTKWindow::raise() {
  XMapRaised(display, xwin);
}
