/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
#include "swindow/sx11/SX11Window.h"
#include "swindow/sx11/SX11Color.h"

#include <stdio.h>

typedef SBinHashtable<Pixmap> SPixmapCache;

/*
 *  These caches hold the clipping pixmap and the real pixmap.
 *  They are always come in pairs. The key is the image id.
 */
SPixmapCache maskCache;
SPixmapCache imageCache;

/*
 * This is true if we actually use the pixmap cache to cahce 
 * the SImage on the X Server itself.
 */
#define SS_IMAGE_SIZE (32*32)
static bool pixmapCacheOn=true;
static unsigned int cacheSize = 10;
static unsigned int cacheCount = 0;
static bool needClear = false;

/**
 * After this size things wont be cached.
 */
void
SX11Window::setPixmapCacheSize(unsigned int size)
{
  cacheSize = size;
}
 
/**
 * turn on/off the cache and clear it
 */
void
SX11Window::setPixmapCacheOn (bool on)
{
  pixmapCacheOn=on;
  needClear = false;
}
/* 
  SGC MAKKA StructureNotifyMask may collide with SubstructureNotifyMask
  for ConfigureNotify   
  SubstructureNotifyMask \
*/

#define SD_YUDIT_EVENT_MASK ( ExposureMask \
  | StructureNotifyMask \
  | ButtonPressMask \
  | ButtonReleaseMask \
  | Button1MotionMask \
  | Button2MotionMask \
  | Button3MotionMask \
  | KeyPressMask \
  | KeyReleaseMask \
  | KeymapStateMask \
  | PropertyChangeMask \
  | EnterWindowMask \
  | LeaveWindowMask \
  | FocusChangeMask)

SX11Window::SX11Window(const SString& n, SX11Impl* _impl, long _id) : name (n),
 inputMethod (_impl, _id, SD_YUDIT_EVENT_MASK),
 background (SX11Color(_impl, SS_WORD32(0xffffffff))),
 pen (SColor(0), SColor(0xffffffff))
{
  modalFlag = false;
  xpos = SD_WIN_X;
  ypos = SD_WIN_Y;
  xwidth = SD_WIN_W;
  yheight = SD_WIN_H;

  isCacheOn = true;
  impl = _impl;
  dbindex = 0;

  isChild = 0;
  mapNotified = false;
  exposedOnce = false;
  dbuffer[0].id = _id;
  dbuffer[1].id = None;

  dbuffer[0].gcFD = 0;

  dbuffer[0].gcFG = (unsigned long) pen.getForeground().getValue();
  impl->addWindow (dbuffer[0].id, this);
  XGCValues   gcv;
  gcv.foreground = SX11Color(impl, pen.getForeground()).getPixelValue();
  gcv.background = background.getPixelValue();
  clipped = false;

  dbuffer[0].gc = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
    GCForeground | GCBackground, &gcv); 

  dbuffer[0].gc_default = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
      0, &gcv);

  gcv.function = GXand;
  dbuffer[0].gc_draw_and = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
    GCForeground | GCBackground | GCFunction, &gcv); 

  gcv.function = GXor;
  dbuffer[0].gc_draw_or = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
     GCForeground | GCBackground | GCFunction, &gcv); 

  setBackground (background);
  engine = 0;
  shown = false;
  XSelectInput (impl->display, (Window) dbuffer[0].id, SD_YUDIT_EVENT_MASK);
}

SX11Window::~SX11Window()
{
  freeDoubleBuffer ();
  XFreeGC (impl->display, dbuffer[0].gc);
  XFreeGC (impl->display, dbuffer[0].gc_default);
  XFreeGC (impl->display, dbuffer[0].gc_draw_and);
  XFreeGC (impl->display, dbuffer[0].gc_draw_or);
  impl->deleteWindow (dbuffer[0].id, this);
  if (engine) delete engine;
}

void
SX11Window::show ()
{
  impl->show (dbuffer[0].id);
  shown = true;
}

void
SX11Window::hide ()
{
  impl->hide (dbuffer[0].id);
  shown = false;
}

/**
 * Drawing routines inherited from SCanvas
 */
bool
SX11Window::beginImage (double _x, double _y, const SString& _id, const SColor& background)
{
  pen = SPen(pen.getForeground(), background, pen.getLineWidth());
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::newpath called without beginDoubleBuffer\n");
    /* this will always return false */
    return engine->beginImage ((int)_x, (int)_y, "");
  }
  if (engine ==0) engine = new SGEngine();
  if (isCacheOn)
  {
    return engine->beginImage ((int)_x, (int)_y, _id);
  }
  else
  {
    /* this will always return false */
    return engine->beginImage ((int)_x, (int)_y, "");
  }
}
void
SX11Window::newpath ()
{
  if (engine==0) return;
    return engine->newpath ();
}
void
SX11Window::fill (const SPen& _pen) {
  if (engine==0) return;
  if (pen != _pen)
  {
     pen = _pen;
  }
  engine->fill (0, 0, getWidth(), getHeight(), pen);
}

#define SS_SHADING_COLORS (SD_OVERSAMPLE * SD_OVERSAMPLE +1)
#define SS_DOUBLE_SCAN 1

/**
 * fill and return the resulting image for better caching.
 */
