/**************************************************************************

    This file is part of Xclass95, a Win95-looking GUI toolkit.
    Copyright (C) 1996, 1997 David Barth, Ricky Ralston, Hector Peraza.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>

#include <X11/cursorfont.h>

#include <xclass/utils.h>
#include <xclass/OXClient.h>
#include <xclass/OXFrame.h>
#include <xclass/OXWidgets.h>
#include <xclass/OXButton.h>
#include <xclass/OXListView.h>
#include <xclass/OXFSDDListBox.h>
#include <xclass/OGC.h>
#include <xclass/OPicture.h>
#include <xclass/OTimer.h>
#include <xclass/OFileHandler.h>
#include <xclass/OIniFile.h>

#include "icons/rbutton.on.xpm"
#include "icons/rbutton.off.xpm"

#include "icons/arrow-left.xpm"
#include "icons/arrow-right.xpm"
#include "icons/arrow-up.xpm"
#include "icons/arrow-down.xpm"


//--- This is tmp here...

#include <sys/wait.h>
#ifdef HAVE_WAITPID
#define ReapChildren()  while ((waitpid(-1, NULL, WNOHANG)) > 0);
#else
#define ReapChildren()  while ((wait3(NULL, WNOHANG, NULL)) > 0);
#endif


Atom WM_DELETE_WINDOW;
Atom _MOTIF_WM_HINTS;
Atom _XCLASS_MESSAGE;

Pixmap checkered, checkered1;

int gray_width  = 8;
int gray_height = 8;
unsigned char gray_bits[] = {
   0x55, 0xaa, 0x55, 0xaa,
   0x55, 0xaa, 0x55, 0xaa
};


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

// The following global declarations were moved from their original
// places to here in order to avoid the linker including unnecessary
// widgets in an executable file.

Colormap OPicturePool::_defaultColormap;

GC OXButton::_defaultGC;
GC OXButton::_hibckgndGC;
XFontStruct *OXTextButton::_defaultFontStruct;

GC OXCheckButton::_defaultGC;
XFontStruct *OXCheckButton::_defaultFontStruct;

GC OXRadioButton::_defaultGC;
XFontStruct *OXRadioButton::_defaultFontStruct;
const OPicture *OXRadioButton::_on;
const OPicture *OXRadioButton::_off;

const OPicture * OXFrame::_defaultBackgroundPicture;
unsigned long OXFrame::_defaultFrameBackground;
unsigned long OXFrame::_defaultSelectedBackground;
unsigned long OXFrame::_whitePixel;
unsigned long OXFrame::_blackPixel;
GC OXFrame::_blackGC;
GC OXFrame::_whiteGC;
GC OXFrame::_hilightGC;
GC OXFrame::_shadowGC;
GC OXFrame::_bckgndGC;

GC OXLabel::_defaultGC;
XFontStruct *OXLabel::_defaultFontStruct;

GC OXMenuTitle::_defaultGC;
GC OXMenuTitle::_defaultSelectedGC;
GC OXMenuTitle::_defaultSelectedBackgroundGC;
XFontStruct *OXMenuTitle::_defaultFontStruct;

GC OXPopupMenu::_defaultGC;
GC OXPopupMenu::_defaultSelectedGC;
GC OXPopupMenu::_defaultSelectedBackgroundGC;
XFontStruct *OXPopupMenu::_defaultFontStruct;
XFontStruct *OXPopupMenu::_hilightFontStruct;
Cursor OXPopupMenu::_defaultCursor;
Cursor OXMenuBar::_defaultCursor;

Pixmap OXScrollBar::_bckgndPixmap;
const OPicture *OXScrollBar::_arrow_left;
const OPicture *OXScrollBar::_arrow_right;
const OPicture *OXScrollBar::_arrow_up;
const OPicture *OXScrollBar::_arrow_down;
int OXScrollBar::_sb_width;

GC OXTab::_defaultGC;
XFontStruct *OXTab::_defaultFontStruct;

GC OXTextEntry::_defaultGC;
GC OXTextEntry::_defaultSelectedGC;
GC OXTextEntry::_defaultSelectedBackgroundGC;
XFontStruct *OXTextEntry::_defaultFontStruct;
Cursor OXTextEntry::_defaultCursor;

GC OXGroupFrame::_defaultGC;
XFontStruct *OXGroupFrame::_defaultFontStruct;

unsigned long OXTextLBEntry::_selPixel;
GC OXTextLBEntry::_defaultGC;            // this is not shared.
XFontStruct *OXTextLBEntry::_defaultFontStruct;

Cursor OXDDPopup::_defaultCursor;

const OPicture *OXDDListBox::_bpic;

GC OSelectedPicture::_selectedGC;
GC OXListViewContainer::_lineGC;
GC OXListView::_defaultGC;  
XFontStruct *OXListView::_defaultFontStruct;

unsigned int OXListItem::_selPixel;
GC OXListItem::_defaultGC;
XFontStruct *OXListItem::_defaultFontStruct;

unsigned long OXTreeLBEntry::_selPixel;
GC OXTreeLBEntry::_defaultGC;            // this is not shared.
XFontStruct *OXTreeLBEntry::_defaultFontStruct;


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

OXClient::OXClient(const char *DpyName) {
  XGCValues gval;
  unsigned long gmask;
  XWindowAttributes root_attr;
  char *picturePoolDir;
  char *inifile = ".xclassrc";
  char inirc[PATH_MAX], line[1024], arg[256];
  char norm_font[256];
  char bold_font[256];
  char small_font[256];
  char prop_font[256];
  char backcolor[256];
  char forecolor[256];
  char selbackcolor[256];
  char selforecolor[256];
  char backpixmap[256];
  char icon_pool[PATH_MAX];
  int sb_width;

  //--- Load xclass defaults from .xclassrc

  char *HomeDir = getenv("HOME");
  if (!HomeDir) HomeDir = "/";

  sprintf(inirc, "%s/.xclassrc", HomeDir);

  OIniFile xcini(inirc, INI_READ);

  strcpy(norm_font, "-adobe-helvetica-medium-r-*-*-12-*-*-*-*-*-iso8859-1");
  strcpy(bold_font, "-adobe-helvetica-bold-r-*-*-12-*-*-*-*-*-iso8859-1");
  strcpy(small_font, "-adobe-helvetica-medium-r-*-*-10-*-*-*-*-*-iso8859-1");
  strcpy(prop_font, "-adobe-courier-medium-r-*-*-12-*-*-*-*-*-iso8859-1");
  strcpy(backcolor, "#c0c0c0");
  strcpy(forecolor, "black");
  strcpy(selforecolor, "white");
  strcpy(selbackcolor, "#000080");
  strcpy(backpixmap, "");
  sb_width = SB_WIDTH;
  strcpy(icon_pool, OX_DEFAULT_POOL);

  while (xcini.GetNext(line)) {
    if (strcasecmp(line, "defaults") == 0) {
      if (xcini.GetItem("normal font", arg)) strcpy(norm_font, arg);
      if (xcini.GetItem("bold font", arg)) strcpy(bold_font, arg);
      if (xcini.GetItem("small font", arg)) strcpy(small_font, arg);
      if (xcini.GetItem("proportional font", arg)) strcpy(prop_font, arg);
      if (xcini.GetItem("fore color", arg)) strcpy(forecolor, arg);
      if (xcini.GetItem("back color", arg)) strcpy(backcolor, arg);
      if (xcini.GetItem("hifore color", arg)) strcpy(selforecolor, arg);
      if (xcini.GetItem("hiback color", arg)) strcpy(selbackcolor, arg);
      if (xcini.GetItem("sb width", arg)) sb_width = atoi(arg);
      if (xcini.GetItem("icon dir", arg)) strcpy(icon_pool, arg);
      if (xcini.GetItem("back pixmap", arg)) strcpy(backpixmap, arg);
    }
  }

  //--- Open the connection to the display

  if (!(_dpy = XOpenDisplay(DpyName)))
    FatalError("Can't open display \"%s\"\n", XDisplayName(DpyName));

  _screen = DefaultScreen(_dpy);
  _depth = DefaultDepth(_dpy, _screen);

  _x_fd = ConnectionNumber(_dpy);

  //--- Initialize internal lists

  _wlist    = new OXSList(_dpy, "Application Frames");
  _tlist    = new OXSTimerList(_dpy, "Timers");
  _idlelist = new OXSList(_dpy, "Idle Handlers");
  _iolist   = new OXSList(_dpy, "I/O Handlers");
  _siglist  = new OXSList(_dpy, "Signal Handlers");

  if (!_wlist || !_tlist || !_idlelist || !_iolist || !_siglist)
    FatalError("Not enough memory");

  _tip_timer = NULL;
  _tip_status = False;

  FD_ZERO(&readset);
  FD_ZERO(&writeset);
  FD_ZERO(&excptset);


  //--- Setup some atoms...

  WM_DELETE_WINDOW = XInternAtom(_dpy, "WM_DELETE_WINDOW", False);
  _MOTIF_WM_HINTS = XInternAtom(_dpy, "_MOTIF_WM_HINTS", False);
  _XCLASS_MESSAGE = XInternAtom(_dpy, "_XCLASS_MESSAGE", False);


  //--- Create an object for the root window, create picture pool, etc...

  _globalNeedRedraw = False;

  _Root = new OXWindow(this, XDefaultRootWindow(_dpy), NULL);

  Debug(DBG_MISC, "Creating picture pool...\n");
  if ((picturePoolDir = getenv("OX_POOL")) == NULL)
    picturePoolDir = icon_pool;
  _picturePool = new OPicturePool(this, picturePoolDir);

  _gcPool = new OGCPool(this);

  //--- Set defaults... NOTE: Need to check whether the fonts exist or not.
  //                          Currently applications would coredump if
  //                          any of these cannot be found.

  OXLabel::_defaultFontStruct =
  OXTab::_defaultFontStruct =
  OXTextLBEntry::_defaultFontStruct =
  OXTreeLBEntry::_defaultFontStruct =
  OXGroupFrame::_defaultFontStruct =
  OXTextEntry::_defaultFontStruct =
  OXRadioButton::_defaultFontStruct =
  OXCheckButton::_defaultFontStruct =
  OXTextButton::_defaultFontStruct =
  OXMenuTitle::_defaultFontStruct =
  OXPopupMenu::_defaultFontStruct = XLoadQueryFont(_dpy, norm_font);
  OXPopupMenu::_hilightFontStruct = XLoadQueryFont(_dpy, bold_font);

  OXListView::_defaultFontStruct =
  OXListItem::_defaultFontStruct = XLoadQueryFont(_dpy, small_font);

  _white = GetColorByName("white");
  _black = GetColorByName("black");

  _backcolor = GetColorByName(backcolor);
  _forecolor = GetColorByName(forecolor);
  _hilite    = GetHilite(_backcolor);
  _shadow    = GetShadow(_backcolor);
  _selforecolor = GetColorByName(selforecolor);
  _selbackcolor = GetColorByName(selbackcolor);

  //--- Get color scheme from the color server, if there is such thing
  //    running (this is just experimental and very rudimentary code).

  {
  int  fmt;
  unsigned long nret, nafter;
  unsigned char *data;
  Atom ret_atom;

  Atom _XCLASS_COLORS = XInternAtom(_dpy, "_XCLASS_COLORS", False);

  if (XGetWindowProperty(_dpy, _Root->GetId(), _XCLASS_COLORS,
           0, 6, False, _XCLASS_COLORS, &ret_atom, &fmt, &nret, &nafter,
           (unsigned char **) &data) == Success) {

    if ((ret_atom != None) && (fmt == 32) && (nret == 6)) {
      unsigned long *d = (unsigned long *) data;

      _backcolor = d[0];
      _forecolor = d[1];
      _hilite = d[2];
      _shadow = d[3];
      _selforecolor = d[4];
      _selbackcolor = d[5];
    }

    XFree(data);
    }
  }

  //--- Default GCs and misc...

  gmask = GCForeground | GCBackground | GCFont |
          GCFillStyle | GCGraphicsExposures;
  gval.fill_style = FillSolid;
  gval.graphics_exposures = False;
  gval.font = OXTextButton::_defaultFontStruct->fid;
  gval.background = _backcolor;

  OXFrame::_blackPixel = gval.foreground = _black;
  //OXFrame::_blackGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXFrame::_blackGC = _gcPool->GetGC(_Root->GetId(), gmask, &gval)->GetGC();

  OXFrame::_whitePixel = gval.foreground = _white;
  //OXFrame::_whiteGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXFrame::_whiteGC = _gcPool->GetGC(_Root->GetId(), gmask, &gval)->GetGC();

  gval.foreground = _hilite;
  //OXFrame::_hilightGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXFrame::_hilightGC = _gcPool->GetGC(_Root->GetId(), gmask, &gval)->GetGC();

  gval.foreground = _shadow;
  //OXFrame::_shadowGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXFrame::_shadowGC = _gcPool->GetGC(_Root->GetId(), gmask, &gval)->GetGC();

  gval.foreground = _backcolor;
  //OXFrame::_bckgndGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXFrame::_bckgndGC = _gcPool->GetGC(_Root->GetId(), gmask, &gval)->GetGC();

  gval.foreground = _forecolor;
  OXRadioButton::_defaultGC =
  OXCheckButton::_defaultGC =
  OXLabel::_defaultGC =
  OXTab::_defaultGC =
  OXGroupFrame::_defaultGC =
  OXTextEntry::_defaultGC =
  OXMenuTitle::_defaultGC =
  OXPopupMenu::_defaultGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXButton::_defaultGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXTextLBEntry::_defaultGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXTreeLBEntry::_defaultGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);

  OXFrame::_defaultFrameBackground = _backcolor;
  OXFrame::_defaultSelectedBackground = gval.foreground = _selbackcolor;
  OXTextEntry::_defaultSelectedBackgroundGC =
  OXPopupMenu::_defaultSelectedBackgroundGC =
  OXMenuTitle::_defaultSelectedBackgroundGC =
       XCreateGC(_dpy, _Root->GetId(), gmask, &gval);

  OXListItem::_selPixel =
  OXTextLBEntry::_selPixel = 
  OXTreeLBEntry::_selPixel = gval.foreground = _selforecolor;
  OXTextEntry::_defaultSelectedGC =
  OXMenuTitle::_defaultSelectedGC =
  OXPopupMenu::_defaultSelectedGC =
       XCreateGC(_dpy, _Root->GetId(), gmask, &gval);

  gval.font = OXListItem::_defaultFontStruct->fid;
  gval.foreground = _forecolor;
  OXListItem::_defaultGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  OXListView::_defaultGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);

  OXDDPopup::_defaultCursor =
  OXMenuBar::_defaultCursor =
  OXPopupMenu::_defaultCursor = XCreateFontCursor(_dpy, XC_arrow);
  OXTextEntry::_defaultCursor = XCreateFontCursor(_dpy, XC_xterm);

  XGetWindowAttributes(_dpy, _Root->GetId(), &root_attr);
  _defaultColormap = root_attr.colormap;
  OPicturePool::_defaultColormap = _defaultColormap;

  OXScrollBar::_sb_width = sb_width;

  OXScrollBar::_bckgndPixmap =
  checkered = XCreatePixmapFromBitmapData(_dpy, _Root->GetId(),
                     (char *)gray_bits, gray_width, gray_height,
                     _backcolor, _white /*_hilite*/, _depth);

  gmask = GCForeground | GCBackground | GCTile |
          GCFillStyle  | GCGraphicsExposures;
  gval.foreground = _hilite;
  gval.background = _backcolor;
  gval.fill_style = FillTiled;
  gval.tile       = checkered;
  OXButton::_hibckgndGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);

  OXRadioButton::_on = GetPicture("rbutton.on.xpm", XCP_rbutton_on);
  OXRadioButton::_off = GetPicture("rbutton.off.xpm", XCP_rbutton_off);

  OXScrollBar::_arrow_left  = GetPicture("arrow-left.xpm", XCP_arrow_left);
  OXScrollBar::_arrow_right = GetPicture("arrow-right.xpm", XCP_arrow_right);
  OXScrollBar::_arrow_up    = GetPicture("arrow-up.xpm", XCP_arrow_up);
  OXDDListBox::_bpic        =
  OXScrollBar::_arrow_down  = GetPicture("arrow-down.xpm", XCP_arrow_down);

  gmask |= GCFillStyle | GCStipple;
  gval.foreground = _selbackcolor;
  gval.background = _black;
  gval.fill_style = FillStippled;
  checkered1 = XCreatePixmapFromBitmapData(_dpy, _Root->GetId(), (char *)gray_bits,
                                          gray_width, gray_height, 1, 0, 1);
  gval.stipple = checkered1;  
  OSelectedPicture::_selectedGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);

  gmask = GCForeground | GCBackground | GCFunction | GCFillStyle |
          GCLineWidth  | GCLineStyle  | GCSubwindowMode | GCGraphicsExposures;
  gval.foreground = _white ^ _black;
  gval.background = _white;
  gval.function   = GXxor;
  gval.line_width = 0;
  gval.line_style = LineOnOffDash;
  gval.fill_style = FillSolid;
  gval.subwindow_mode = IncludeInferiors;
  gval.graphics_exposures = False; 
  OXListViewContainer::_lineGC = XCreateGC(_dpy, _Root->GetId(), gmask, &gval);
  XSetDashes(_dpy, OXListViewContainer::_lineGC, 0, "\x1\x1", 2);

  if (backpixmap[0] == 0)
    OXFrame::_defaultBackgroundPicture = NULL;
  else
    OXFrame::_defaultBackgroundPicture = GetPicture(backpixmap);
}