void
SX11Window::endImage ()
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::fill called without beginDoubleBuffer\n");
    return;
  }
  if (engine==0) return;
  SImage* si= engine->endImage ();
  if (si==0) return; /* offscreen */

  /* Use the putimage that does some caching. */
  if (pixmapCacheOn)
  {
    putImage (si->getOrigoX(), si->getOrigoY(), *si);
    delete si;
    return;
  }

  int imageWidth = (int) si->getWidth();
  int imageHeight = (int) si->getHeight();

  SS_WORD32* image = new SS_WORD32[imageWidth*imageHeight];
  int bits = DefaultDepth (impl->display, impl->screen);

  XImage* ximage = XCreateImage (impl->display, impl->visual,
     bits, (bits > 1? ZPixmap: XYBitmap), 0, (char*) image,
     imageWidth, imageHeight, 32, 0);

  int shades = si->getShades();
  SX11Color** colors  = new SX11Color*[shades];
  CHECK_NEW (colors);
  
  int i;
  /*
   * We could blend it with the current background, 
   * but we would lose a lot of speed...
   */
  for (i=0; i<shades; i++)
  {
      SColor bg (pen.getBackground());
      SColor fg (pen.getForeground().red,
        pen.getForeground().green, 
        pen.getForeground().blue,
        (unsigned char) (i * 255 /(shades-1)));
      bg.blend (fg);

      colors[i] = new SX11Color (impl, bg);
  } 

  /*
   * we are getting and putting data on the same area
   */

  /* 
   *  Here comes Gaspar's version of 
   * "Poor man's transparency" It depends on believing that
   *  background of the window in the region is really pen.getBackground()
   *  If it is not true you should get the image yourself.
   */

  /* First wipe out the shape */
  for (int y=0; y<imageHeight; y++)
  {
    for (int x=0; x<imageWidth; x++)
    {
      SS_WORD32 sh = si->getShade (x, y);
      SS_WORD32  rpvle = (sh==0) ? 0xffffff : 0;
      XPutPixel (ximage, x, y, rpvle); 
    }
  }
  XPutImage (impl->display, (Drawable) dbuffer[dbindex].id, 
     dbuffer[dbindex].gc_draw_and, 
     ximage, 0, 0, si->getOrigoX(), si->getOrigoY(), 
     imageWidth, imageHeight);

  /* Then blot the image */
  for (int y=0; y<imageHeight; y++)
  {
    for (int x=0; x<imageWidth; x++)
    {
      if (shades > 0) {
        SS_WORD32 sh = si->getShade (x, y);
        SS_WORD32  rpvle = (sh==0) ? 0 : colors[si->getShade (x, y)]->getPixelValue();
        XPutPixel (ximage, x, y, rpvle); 
      } else {
        SColor scolor(si->getShade (x, y));
        SColor bg (pen.getBackground());
        bg.blend (scolor);
        SS_WORD32  rpvle = SX11Color(impl, bg).getPixelValue();
        XPutPixel (ximage, x, y, rpvle); 
      }
    }
  }

  XPutImage (impl->display, (Drawable) dbuffer[dbindex].id, 
     dbuffer[dbindex].gc_draw_or, ximage, 0, 0,
     si->getOrigoX(), si->getOrigoY(), 
     imageWidth, imageHeight);

  for (i=0; i<shades; i++)
  {
     delete colors[i];
  }
  delete colors;
  delete si;
  XDestroyImage (ximage);
}

/**
 * FIXME:
 * This method is not implemented 
 */
void
SX11Window::stroke (const SPen& _pen)
{
  if (engine ==0) return;
  if (pen != _pen)
  {
     pen = _pen;
  }
  engine->stroke(0,0, getWidth(),  getHeight(), pen);
}

/**
 * Move to a new point
 * This will clear the path and push 3 element-pairs 
 * one is the bounding low, second is bounding high 
 * third is the new coord.
 */
void
SX11Window::moveto (double x, double y)
{
  if (engine ==0) return;
  engine->moveto (x, y);
}

/**
 * The lowest level function to add a new element
 */
void
SX11Window::lineto (double x, double y)
{
  if (engine ==0) return;
  engine->lineto (x, y);
}

/** 
 *  Draw a cubic beizer curve
 */
void
SX11Window::curveto (double _x0, double _y0, double _x1, 
  double _y1, double _x2, double _y2)
{
  if (engine ==0) return;
  engine->curveto (_x0, _y0, _x1, _y1, _x2, _y2);
}

void
SX11Window::closepath()
{
  if (engine ==0) return;
  engine->closepath();
}

/**
 * put an image to the scene. originX and OriginY is ignored.
 * This version is using full pixmaps for speed.
 * @param x is the x corner of the image.
 * @param y is the y corner of the image.
 * @param image is the actual image.
 */
void 
SX11Window::putImage (int _x, int _y, const SImage& im)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::putImage called without beginDoubleBuffer\n");
    return;
  }
  if (cacheCount > cacheSize)
  {
      //fprintf (stderr, "SX11Window:: clearing cache (%u elements)\n",
      //    cacheCount);
      needClear = true;
  }
  if (needClear && cacheCount!=0)
  {
     unsigned int i;
     unsigned int j;
     for (i=0; i<imageCache.size(); i++)
     {
       for (j=0; j<imageCache.size(i); j++)
       {
         Pixmap e = imageCache.get (i, j);
         if (e != 0) XFreePixmap (impl->display, e);
       }
     }
     for (i=0; i<maskCache.size(); i++)
     {
       for (j=0; j<maskCache.size(i); j++)
       {
         Pixmap e = maskCache.get (i, j);
         if (e != 0) XFreePixmap (impl->display, e);
       }
     }
     needClear = false;
     imageCache.clear();
     maskCache.clear();
     cacheCount = 0;
  }

  char a[64];
  const SColor& cf = pen.getForeground();
  const SColor& cb = pen.getBackground();
  a[0] = 'i';
  a[1] = 'm';
  a[2] = (char) cf.red;
  a[3] = (char) cf.green;
  a[4] = (char) cf.blue;
  a[5] = (char) cb.red;
  a[6] = (char) cb.green;
  a[7] = (char) cb.blue;
  SString key (a, 8);
  key.append ((const SString&)im.getID());

  Pixmap mask = maskCache.get (key);
  Pixmap pixmap = imageCache.get (key);

  bool createMask = (mask==0);
  bool createPixmap = (pixmap==0);

//fprintf (stderr, "createPixmap=%d %*.*s\n", (int)createPixmap, SSARGS(key));

  /* Pixmap is always added, mask is on-demand */
  if (createPixmap)
  {
    if (im.getShades() == 0)
    {
      createColoredPixmap (im, &pixmap, (createMask) ? &mask : 0);
    }
    else
    {
      createShadedPixmap (im, &pixmap, (createMask) ? &mask : 0);
    }
#if 0
    SString id = key;
fprintf (stderr, "New Map...");
    for (unsigned int i=0; i<id.size(); i++)
    {
      fprintf (stderr, "%02X ", (unsigned int) ((unsigned char)id[i]));
    }
fprintf (stderr, "\n");
#endif
  }
  else
  {
//fprintf (stderr, "Using cache X11\n");
  }
  XCopyArea(impl->display, mask, (Drawable)dbuffer[dbindex].id, 
     dbuffer[dbindex].gc_draw_and, 0, 0, 
     im.getWidth(), im.getHeight(), _x, _y);
  XCopyArea(impl->display, pixmap, (Drawable)dbuffer[dbindex].id, 
    dbuffer[dbindex].gc_draw_or, 0, 0, 
    im.getWidth(), im.getHeight(), _x, _y);

  /* This should have the foreground and background in it */
  /* We dont want to cache big ones */
  if (pixmapCacheOn && (im.getWidth() * im.getHeight()) / SS_IMAGE_SIZE < 10)
  {
    if (createPixmap)
    {
      imageCache.put (key, pixmap);
//Pixmap pixmap2 = imageCache.get (key);
//fprintf (stderr, "addPixmap=%lx %*.*s\n", (unsigned long)pixmap2, SSARGS(key));
      //cacheCount += im.getWidth() * im.getHeight() / SS_IMAGE_SIZE;
      cacheCount++;
      //fprintf (stderr, "cacheCount=%u cacheSize=%u\n", cacheCount, cacheSize);
    }
    if (createMask)
    {
      maskCache.put (key, mask);
    }
  }
  else
  {
    if (createPixmap)
    {
      XFreePixmap (impl->display, pixmap);
    }
    if (createMask)
    {
      XFreePixmap (impl->display, mask);
    }
  }
}

/**
 * Create two pixmaps, one for the image and one for the colored ones.
 * We use the same depth to avoid XCopyPlanes to gain speed
 * @param p is the image
 * @param m is the shape image. 
 */

/* unsigned char in color is less that this -> transparent */

void
SX11Window::createColoredPixmap (const SImage& im, Pixmap* p, Pixmap* m)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::createColoredPixmap called without beginDoubleBuffer\n");
    return;
  }
  int imageWidth = (int) im.getWidth();
  int imageHeight = (int) im.getHeight();

  SS_WORD32* image = new SS_WORD32[imageWidth*imageHeight];
  int bits = DefaultDepth (impl->display, impl->screen);

  XImage* ximage = XCreateImage (impl->display, impl->visual,
     bits, (bits > 1? ZPixmap: XYBitmap), 0, (char*) image,
     imageWidth, imageHeight, 32, 0);

  /* First wipe out the shape */
  if (m != 0)
  {
    for (int y=0; y<imageHeight; y++)
    {
      for (int x=0; x<imageWidth; x++)
      {
        SColor c (im.getShade (x, y));
        SS_WORD32  rpvle = (c.alpha == 0) ? 0xffffff : 0x0;
        XPutPixel (ximage, x, y, rpvle); 
      }
    }

    Pixmap xshape = XCreatePixmap (impl->display, 
      (Drawable) dbuffer[dbindex].id, 
      im.getWidth(), im.getHeight(), bits);
    XPutImage (impl->display, xshape, dbuffer[dbindex].gc_default, 
       ximage, 0, 0, 0, 0, 
      im.getWidth(), im.getHeight());
    *m = xshape;
  }

  /* Then blot the image */
  for (int y=0; y<imageHeight; y++)
  {
    for (int x=0; x<imageWidth; x++)
    {
      SColor c (im.getShade (x, y));
      SColor bg (pen.getBackground());
      //if (c.alpha != 0) bg.blend (c);
      bg.blend (c);
      // CHANGED 2020-05-24
      SS_WORD32  rpvle = (c.alpha == 0) 
        ? 0 : SX11Color(impl, bg).getPixelValue();
      XPutPixel (ximage, x, y, rpvle); 
    }
  }

  Pixmap xpixmap = XCreatePixmap (impl->display, (Drawable)dbuffer[dbindex].id, 
     im.getWidth(), im.getHeight(), bits);

  XPutImage (impl->display, xpixmap, dbuffer[dbindex].gc_default, 
      ximage, 0, 0, 0, 0, 
     im.getWidth(), im.getHeight());

  XDestroyImage (ximage);
  *p = xpixmap;
}

/**
 * Create two pixmaps, one for the image and one for the shade.
 * We use the same depth to avoid XCopyPlanes to gain speed
 * @param p is the image
 * @param m is the shape image. it contains 1's where there is no image.
 */
void
SX11Window::createShadedPixmap (const SImage& im, Pixmap* p, Pixmap* m)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::createShadedPixmap called without beginDoubleBuffer\n");
    return;
  }
  int imageWidth = (int) im.getWidth();
  int imageHeight = (int) im.getHeight();

  SS_WORD32* image = new SS_WORD32[imageWidth*imageHeight];
  int bits = DefaultDepth (impl->display, impl->screen);

  XImage* ximage = XCreateImage (impl->display, impl->visual,
     bits, (bits > 1? ZPixmap: XYBitmap), 0, (char*) image,
     imageWidth, imageHeight, 32, 0);

  int i;

  /* First wipe out the shape */
  if (m != 0)
  {
    for (int y=0; y<imageHeight; y++)
    {
      for (int x=0; x<imageWidth; x++)
      {
        SS_WORD32 sh = im.getShade (x, y);
        SS_WORD32  rpvle = (sh==0) ? 0xffffff : 0x0;
        XPutPixel (ximage, x, y, rpvle); 
      }
    }
    Pixmap xshape = XCreatePixmap (impl->display, 
      (Drawable) dbuffer[dbindex].id, 
      im.getWidth(), im.getHeight(), bits);

    XPutImage (impl->display, xshape, dbuffer[dbindex].gc_default, 
      ximage, 0, 0, 0, 0, 
      im.getWidth(), im.getHeight());
    *m = xshape;
  }

  int shades = im.getShades();
  SX11Color** colors  = new SX11Color*[shades];
  CHECK_NEW (colors);
  /*
   * We could blend it with the current background, 
   * but we would lose a lot of speed...
   */
  for (i=0; i<shades; i++)
  {
      SColor bg (pen.getBackground());
      //SGC SColor bg (SColor("#808080"));
      SColor fg (pen.getForeground().red,
        pen.getForeground().green, 
        pen.getForeground().blue,
        (unsigned char) (i * 255 /(shades-1)));
      bg.blend (fg);

      colors[i] = new SX11Color (impl, bg);
  } 


  /* Then blot the image */
  for (int y=0; y<imageHeight; y++)
  {
    for (int x=0; x<imageWidth; x++)
    {
      SS_WORD32 sh = im.getShade (x, y);
      SS_WORD32  rpvle = (sh==0) ? 0 : colors[im.getShade (x, y)]->getPixelValue();
      XPutPixel (ximage, x, y, rpvle); 
    }
  }

  Pixmap xpixmap = XCreatePixmap (impl->display, 
     (Drawable) dbuffer[dbindex].id, 
     im.getWidth(), im.getHeight(), bits);

  XPutImage (impl->display, xpixmap, dbuffer[dbindex].gc_default, 
     ximage, 0, 0, 0, 0, 
     im.getWidth(), im.getHeight());

  for (i=0; i<shades; i++)
  {
     delete colors[i];
  }
  delete colors;

  XDestroyImage (ximage);
  *p = xpixmap;
}