OXClient::~OXClient() {

  //--- Going down: cleanup, close X connection and exit

  Debug(DBG_MISC, "OXClient: terminating!\n");

  delete _wlist;
  delete _tlist;
  delete _idlelist;
  delete _iolist;
  delete _siglist;

  delete _picturePool;
  delete _gcPool;

  XFreeFont(_dpy, OXPopupMenu::_defaultFontStruct);
  XFreeFont(_dpy, OXPopupMenu::_hilightFontStruct);

  XFreeGC(_dpy, OXPopupMenu::_defaultGC);
  XFreeGC(_dpy, OXButton::_defaultGC);
  //XFreeGC(_dpy, OXFrame::_blackGC);
  //XFreeGC(_dpy, OXFrame::_whiteGC);
  //XFreeGC(_dpy, OXFrame::_hilightGC);
  //XFreeGC(_dpy, OXFrame::_shadowGC);
  //XFreeGC(_dpy, OXFrame::_bckgndGC);
  XFreeGC(_dpy, OXPopupMenu::_defaultSelectedGC);
  XFreeGC(_dpy, OXButton::_hibckgndGC);

  XFreePixmap(_dpy, checkered);
  XFreePixmap(_dpy, checkered1);

  XCloseDisplay(_dpy); // this should do a cleanup of the remaining
                       // X allocated objects...

  exit(0);  // bye, bye...
}

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