/**
 * @param im is the image
 * @param p where the image will be put.
 * @param m is the shape bitmap. 
 */
void
SX11Window::createMaskedPixmap (const SImage& im, Pixmap* p, Pixmap* m)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::createMaskedPixmap called without beginDoubleBuffer\n");
    return;
  }
  int imageWidth = (int) im.getWidth();
  int imageHeight = (int) im.getHeight();

  /* First wipe out the shape */
  if (m != 0)
  {
    Pixmap xshape = XCreatePixmap (impl->display, 
      (Drawable) dbuffer[dbindex].id, 
       im.getWidth(), im.getHeight(), 1);
    XGCValues   gcv;
    gcv.background = 0x0;
    gcv.foreground = 0x0;
    GC sgc = XCreateGC (impl->display, xshape, GCForeground
       | GCBackground, &gcv); 
    XFillRectangle (impl->display, xshape, sgc, 0, 0, imageWidth, imageHeight);
    XSetForeground (impl->display, sgc, 0x1);

    for (int y=0; y<imageHeight; y++)
    {
      for (int x=0; x<imageWidth; x++)
      {
        SColor c (im.getShade (x, y));
        if (c.alpha != 0)
        {
          XDrawPoint (impl->display, xshape, sgc, x, y);
        }
      }
    }
    *m = xshape;
    XFreeGC (impl->display, sgc);
  }

  int bits = DefaultDepth (impl->display, impl->screen);

  SS_WORD32* image = new SS_WORD32[imageWidth*imageHeight];

  XImage* ximage = XCreateImage (impl->display, impl->visual,
     bits, (bits > 1? ZPixmap: XYBitmap), 0, (char*) image,
     imageWidth, imageHeight, 32, 0);

  /* Then blot the image */
  for (int y=0; y<imageHeight; y++)
  {
    for (int x=0; x<imageWidth; x++)
    {
      SColor c (im.getShade (x, y));
      SColor bg (pen.getBackground());
      if (c.alpha != 0) bg.blend (c);
      SS_WORD32  rpvle = SX11Color(impl, bg).getPixelValue();
      XPutPixel (ximage, x, y, rpvle); 
    }
  }

  Pixmap xpixmap = XCreatePixmap (impl->display, 
     (Drawable) dbuffer[dbindex].id, 
     im.getWidth(), im.getHeight(), bits);

  XPutImage (impl->display, xpixmap, dbuffer[dbindex].gc_default, 
     ximage, 0, 0, 0, 0, 
     im.getWidth(), im.getHeight());

  XDestroyImage (ximage);
  *p = xpixmap;
}

void
SX11Window::setBackground(const SColor &color)
{
  SX11Color c(impl, color);
  background = c;
  XSetWindowBackground (impl->display, (Window)dbuffer[0].id, 
     background.getPixelValue());
  pen = SPen(pen.getForeground(), color, pen.getLineWidth());
}

/**
 * TODO: not implemented 
 */
void
SX11Window::rotate (double angle)
{
  if (engine ==0) engine = new SGEngine();
  engine->rotate (angle);
}

void
SX11Window::scale (double x, double y)
{
  if (engine ==0) engine = new SGEngine();
//fprintf (stderr, "scaling %g %g\n", x, y);
  engine->scale (x, y);
}

void
SX11Window::translate (double x, double y)
{
  if (engine ==0) engine = new SGEngine();
  engine->translate (x, y);
}

void
SX11Window::pushmatrix()
{
  if (engine ==0) engine = new SGEngine();
  engine->pushmatrix();
}

void
SX11Window::popmatrix()
{
  if (engine ==0) engine = new SGEngine();
  engine->popmatrix();
}

//extern const char* debugComponent;

void
SX11Window::resize (unsigned int _width, unsigned int _height)
{
  //fprintf (stderr, "resizing from: %lx   %u %u to %u %u\n",
  //(unsigned long) this, xwidth, yheight, _width, _height);
//if (debugComponent) fprintf (stderr, "XResizeWindow %lx: %s %d,%d -> %d,%d\n", 
 //   getWindowID(), debugComponent, getWidth(), getHeight(), _width, _height);

  if (getWidth() == _width && getHeight() == _height) return;
  if (_width == 0) _width = 1;
  if (_height == 0) _height = 1;
  XResizeWindow (impl->display, (Window) dbuffer[0].id, _width, _height);
  setSize(_width, _height);
}