// Color management routines...

unsigned long OXClient::GetColorByName(const char *name) const {
  XColor color;
  XWindowAttributes attributes;

  XGetWindowAttributes(_dpy, _Root->GetId(), &attributes);
  color.pixel = 0;
  if (!XParseColor (_dpy, attributes.colormap, name, &color)) 
    Debug(DBG_WARN, "OXClient: Couldn't parse color %s\n",name);
  else if(!XAllocColor (_dpy, attributes.colormap, &color)) 
    Debug(DBG_WARN, "OXClient: Couldn't retrieve color %s\n",name);
  return color.pixel;
}

unsigned long OXClient::GetHilite(const unsigned long base_color) const {
  XColor color, white_p;
  XWindowAttributes attributes;
  
  XGetWindowAttributes(_dpy, _Root->GetId(), &attributes);
  
  color.pixel = base_color;
  XQueryColor(_dpy, attributes.colormap, &color);

  white_p.pixel = GetColorByName("white");
  XQueryColor(_dpy, attributes.colormap, &white_p);
  
  color.red   = max((white_p.red/5),   color.red);
  color.green = max((white_p.green/5), color.green);
  color.blue  = max((white_p.blue/5),  color.blue);
  
  color.red   = min(white_p.red,   (color.red*140)/100);
  color.green = min(white_p.green, (color.green*140)/100);
  color.blue  = min(white_p.blue,  (color.blue*140)/100);
  
  if (!XAllocColor(_dpy, attributes.colormap, &color))
    Debug(DBG_WARN, "OXClient: Couldn't allocate hilight color\n");

  return color.pixel;
}