void
SX11Window::move (int _x, int _y)
{
//if (debugComponent) fprintf (stderr, "XMoveWindow %lx: %s %d,%d -> %d,%d\n", 
 //   getWindowID(), debugComponent, getPositionX(), getPositionY(), _x, _y);

  // Commented because XMoveWindow sometimes fails. 
  if (getPositionX() == _x && getPositionY() == _y) return;
/*
if (getHeight() == 65) {
  fprintf (stderr, "SGC XMoveWindow %lx  size-%d,%d  xy= %d,%d\n", 
    getWindowID(), getWidth(), getHeight(), _x, _y);
}
*/
  XMoveWindow (impl->display, (Window) dbuffer[0].id, _x, _y);
  setPosition(_x, _y);
}
/**
 * Draw a native font to the screen. This will not delete existing data. 
 * @param x is the origo position 
 * @param y is the origo position 
 * @param native is X11 FID
 * @param data is a XChar2b structure
 * @param len is the unber of chars
 */
void
SX11Window::bitfont (const SPen& _pen, double x, double y, 
  void* native, char* data, unsigned int len)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::bitfont called without beginDoubleBuffer\n");
    return;
  }
  if (dbuffer[dbindex].gcFD != (unsigned long) native)
  {
    XSetFont (impl->display, dbuffer[dbindex].gc, (Font) native);
    dbuffer[dbindex].gcFD  = (unsigned long) native;
  }
  if (dbuffer[dbindex].gcFG != (unsigned long) _pen.getForeground().getValue())
  {
    XSetForeground (impl->display, dbuffer[dbindex].gc,
       SX11Color(impl, _pen.getForeground()).getPixelValue());
    dbuffer[dbindex].gcFG = (unsigned long) _pen.getForeground().getValue();
  }
  XDrawString16 (impl->display, (Drawable) dbuffer[dbindex].id, 
    dbuffer[dbindex].gc, (int)x, (int)y, 
    (XChar2b*) data, len/2);
}

void
SX11Window::setParent (SWindow* w, int x, int y)
{
  //fprintf (stderr, "reparent %lx\n", (unsigned long) this);
  SX11Window* x11w = (SX11Window*) w;
  XReparentWindow (impl->display, (Window) dbuffer[0].id, (Window) 
      x11w->dbuffer[0].id, x, y);
  isChild  = true;
}

/**
 * Clear a region (set it to the background)
 * @param x is the upper left corner
 * @param y is the upper top corner
 * @param width is the width of the region to clear
 * @param height is the height of the region to clear
 */
void
SX11Window::clear (int x, int y, unsigned int width, unsigned int height)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::clear called without beginDoubleBuffer\n");
    return;
  }
  bitfill (background, x, y, width, height);
/* This one does not takes clip */
/*
  // does not work with pixmaps
  XClearArea (impl->display, (Window) id, x, y, width, height, 0);
*/
}
/**
 * Copy an area on the window to another area.
 * overlap is ok.
 * @param x is the upper left corner
 * @param y is the upper top corner
 * @param width is the width of the region to copy
 * @param height is the height of the region to copy
 * @param tox is the destination left corner
 * @param toy is the destination top corner
 */
void
SX11Window::copy (int x, int y, unsigned int width, unsigned int height, 
  int tox, int toy)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::copy called without beginDoubleBuffer\n");
    return;
  }
  XCopyArea (impl->display, (Drawable) dbuffer[dbindex].id, 
     (Drawable) dbuffer[dbindex].id, 
      dbuffer[dbindex].gc, x, y, width, height, tox, toy);
  impl->moveRedrawEvent (dbuffer[0].id, tox-x, toy-y);
}

/**
 * Fill a solid rectangle
 * @param x is the upper left corner
 * @param y is the upper top corner
 * @param width is the width of the region to fill
 * @param height is the height of the region to fill
 */
void
SX11Window::bitfill (const SColor& bg, int x, int y, 
 unsigned int width, unsigned int height)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::bitfill called without beginDoubleBuffer\n");
    return;
  }
  if (dbuffer[dbindex].gcFG != (unsigned long)  bg.getValue())
  {
    XSetForeground (impl->display, dbuffer[dbindex].gc, 
       SX11Color(impl, bg).getPixelValue());
    dbuffer[dbindex].gcFG = (unsigned long)  bg.getValue();
  }
  XFillRectangle (impl->display, (Drawable) dbuffer[dbindex].id, 
       dbuffer[dbindex].gc, x, y, width, height);
}

/**
 * Draw a solid line.
 * @param x is the starting x point
 * @param y is the starting y point
 * @param x is the ending non-exclusive  x point
 * @param y is the ending non-exclusive  y point
 */
void
SX11Window::bitline (const SColor& fg, int x, int y, int tox, int toy)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::bitline called without beginDoubleBuffer\n");
    return;
  }
  if (dbuffer[dbindex].gcFG != (unsigned long)  fg.getValue())
  {
    XSetForeground (impl->display, dbuffer[dbindex].gc, 
         SX11Color(impl, fg).getPixelValue());
    dbuffer[dbindex].gcFG = (unsigned long)  fg.getValue();
  }
  XDrawLine (impl->display, (Drawable) dbuffer[dbindex].id, dbuffer[dbindex].gc, x, y, tox, toy);
}

/**
 * Draw a solid point.
 * @param x is the x point
 * @param y is the y point
 */
void
SX11Window::bitpoint (const SColor& fg, int x, int y)
{
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::bitpoint called without beginDoubleBuffer\n");
    return;
  }
  if (dbuffer[dbindex].gcFG != (unsigned long)  fg.getValue())
  {
    XSetForeground (impl->display, dbuffer[dbindex].gc, SX11Color(impl, fg).getPixelValue());
    dbuffer[dbindex].gcFG = (unsigned long)  fg.getValue();
  }
  XDrawPoint (impl->display, (Drawable) dbuffer[dbindex].id, dbuffer[dbindex].gc, x, y);
}

void
SX11Window::bitpoints (const SColor& fg, const int* _x, const int* _y, 
         unsigned int _size)
{
  if (_size==0) return;
  if (dbuffer[dbindex].id == None)
  {
    fprintf (stderr, "SX11Window::bitpoints called without beginDoubleBuffer\n");
    return;
  }
  if (dbuffer[dbindex].gcFG != (unsigned long)  fg.getValue())
  {
    XSetForeground (impl->display, dbuffer[dbindex].gc, 
       SX11Color(impl, fg).getPixelValue());
    dbuffer[dbindex].gcFG = (unsigned long)  fg.getValue();
    // If I don't do XSync there is a point drawing error of 
    // highlighted text on Matrox Video card with X64
    // XSync (impl->display, False);
  }
  XPoint* p = new XPoint[_size];
  CHECK_NEW (p);
  for (unsigned int i=0; i<_size; i++)
  {
     p[i].x = _x[i];
     p[i].y = _y[i];
  }
  XDrawPoints (impl->display, (Drawable) dbuffer[dbindex].id, 
      dbuffer[dbindex].gc, p, _size, CoordModeOrigin);
  delete p;
}


void
SX11Window::getKeyboardFocus()
{
  impl->getKeyboardFocus (this);
  XSetInputFocus (impl->display, (Window) dbuffer[0].id, 
      RevertToNone, CurrentTime);
  XIC xic = inputMethod.getIC();
  if (xic)
  {
    XSetICFocus (xic);
  }
  return;
}

long
SX11Window::getID ()
{
  return dbuffer[0].id;
}

/**
 * Assign a rectangualr clip area. Everything outside this area will be clipped.
 */
void
SX11Window::setClippingArea (int _x, int _y, unsigned int _width, unsigned int _height)
{
  // called outside dbufer begin and end
  if (dbuffer[dbindex].id == None)
  {
    clipRegion = SX11Region (_x, _y, _width, _height);
    return;
  }
  XRectangle rect;
  rect.x = _x; rect.y = _y; rect.width = _width; rect.height = _height;
  XSetClipRectangles (impl->display, dbuffer[dbindex].gc, 0, 0, &rect, 1, Unsorted);
  XSetClipRectangles (impl->display, dbuffer[dbindex].gc_draw_and, 0, 0, &rect, 1, Unsorted);
  XSetClipRectangles (impl->display, dbuffer[dbindex].gc_draw_or, 0, 0, &rect, 1, Unsorted);
  clipped = true;
}

/**
 *  clear the clipping area.
 */
void
SX11Window::removeClippingArea ()
{
  // called outside dbufer begin and end
  if (dbuffer[dbindex].id == None) return;

  if (clipped)
  {
     XSetClipMask (impl->display, dbuffer[dbindex].gc, None);
     XSetClipMask (impl->display, dbuffer[dbindex].gc_draw_and, None);
     XSetClipMask (impl->display, dbuffer[dbindex].gc_draw_or, None);
     clipped = false;
  }
}

/**
 * This routine is not supposed to be used extensively. This is
 * to check the current matrix.
 */
SS_Matrix2D
SX11Window::getCurrentMatrix() const
{
  if (engine!=0) return engine->getCurrentMatrix();
  return SS_Matrix2D();
}

// TODO: implement this.
void 
SX11Window::setApplicationImage (const SImage& im)
{
  Pixmap pixmap = 0;
  Pixmap mask = 0;
  unsigned int dbindexSave = dbindex;
  dbindex = 0;
  createMaskedPixmap (im, &pixmap, &mask); 
  dbindex = dbindexSave;
  //XWMHints *hints = XGetWMHints(impl->display, (Window) id);
  XWMHints *hints = XAllocWMHints();
  if (!pixmap ||!mask || !hints)
  {
    return;
  }
  hints->flags |= IconPixmapHint;
  hints->icon_pixmap = pixmap;
  hints->flags |= IconMaskHint;
  hints->icon_mask = mask;
  XSetWMHints(impl->display, (Window) dbuffer[0].id, hints); 
  XFree (hints);
}

/**
 * This requests a redraw, efficiently after all events got processed.
 * This is called by the client and not by SX11Impl.
 * @param clear is true if the window needs to be cleared before calling redraw.
 * @param x is the x origin of the event
 * @param y is the y origin of the event
 * @param width is the width of the event
 * @param height is the height of the event
 */
void
SX11Window::redraw (bool clear, int _x, int _y, unsigned int _width, unsigned int _height)
{
  if (_x+(int)_width  < 0 || _y + (int) _height < 0)
  {
    return;
  }
  if (_x  > (int)getWidth() || _y > (int)getHeight())
  {
    return;
  }
  impl->addRedrawEvent (dbuffer[0].id, 
      SRedrawEvent (clear, _x, _y, _width, _height)); 
}

/**
 * Start a native input method.
 * @param name is the name of the input method:
 *  like "kinput2"
 * @param properties provide some attributes to the input method.
 */
bool
SX11Window::startInputMethod (const SString& _name, const SProperties& prop,  SPreEditor* preEditor)
{
  return inputMethod.start (_name, prop);
}

void
SX11Window::stopInputMethod ()
{
  inputMethod.stop();
}
/**
 * Change properties of the input method on the fly.
 * @param prop contains properties like:
 * InputStyle: root over-the-spot off-the-spot
 */
void
SX11Window::setInputMethodProperties (const SProperties& prop)
{
  inputMethod.setProperties(prop);
}

/**
 * Get the current input method.
 * it returns a zero sized string if input method is not started.
 */
SString
SX11Window::getInputMethod ()
{
  return SString(inputMethod.getName());
}

bool
SX11Window::hasStatusArea () {
  return inputMethod.hasStatusArea ();
}

bool
SX11Window::isVisible ()
{
#if 0
  /* Oh man this did not work at all . I left here to draw lessons */
  XWindowAttributes   xwa;
  if (!XGetWindowAttributes (impl->display, (Window) id, &xwa))
  {
    return false;
  }
  XWindowAttributes   txwa;
  txwa.map_state = xwa.map_state;
  Window tw = impl->getTopLevelWindow ((Window) id);
  if (tw != (Window) id) 
  {
    XGetWindowAttributes (impl->display, (Window) tw, &txwa);
  }
  return (xwa.map_state == IsViewable && txwa.map_state == IsViewable);
#endif
  return mapNotified;
}

#if 0
  RETIRED 2001-11-21