unsigned long OXClient::GetShadow(const unsigned long base_color) const {
  XColor color;
  XWindowAttributes attributes;
  
  XGetWindowAttributes(_dpy, _Root->GetId(), &attributes);
  
  color.pixel = base_color;
  XQueryColor(_dpy, attributes.colormap, &color);
  
  color.red   = (unsigned short)((color.red*60)/100); /* was 50% */
  color.green = (unsigned short)((color.green*60)/100);
  color.blue  = (unsigned short)((color.blue*60)/100);
  
  if(!XAllocColor(_dpy, attributes.colormap, &color))
    Debug(DBG_WARN, "OXClient: Couldn't allocate shadow color\n");
  
  return color.pixel;
}


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

// Picture pool management...

const OPicture *OXClient::GetPicture(const char *name, char **data) {
  return _picturePool->GetPicture(name, data);
}

const OPicture *OXClient::GetPicture(const char *name, char **data,
                                     int new_width, int new_height) {
  return _picturePool->GetPicture(name, data,new_width, new_height);
}

const OPicture *OXClient::GetPicture(const char *name) {
  return _picturePool->GetPicture(name);
}

const OPicture *OXClient::GetPicture(const char *name,
                                     int new_width, int new_height) {
  return _picturePool->GetPicture(name, new_width, new_height);
}