/**
 * draw a polygon on the screen with bits.
 */
void
SX11Window::bitpolygon (const SColor& fg, const SV_INT& polygon)
{
  if (gcFG != (unsigned long)  fg.getValue())
  {
    XSetForeground (impl->display, gc, SX11Color(impl, fg).getPixelValue());
    gcFG = (unsigned long)  fg.getValue();
  }
  XPoint* points = new XPoint[polygon.size()/2];
  for (unsigned int i=0; i<polygon.size(); i=i+2)
  {
    points[i/2].x = polygon[i];
    points[i/2].y = polygon[i+1];
  }

  XFillPolygon (impl->display, (Window) id, gc, points, polygon.size()/2, 
     Convex, CoordModeOrigin);
  delete [] points;
}

#endif

/**
 * Get an utf8 encoded text from clipboard.
 */
SString
SX11Window::getClipUTF8(bool isPrimary)
{
  return impl->getClipUTF8 (dbuffer[0].id, isPrimary);
}

/**
 * put and utf8-encoded text to clipboard.
 */
void
SX11Window::putClipUTF8(const SString& utf8, bool isPrimary)
{
  impl->putClipUTF8 (dbuffer[0].id, utf8, isPrimary);
}

/**
 * Fixme.
 */
void
SX11Window::setMinimumSize (unsigned int _width, unsigned int _height)
{
//fprintf (stderr, "in -------------minimumsize %u %u\n", _width, _height);
  XSizeHints  sizeHints;
  sizeHints.min_width = _width;
  sizeHints.min_height = _height;
  sizeHints.flags = PMinSize;
  XSetWMProperties (impl->display, (Window) dbuffer[0].id, 0, 0, 0, 0, &sizeHints, 0, 0);
}

void
SX11Window::setTitle (const SString& title)
{
  SString windowName = title;
  windowName.append((char)0);

  XTextProperty   propertyIconName;
  XTextProperty   propertyWindowName;

  XStoreName (impl->display, (Window) dbuffer[0].id, windowName.array());

  /* Discard const */
  char* wnc = (char*) windowName.array();
#ifdef X_HAVE_UTF8_STRING
  if (Xutf8TextListToTextProperty (impl->display, &wnc, 1, XUTF8StringStyle, &propertyWindowName)==Success &&
     Xutf8TextListToTextProperty (impl->display, &wnc, 1, XUTF8StringStyle, &propertyIconName)==Success)
#else
  if (XStringListToTextProperty (&wnc, 1, &propertyWindowName)!=0 &&
     XStringListToTextProperty (&wnc, 1, &propertyIconName)!=0)
#endif
  {
    XSetWMProperties (impl->display, (Window) dbuffer[0].id,
       &propertyWindowName, &propertyIconName,
        0, 0, // argv, argc,
        0, 0, 0);
        XFree (propertyWindowName.value);
        XFree (propertyIconName.value);
  }

  XSetWMProtocols (impl->display, (Window) dbuffer[0].id, &impl->wmDeleteWindow, 1);
}

/**
 * Do nothing special here.
 */
bool
SX11Window::cacheOn (bool on)
{
  bool old = isCacheOn;
  isCacheOn = on;
  return old;
}

/**
 * add and remove keyboard accelerator
 */
void
SX11Window::addAccelerator (const SAccelerator& a, SAcceleratorListener* l)
{
  accelerators.put (a.toString(), l);
  impl->addAccelerator (dbuffer[0].id, a);
}

void
SX11Window::removeAccelerator (const SAccelerator& a, SAcceleratorListener* l)
{
  accelerators.remove (a.toString());
  impl->removeAccelerator (dbuffer[0].id, a);
}

void
SX11Window::_acceleratorPressed (const SAccelerator& a)
{
  SAcceleratorListener* l = accelerators.get (a.toString());
  if (l==0)
  {
    fprintf (stderr, "SX11Window::_acceleratorPressed - can not find accelerator listener.\n");
    return;
  }
  l->acceleratorPressed (a);
}
void
SX11Window::_acceleratorReleased (const SAccelerator& a)
{
  SAcceleratorListener* l = accelerators.get (a.toString());
  if (l==0)
  {
    fprintf (stderr, "SX11Window::_acceleratorPressed - can not find accelerator listener.\n");
    return;
  }
  l->acceleratorReleased (a);
}

/**
 * Sets the targets that can be dropped on this window
 */
void
SX11Window::setDroppable (const SStringVector& _targets)
{
  const Atom xdndVersion = 3;
  XChangeProperty (impl->display, (Window) dbuffer[0].id, 
     XInternAtom (impl->display, "XdndAware", False), XA_ATOM, 32,
     PropModeReplace, (unsigned char*) &xdndVersion, 1);
  for (unsigned int i=0; i<_targets.size(); i++)
  {
    targetAtoms.append (impl->toAtom(_targets[i]));
  }
  targets = _targets;
  //fprintf (stderr, "SX11Window::setDroppable\n");
}

const SStringVector&
SX11Window::getDroppable() const
{
  return targets;
}

void
SX11Window::setModal (SWindow* _parent, bool decorated)
{
  XSetWindowAttributes winattr;
  /* CHECKME */
  modalFlag = true;
  winattr.override_redirect = True;
  winattr.save_under = True;
  if (_parent == 0)
  {
    XSetTransientForHint (impl->display, (Window) dbuffer[0].id, (Window)impl->root);
  }
  else
  {
    XSetTransientForHint (impl->display, (Window) dbuffer[0].id, (Window)
     (((SX11Window*)_parent)->dbuffer[0].id));
  }
  if (decorated) return;
  XChangeWindowAttributes (impl->display, (Window) dbuffer[0].id,
       CWOverrideRedirect | CWSaveUnder, &winattr);
}
/**
 * put this window in the middle
 */