void OXClient::FreePicture(const OPicture *pic) {
  _picturePool->FreePicture(pic);
}


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

// Register / unregister windows...

int OXClient::RegisterWindow(OXWindow *w) {
  return (_wlist->Add(w->_id, (XPointer) w) != NULL);
}

int OXClient::UnregisterWindow(OXWindow *w) {
  return (_wlist->Remove(w->_id));
}


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

// Register / unregister timers...

int OXClient::RegisterTimer(OTimer *t) {
  return (_tlist->Add(t->_id, (XPointer) t) != NULL);
}

int OXClient::UnregisterTimer(OTimer *t) {
  return (_tlist->Remove(t->_id));
}


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

// Register / unregister file events...

int OXClient::RegisterFileEvent(OFileHandler *fh) {
  return (_iolist->Add(fh->_fd, (XPointer) fh) != NULL);
}

int OXClient::UnregisterFileEvent(OFileHandler *fh) {
  return (_iolist->Remove(fh->_fd));
}


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

// Event loop stuff...

void OXClient::Run() {
  MainLoop();
}

void OXClient::MainLoop() {
  while (True) {
    ProcessOneEvent(0, None);
  }
}

void OXClient::WaitFor(OXWindow *w) {
  Window wid;

  //_tip_status = 1;
  if (_tip_timer) {
    delete _tip_timer;
    _tip_timer = NULL;
  }
  wid = w->GetId();
  while (ProcessOneEvent(DestroyNotify, wid));
  _tip_status = 0;
}