void
SX11Window::center (SWindow* _window)
{
  Window cw = impl->root;
  if (_window!=0)
  {
    cw = (Window) ((SX11Window*)_window)->dbuffer[0].id;
    cw = impl->getTopLevelWindow (cw);
    
  }
  /* center this in cw */
  Window root_return;
  int x_return, y_return;
  unsigned int width_return, height_return;
  unsigned int border_width_return;
  unsigned int depth_return;

  if (!XGetGeometry (impl->display, cw, 
     &root_return, &x_return, &y_return, 
     &width_return, &height_return, &border_width_return,&depth_return))
  {
    return;
  }
#if 0
  fprintf (stderr, "x=%d, y=%d, w=%d, h=%d  mine %u %u %ld\n", 
        x_return, y_return, 
        width_return, height_return,
        width, height,
        (long)cw);
#endif
  int lx = (x_return + (int)width_return/2);  
  int ly = (y_return + (int)height_return/2);  

  int mx = lx - (int) getWidth()/2;
  int my = ly - (int) getHeight()/2;

  if ((int)impl->rootWidth < (int) getWidth() + mx + 20)
  {
    mx = (int)impl->rootWidth - (int)getWidth() - 20;
  }
  if ((int)impl->rootHeight < (int) getHeight() + my + 20)
  {
    my = (int)impl->rootHeight - (int)getHeight() - 20;
  }
  if (mx<0) mx = 0;
  if (my<0) my = 0;
  move (mx, my);
}

/**
 * wait till window is mapped and unmapped
 */
void
SX11Window::wait ()
{
  /* x may have some stuff sucked in */
//  int lcount=0;
  impl->doXLoop();
  /* This is in a job and the job is removed already */
  SJob* sjob = new SJob();
  // SGC BUG in dialogs same job removed.
  //SEventHandler::addJob (impl->job, impl);
  SEventHandler::addJob (sjob, impl);
  while (shown && SEventHandler::next())
  {
//    fprintf (stderr, "NEXT=%d\n", lcount++);
  }
  SEventHandler::remove (sjob);
  delete sjob;
}

XIC
SX11Window::getIC ()
{
  return inputMethod.getIC();
}
const SString&
SX11Window::getICEncoding () const
{
  return inputMethod.getICEncoding();
}

bool
SX11Window::isAsciiInput ()
{
  return inputMethod.isAsciiInput();
}
bool
SX11Window::isKInput ()
{
  return inputMethod.isKInput();
}

unsigned long
SX11Window::getWindowID() const
{
  return (unsigned long) dbuffer[0].id;
}

void
SX11Window::setDoubleBuffer (bool isOn)
{
  
  dbindex = isOn ? 1 : 0;
}

bool
SX11Window::isDoubleBufferEnabled () const
{
  return (dbindex == 1);
}

void
SX11Window::freeDoubleBuffer ()
{
  if (dbuffer[1].id != None)
  {
    XFreeGC (impl->display, dbuffer[1].gc);
    XFreeGC (impl->display, dbuffer[1].gc_default);
    XFreeGC (impl->display, dbuffer[1].gc_draw_and);
    XFreeGC (impl->display, dbuffer[1].gc_draw_or);
    XFreePixmap (impl->display, (Pixmap) dbuffer[1].id);
    dbuffer[1].id = None;
  }
}

// called before global redraw to sync backing buffer with window.
void
SX11Window::beginDoubleBuffer (const SX11Region& region)
{
  if (dbindex == 0) return;
  // Free Up Previous pixmap if 
  if (dbuffer[1].id)
  {
    fprintf (stderr, "SX11Window::beginDoubleBuffer nested call?");
    freeDoubleBuffer ();
  }

  dbuffer[1].gcFD = 0; // Font

  unsigned int pw = (getWidth() == 0) ? 1 : getWidth();
  unsigned int ph = (getHeight() == 0) ? 1 : getHeight();

  int bits = DefaultDepth (impl->display, impl->screen);
  dbuffer[1].id = XCreatePixmap (impl->display, (Window) dbuffer[0].id, 
    pw,  ph, bits);

  XGCValues   gcv;
  gcv.foreground = SX11Color(impl, pen.getForeground()).getPixelValue();
  gcv.background = background.getPixelValue();
  clipped = false;
  
  dbuffer[1].gc = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
    GCForeground | GCBackground, &gcv); 
  
  dbuffer[1].gc_default = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
      0, &gcv);
  
  gcv.function = GXand;
  dbuffer[1].gc_draw_and = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
    GCForeground | GCBackground | GCFunction, &gcv); 
  
  gcv.function = GXor;
  dbuffer[1].gc_draw_or = XCreateGC (impl->display, (Drawable)dbuffer[0].id, 
       GCForeground | GCBackground | GCFunction, &gcv); 

  XSetForeground (impl->display,  dbuffer[1].gc, 
     SX11Color(impl, background).getPixelValue());
  dbuffer[1].gcFG = (unsigned long)  background.getValue();

  XFillRectangle (impl->display, (Drawable) dbuffer[1].id, 
      dbuffer[1].gc, region.x, region.y, region.width,  region.height);
  if (clipped)
  {
    XRectangle rect;
    rect.x = clipRegion.x;
    rect.y = clipRegion.y;
    rect.width = clipRegion.width;
    rect.height = clipRegion.height;
    XSetClipRectangles (impl->display, dbuffer[1].gc, 0, 0, &rect, 1, Unsorted);
    XSetClipRectangles (impl->display, dbuffer[1].gc_draw_and, 0, 0, &rect, 1, Unsorted);
    XSetClipRectangles (impl->display, dbuffer[1].gc_draw_or, 0, 0, &rect, 1, Unsorted);
  }
}

void
SX11Window::endDoubleBuffer (const SX11Region& region)
{
  if (dbindex == 0)
  {
     if (dbuffer[1].id != None) freeDoubleBuffer ();
     return;
  }
  if (dbuffer[1].id == 0)
  {
    fprintf (stderr, "SX11Window::endDoubleBuffer nested call?");
    return;
  }
  XCopyArea (impl->display, (Drawable) dbuffer[1].id, 
    (Drawable)dbuffer[0].id, dbuffer[dbindex].gc_default, region.x, region.y, 
    region.width, region.height, region.x, region.y);
  freeDoubleBuffer ();
}