void OXClient::WaitForUnmap(OXWindow *w) {
  Window wid;

  //_tip_status = 1;
  //if (_tip_timer) {
  //  delete _tip_timer;
  //  _tip_timer = NULL;
  //  }
  wid = w->GetId();
  while (ProcessOneEvent(UnmapNotify, wid));
  //_tip_status = 0;
}

void OXClient::NeedRedraw(OXWindow *w) {
  w->_needRedraw = True;
  _globalNeedRedraw = True;
}


// Still needs an idle-time event dispatcher...

int OXClient::ProcessOneEvent(int EventType, Window wid) {
  XEvent Event;
  struct timeval now, tmout, *tptr;
  int fd_width, nf;
  const OXSNode *ptr;

  //--- 1: process pending X events...

  if (XPending(_dpy)) {
    XNextEvent(_dpy, &Event);
    if (wid == None) {
      HandleEvent(&Event);
      return True;
    } else {
      HandleMaskEvent(&Event, wid);
      if ((Event.xany.window == wid) &&
          (Event.xany.type == EventType))
        return False;
      else
        return True;
    }
  }

  //--- 2: process pending file events from the previous call

  for (ptr=_iolist->GetHead(); ptr!=NULL; ptr=ptr->next) {
    OFileHandler *fh = (OFileHandler *) ptr->data;
    int fd = fh->_fd;

    if (fh->_eventMask & XCM_READABLE) {
      if (FD_ISSET(fd, &readset)) {
        FD_CLR(fd, &readset);
        fh->_w->HandleFileEvent(fh, XCM_READABLE);
        return True;
      }
    }

    if (fh->_eventMask & XCM_WRITABLE) {
      if (FD_ISSET(fd, &writeset)) {
        FD_CLR(fd, &writeset);
        fh->_w->HandleFileEvent(fh, XCM_WRITABLE);
        return True;
      }
    }

    if (fh->_eventMask & XCM_EXCEPTION) {
      if (FD_ISSET(fd, &excptset)) {
        FD_CLR(fd, &excptset);
        fh->_w->HandleFileEvent(fh, XCM_EXCEPTION);
        return True;
      }
    }

  }

  //--- 3: check whether there are any X events ready...

  fd_width = _x_fd + 1;

  FD_ZERO(&readset);
  FD_SET(_x_fd, &readset);

  tmout.tv_sec = tmout.tv_usec = 0;

  nf = select(fd_width, (FD_MASK *) &readset, NULL, NULL, &tmout);
  if (nf <= 0) {
    FD_ZERO(&readset);
    FD_ZERO(&writeset);
    FD_ZERO(&excptset);
  }

  while (True) {

    if ((nf > 0) || ((nf == -1) && (errno == EINTR))) {
      if (FD_ISSET(_x_fd, &readset)) {
        XNextEvent(_dpy, &Event);
        if (wid == None) {
          HandleEvent(&Event);
          return True;
        } else {
          HandleMaskEvent(&Event, wid);
          if ((Event.xany.window == wid) &&
              (Event.xany.type == EventType))
            return False;
          else
            return True;
        }
      } else {
        // leave the processing of other file events for the
        // next call, otherwise things like pending redraws
        // or timers wont get a chance against continuous file
        // operations...
        return False;
      }
    }

    //--- 4: check timers...

    if (_tlist->GetHead()) {
      OTimer *t = (OTimer *) _tlist->GetHead()->data;

      gettimeofday(&now, NULL);
      if ((t->_tm.tv_sec < now.tv_sec) || 
         ((t->_tm.tv_sec == now.tv_sec) &&
          (t->_tm.tv_usec < now.tv_usec))) {
        UnregisterTimer(t); // _tlist = GetHead()->next;
        // not sure if this belongs here, but...
        if (t == _tip_timer) {
          delete _tip_timer;
          _tip_timer = NULL;
          if ((_tip_status = 2-_tip_status) == 2)
            _tip_frame->ShowTip();
        } else
          t->_w->HandleTimer(t);
        return True;
      }
    }

    //--- 5: do pending redraws if there is nothing else to do...

    if (_DoRedraw()) return True;
    ReapChildren();

    //--- 6: sleep until next event arrives or timeout expires...

    FD_ZERO(&readset);
    FD_ZERO(&writeset);
    FD_ZERO(&excptset);
    FD_SET(_x_fd, &readset);

    fd_width = _x_fd + 1;

    for (ptr=_iolist->GetHead(); ptr!=NULL; ptr=ptr->next) {
      OFileHandler *fh = (OFileHandler *) ptr->data;
      int fd = fh->_fd;

      if (fh->_eventMask & XCM_READABLE) FD_SET(fd, &readset);
      if (fh->_eventMask & XCM_WRITABLE) FD_SET(fd, &writeset);
      if (fh->_eventMask & XCM_EXCEPTION) FD_SET(fd, &excptset);
      if (fd_width <= fd) fd_width = fd + 1;
    }

    if (_tlist->GetHead() == NULL) {
      tptr = NULL;
    } else {
      tptr = &tmout;
      OTimer *t = (OTimer *) _tlist->GetHead()->data;
      tmout.tv_sec  = t->_tm.tv_sec  - now.tv_sec;
      tmout.tv_usec = t->_tm.tv_usec - now.tv_usec;
      if (tmout.tv_usec < 0) {
        tmout.tv_usec += 1000000;
        tmout.tv_sec--;
      }
    }

    nf = select(fd_width, (FD_MASK *) &readset, &writeset, &excptset, tptr);

    if (nf == -1) {
      FD_ZERO(&readset);
      FD_ZERO(&writeset);
      FD_ZERO(&excptset);
    }

  }

  return True;
}

// Check the whole frame list and do all pending redrawings...

int OXClient::_DoRedraw() { 
  const OXSNode *e = _wlist->GetHead();
  OXWindow *w;
 
  if (_globalNeedRedraw == False) return False;
  while (e) {
    w = (OXWindow *) e->data;
    if (w->_needRedraw) {
      w->_DoRedraw();
      w->_needRedraw = False;
    }
    e = e->next;
  }

  _globalNeedRedraw = False;
  return True;
}


int OXClient::HandleEvent(XEvent *Event) {
  OXWindow *w;
  
  if ((w = GetWindowById(Event->xany.window)) == NULL) return False;

  w->HandleEvent(Event);
  return True;
}

int OXClient::HandleMaskEvent(XEvent *Event, Window wid) {
  OXWindow *w, *ptr;

  if ((w = GetWindowById(Event->xany.window)) == NULL) return False;

  // This breaks class member protection, but OXClient is a friend of all
  // classes and _should_ know what to do and what *not* to do...

  for (ptr = w; ptr->_parent != NULL; ptr = (OXWindow *) ptr->_parent)
    if ((ptr->_id == wid) ||
        ((Event->type != ButtonPress) &&
         (Event->type != ButtonRelease) &&
         (Event->type != KeyPress) &&
         (Event->type != KeyRelease) &&
         (Event->type != EnterNotify) &&
         (Event->type != LeaveNotify) &&
         (Event->type != MotionNotify))) {
      w->HandleEvent(Event);
      return True;
    }

  if (Event->type == ButtonPress ||
      Event->type == KeyPress)
    XBell(_dpy, 0);

  return False;
}

OXWindow *OXClient::GetWindowById(Window wid) const {
  OXSNode *e;

  e = _wlist->GetNode(wid);
  if (!e) return NULL;
  return ((OXWindow *) e->data);
}

void OXClient::StartTip(OXFrame * w) {
  if (_tip_status == 2) {
    if (_tip_timer) {
      delete _tip_timer;
      _tip_timer = NULL;
    }
  } else {
    _tip_frame = w;
    if (_tip_timer)
      delete _tip_timer;
    _tip_timer = new OTimer(w, 1500);
  }
}

void OXClient::StopTip(OXFrame * w) {
  if (_tip_status == 2) {
    if (!_tip_timer)
      _tip_timer = new OTimer(w, 200);
  } else if (_tip_timer) {
    delete _tip_timer;
    _tip_timer = NULL;
  }
}
