/** 
 *  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.
 */

#define SS_YUDIT_DIALOG_STYLE \
     (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_CLIPCHILDREN)

#define SS_YUDIT_TOPLEVEL_STYLE \
     (WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN)

#define SS_YUDIT_CHILD_STYLE \
      (WS_CHILD | WS_CLIPCHILDREN)

/* 
 * You might want to comment this out to build on Wndows CE
 * Having it makes it more responsive, event based.
 * it needs ws2_32.lib
 */
 
#include "stoolkit/SExcept.h"
#include "stoolkit/SEvent.h"
#include "stoolkit/SString.h"
#include "stoolkit/SUtil.h"
#include "stoolkit/SStringVector.h"
#include "stoolkit/SBinHashtable.h"
#include "swindow/SAwt.h"
#include "swindow/SGEngine.h"
#include "swindow/swin32/SWin32.h"
#include "swindow/SRedrawEvent.h"


#include <sys/types.h>

#include <winsock2.h>

#include <time.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <imm.h>
#undef WIN32_LEAN_AND_MEAN 

#include <stdio.h>


#ifdef USE_WINAPI
/* SEventBSD hookup hack */
int main(int argc, char* argv[]);
#endif

extern int (*_windowsSelectHookup)(int readSize, fd_set *ro, 
  int writeSize, fd_set *wo, int exceptSize, fd_set *ex,
  struct timeval* t);

static int
winSelectHack (int readSize, fd_set *ro, int writeSize, fd_set *wo,
  int exceptSize, fd_set *ex, struct timeval* t);

static int
winWineHack (int readSize, fd_set *ro, int writeSize, fd_set *wo,
  int exceptSize, fd_set *ex, struct timeval* t);


class SDoubleBuffer 
{
public:
  SDoubleBuffer (HWND _id, HDC _gotDC, 
     const SColor& background,
     int _x, int _y, 
     unsigned int _w, unsigned int _h);
  void copyToScreen  (HWND _id, HDC _gotDC);
  ~SDoubleBuffer ();

  HDC     bitmapHDC;
  HBITMAP bitmap; 

  int x;
  int y;
  unsigned int width;
  unsigned int height;
};


/**
 * Clipboard Stuff.
 */
static UINT UTF8_STRING = 0;
SV_UCS4 clipData;

typedef struct _KeyData
{
  SWindowListener::SKey key;
  bool ctrl0;
  bool ctrl1;
  bool meta0;
  bool meta1;
  bool shift0;
  bool shift1;
} KeyData;

static int putClipText();
static int putClipUnicodeText();
static int putClipUtf8Text();
static void notifyClip ();
static void processKey (KeyData* kd, bool syskey, int keycod, bool isdown);
static void sendKeyReleased (KeyData* kd, SW32Window* wn, SWindowListener* ln);
static void sendKeyChar (KeyData* kd, const SString& s);

static bool sendAcceleratorPressed (int key, bool ctrl, bool shift, bool meta);
static bool sendAcceleratorReleased ();

/**
 * @author: Gaspar Sinai <gaspar@yudit.org>
 * @version: 2000-04-23
 * This is the abstract widget toolkit
 */
static bool needClear = false;

SBinHashtable<HBRUSH> brushes;
SBinHashtable<HPEN> pens;

SBinHashtable<unsigned int> minimumSizesX;
SBinHashtable<unsigned int> minimumSizesY;

static HPEN getBitPen (const SColor& clr);
static HBRUSH getSolidBrush (const SColor& clr);

static long currentTopFocusWindow=0;
static long currentFocusWindow=0;

/* There is only one context */
HINSTANCE instance;
HINSTANCE pinstance;
LPSTR cmdLine;
int cmdShow;
HANDLE accel;
/* always reset it on colormap change */
static HDC compatibleDC = 0;
static HBITMAP compatibleHBitmap = 0;

static bool winOK = false;

typedef SBinHashtable<SW32Window*> SWindowHashtable;
typedef SBinHashtable<SWindowListener*> SListenerHashtable;

static SWindowHashtable windowHashtable;
static SListenerHashtable listenerHashtable;

static void createShadedBitmap (const SPen& pen, const SImage& im, 
  HBITMAP* p, HBITMAP* m, HDC odc, HDC dc);
static void createColoredBitmap (const SPen& pen, const SImage& im,
  HBITMAP* p, HBITMAP* m, HDC odc, HDC dc);

static long clipboardOwner=0;

char* windowName = "Yudit";
typedef SHashtable<SRedrawEvent> SRedrawEventTable;

int shownWindows = 0;
long buttonFlags[3];

static SW32Window* getToplevelWindow (SW32Window* w);
static SW32Window* getToplevelWindow (long id);

/*--------------------------------------------------------------------------
 * You would not need this if win98 could create more than 500 bitmaps.
 * START
 *-------------------------------------------------------------------------*/
SDoubleBuffer::SDoubleBuffer (HWND _id, HDC _gotDC, 
     const SColor& background, int _x, int _y, 
     unsigned int _w, unsigned int _h)
{
  HDC winDC = (_gotDC == 0) ? GetDC(_id) : _gotDC;

  bitmapHDC = CreateCompatibleDC (winDC);
//  fprintf (stderr, "bitmapHDC=%u\n", (unsigned int) bitmapHDC);

  x = _x; y = _y;
  width = _w; height = _h;

  if (width == 0) width = 1;
  if (height == 0) height = 1;

  // can't use bitmapHDC, don't ask me why
  bitmap = CreateCompatibleBitmap(winDC, x+width, y+height);
  SelectObject(bitmapHDC, bitmap);
  if (_gotDC == 0) ReleaseDC (_id, winDC);

  if (bitmap == 0)
  {
    fprintf (stderr, "Bitmap is null...\n");
  }
  // Clear the bitmap with background
  RECT rect; rect.left = x; rect.top = y;
  rect.right = x + (int) width; rect.bottom = y + (int) height;
  HBRUSH brush = getSolidBrush (background);
  int mode = SetMapMode (bitmapHDC, MM_TEXT);
  FillRect (bitmapHDC, &rect,  brush);
  SetMapMode (bitmapHDC, mode);
  // brush is cached, dont delete it
}

void
SDoubleBuffer::copyToScreen  (HWND _id, HDC _gotDC)
{
  HDC winDC = (_gotDC == 0) ? GetDC(_id) : _gotDC;

  // SelectClipRgn ((HDC)bitmapHDC, (HRGN) 0);
  // SelectClipRgn (winDC, (HRGN) 0);
  BitBlt (winDC, x, y, width, height, bitmapHDC, x, y, SRCCOPY);

  if (_gotDC == 0) ReleaseDC (_id, winDC);
}

SDoubleBuffer::~SDoubleBuffer ()
{
  DeleteDC (bitmapHDC);
  DeleteObject (bitmap);
}
class SBitmapItem
{
public:
  HBITMAP bitmap;
  HDC     dc;
  int x;
  int y;
  int width;
  int height;
};

class SBitmapArea 
{
public:
  SBitmapArea  (unsigned int _xy) {
    xy = _xy;
    bitmap = 0;
    cursor = 0;
    size = 0;
    ison = false;
    dc = 0;
  }
  void  clear () {
    /* delete dc first. always bitmap might be selected...*/
    if (dc) DeleteDC (dc);
    if (bitmap) DeleteObject (bitmap);
    dc = 0;
    bitmap = 0;
    cursor = 0;
  }
  void  setOn (bool _ison) {
    clear();
    ison = _ison;
  }
  void  setSize (int _size) {
    clear();
    unsigned long l = ss_sqrtlong ((unsigned long) _size);
    size = (int) (l);
    /* limit size 4 Megs */
    if (xy > 2 && size * xy > 2000) size = 2000/xy;
  }
  int put (const SString& key, SBitmapItem* item, SString* old);
private:
  bool    ison;
  int     xy;
  int     size;
  int     cursor;
  HBITMAP bitmap;
  HDC     dc;
  SStringVector keys;
};

/**
 * Put a new item in cache. 
 * @return -1 if old data was replaced 
 * 0 if it was not successful
 * positive if it suceesed.
 * it also sets origox, origoy
 */
int
SBitmapArea::put (const SString& key, SBitmapItem* item, SString* old)
{
  /* create compatible bitmap */
  if (!ison) return 0;
  if (!item->dc) return 0;
  if (!item->bitmap) return 0;
  if (bitmap == 0)
  {
    if (size<2) return 0;
    bitmap = CreateCompatibleBitmap (item->dc, size*xy, size*xy);
    if (bitmap == 0)
    {
      fprintf (stderr, "could not create bitmap dime=%dx%d size=%d\n",
             size * xy, size * xy, xy);
      return 0;
    }
    dc = CreateCompatibleDC(item->dc);
    if (dc == 0)
    {
      fprintf (stderr, "could not create dc\n");
      return 0;
    }
    SelectObject (dc, bitmap);
    cursor = 0;
    keys.clear();
  }
  if (dc == 0)
  {
    return 0;
  }
  if (cursor >= size * size) 
  {
    cursor = 0;
  }
  int ret = 1;
  if (keys.size() > cursor)
  {
    ret = -1;
    old->append (keys[cursor]);
    keys.replace (cursor, key);
  }
  else
  {
    keys.append (key);
  }
  SelectObject (item->dc, item->bitmap);
  int ypos = xy * (cursor/size);
  int xpos = xy * (cursor%size);

  BitBlt (dc, xpos, ypos, item->width, item->height, 
      item->dc, 0, 0, SRCCOPY);

  item->bitmap = bitmap;
  item->dc = dc;
  item->x = xpos;
  item->y = ypos;
  cursor++;
  return ret;
}

typedef SBinHashtable<SBitmapItem*> SBitmapHash;

/**
 * This object re-uses a big bitmap area.
 */
class SBitmapCache
{
public:
  SBitmapCache (void);
  SBitmapItem* get (const SString& key) {
    SBitmapItem* it =  (SBitmapItem*) cache.get (key);
    return it;
  }
  void put (const SString& key, const SBitmapItem& item);
  void  setOn (bool ison) {
    area16.setOn (ison); area32.setOn (ison);
    area64.setOn (ison); area128.setOn (ison);
    iscaching = true;
    clear();
  }
  bool  isOn () {
    return iscaching;
  }
  void clear();
  void  setSize (int size) {
    area16.setSize (size); area32.setSize (size);
    area64.setSize (size); area128.setSize (size);
    clear();
  }
private:
  bool iscaching;
  SBitmapHash cache;
  SBitmapArea area16;
  SBitmapArea area32;
  SBitmapArea area64;
  SBitmapArea area128;
};

void
SBitmapCache::clear()
{
  area16.clear (); area32.clear ();
  area64.clear (); area128.clear ();
  for (unsigned int i=0; i<cache.size(); i++)
  {
    for (unsigned int j=0; j<cache.size(i); j++)
    {
       SBitmapItem* it = cache.get(i,j);
       if (it) delete it;
    }
  }
  cache.clear();
}

SBitmapCache::SBitmapCache (void) :area16(16),area32(32),area64(64),area128(128)
{
  iscaching = false;
}

/**
 * Put item to cache.
 * item bitmap and dc will not be touched. bitmap may get selected
 * in dc. 
 */
void
SBitmapCache::put (const SString& key, const SBitmapItem& _item)
{
  if (!_item.bitmap) return;
  if (!iscaching)
  {
     return;
  }
  if (cache.get (key) || _item.height > 128 || _item.width > 128)
  {
     return;
  }
  SBitmapItem * item = new SBitmapItem();
  item->bitmap = _item.bitmap;
  item->dc = _item.dc;
  item->width = _item.width;
  item->height = _item.height;
  int ret = 0;
  SString old;
  if (item->height > 64 || item->width > 64)
  {
    ret = area128.put (key, item, &old);
  }
  else if (item->height > 32 || item->width > 32)
  {
    ret = area64.put (key, item, &old);
  }
  else if (item->height > 16 || item->width > 16)
  {
    ret = area32.put (key, item, &old);
  }
  else 
  {
    ret = area16.put (key, item, &old);
  }
  /* replaced */
  if (ret < 0)
  {
    SBitmapItem* bold = cache.get (old);
    if (bold)
    {
      cache.remove (old);
      delete bold;
    }
  }
  /* can not use it */
  if (ret == 0) 
  {
    delete item;
    return;
  }
  /* dont delete item - reused */
  cache.put (key, item);
  return;
}


SBitmapCache imageCache;
SBitmapCache maskCache;


/*--------------------------------------------------------------------------
 * You would not need this if win98 could create more than 500 bitmaps.
 * END
 *-------------------------------------------------------------------------*/

class SWHandler : public SEventTarget
{
public:
  SWHandler(unsigned int msec, bool iswine);
  ~SWHandler();
  virtual bool done(const SEventSource* s);
  virtual bool timeout(const SEventSource* s);
  void addRedrawEvent (long id, const SRedrawEvent& evt);
  void moveRedrawEvent (long id, int xoffset, int yoffset);
  bool doWin32();
  bool doWin32Loop();
  SRedrawEventTable  redrawEventTable;
  SJob* job;
  STimer* timer;
private:
};


/**
 * The command line -wine flag make this work in wine.
 * @param msec is zero for event based version. or >= 1 for wine.
 * @param iswine is true if we use just a Sleep in event loop.
 *  this happens because there is no socket in wine. 
 *  if iswine is specified msec can not be zero.
 */
SWHandler::SWHandler(unsigned int msec, bool iswine)
{
  job = new SJob();
  if (msec!=0)
  {
    timer = new STimer(msec);
    SEventHandler::addTimer(timer, this);
    if (iswine)
    {
       _windowsSelectHookup = winWineHack; 
      fprintf (stderr, "Hooked up 'Sleep' event handler for wine.\n");
    }
    fprintf (stderr, "Timer is set to %d msecs.\n", msec);
  }
  else
  {
//    fprintf (stderr, "Waiting for (I think ON is a better word) multiple objects. Throw 'em at me!\n");
    _windowsSelectHookup = winSelectHack; 
    timer = 0;
  }
  SEventHandler::addJob(job, this);
  UTF8_STRING = RegisterClipboardFormat ("UTF8_STRING");
}

SWHandler::~SWHandler()
{
  if (job) delete job;
  if (timer) delete timer;
}

bool
SWHandler::done(const SEventSource* s)
{
  if (!winOK)
  {
    return false;
  }

  /* we dont get a notice so better hurry up and process all messages. */
  doWin32Loop();

  if (redrawEventTable.size()==0) return false;
  /* this is the fast serving of collapsing events */
  /* we request redraw events only after all events are processed */
  SRedrawEventTable t;
  /* redraw block */
  do {
    t.clear();
    t = redrawEventTable;
    redrawEventTable.clear();
    for (unsigned int i=0; i<t.size(); i++)
    {
      for (unsigned int j=0; j<t.size(i); j++)
      {
        const SRedrawEvent* evt = t.get (i, j);
        if (evt == 0) continue;
        SString sid = t.key (i, j);
        long wid = sid.longValue();
        SW32Window* swid = (SW32Window*) windowHashtable.get(wid);
        if (swid == 0)
        {
          fprintf (stderr, "Window %ld not found.\n", wid);
          continue;
        }
        SWindowListener* li = listenerHashtable.get(swid->getID());
        if (li == 0)
        {
          fprintf (stderr, "Window listener for %ld not found.\n", wid);
          continue;
        }

        // swid->dbuffer IS 0
        if (swid->dbufferOn && swid->dbuffer == 0)
        {
          swid->dbuffer = (evt->width == 0 || evt->height == 0)
            ? new SDoubleBuffer ((HWND) swid->id, 0, 
               swid->background, 0, 0, 
               swid->getWidth(), swid->getHeight())
            : new SDoubleBuffer ((HWND) swid->id, 0, 
               swid->background, evt->x, evt->y, evt->width, evt->height);
        }
        /* FIXME : if window is not yet visible continue */
        if (swid->dbuffer == 0 && 
	    evt->clear && evt->width > 0 && evt->height > 0)
        {
          swid->repaintBackground (
            evt->x, evt->y,
            evt->x + evt->width, evt->y + evt->height);
        }
        // fprintf (stderr, "deliver RedrawEvent %ld.\n", wid);
        if (evt->width == 0 || evt->height == 0)
        {
          li->redraw (swid, 0, 0, swid->getWidth(), swid->getHeight());
        }
        else
        {
          li->redraw (swid, evt->x, evt->y, evt->width, evt->height);
        }
        if (swid->dbuffer)
        {
           ((SDoubleBuffer*) swid->dbuffer)->copyToScreen (
               (HWND) swid->id, 
               (HDC)  0);
           delete (SDoubleBuffer*) swid->dbuffer;
           swid->dbuffer = 0;
        }
      }
    }
  } while (doWin32Loop());
  SEventHandler::addJob(job, this);

  return false;
}

/**
 * Process X11 events in a loop
 * @return true if at least one event was found.
 */
bool 
SWHandler::doWin32Loop()
{
  bool done1 = false;
  while (doWin32())
  {
     done1 = true;
  } 
  return  done1;
}

/**
 * Process one message.
 * @return true if at least one message was processed.
 */
bool
SWHandler::doWin32()
{
  MSG msg;
  if (!PeekMessage(&msg ,0 , 0, 0, PM_REMOVE)) return false;
  do {
    //if (!TranslateAccelarator(hwnd, accel,&msg)
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  } while (PeekMessage(&msg ,0 , 0, 0, PM_REMOVE));
  return true;
}

/**
 * Add a collapsing window redraw.
 * @param id is the id of the window.
 * @param evt is the event.
 */
void
SWHandler::addRedrawEvent (long id, const SRedrawEvent& evt)
{
  SVector <SRedrawEvent> save;
  SRedrawEvent newEvt (evt);
  const SRedrawEvent* old = 0;
  while ((old = redrawEventTable.get (id)))
  {
    if (newEvt.merge (*old))
    {
      redrawEventTable.remove (id);
      break;
    }
    save.append (*old);
    redrawEventTable.remove (id);
  }
  /* put back */
  for (unsigned int i=0; i<save.size(); i++)
  {
    redrawEventTable.put (id, save[i], false);
  }
  redrawEventTable.put (id, newEvt, false);
}

/**
 * If there is a redraw event associated with this id, move it.
 * @param xoffset will be added to y
 * @param yoffset will be added to x
 */
void
SWHandler::moveRedrawEvent (long id, int xoffset, int yoffset)
{
  SVector <SRedrawEvent> save;
  const SRedrawEvent* old = 0;
  while ((old=redrawEventTable.get (id)))
  {
    save.append (*old);
    redrawEventTable.remove (id);
  }
  //fprintf (stderr, "moveRedrawEvent %u\n", save.size());
  for (unsigned int i=0; i<save.size(); i++)
  {
    SRedrawEvent newEvt (save[i]);
    newEvt.x += xoffset;
    newEvt.y += yoffset;
    redrawEventTable.put (id, newEvt, false);
  }
}

bool
SWHandler::timeout(const SEventSource* s)
{
  return true;
}

LRESULT CALLBACK _eventHandler(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);


SWHandler* handler=0;


static void buttonEnter (long hwnd);
static void buttonPressed (long wid, int button, int x, int y);
static void buttonDragged (long wid, int button, int x, int y);
static void buttonReleased (long wid, int button, int x, int y);
static void lostCapture (long wid);
static void processMouseWheel (long hwnd, int increment);

/**
 * This is where we start off
 */
int APIENTRY
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
  buttonFlags[0] = 0;
  buttonFlags[1] = 0;
  buttonFlags[2] = 0;

  winOK = true;
  instance = hInstance;
  pinstance = hPrevInstance;
  cmdLine = lpszCmdLine;
  cmdShow = nCmdShow;

  WNDCLASS wcl;
  wcl.hInstance = instance;
  wcl.lpszClassName=windowName;
  wcl.lpfnWndProc = _eventHandler;
  wcl.style = CS_CLASSDC | CS_HREDRAW | CS_VREDRAW | CS_PARENTDC;
  //wcl.style = CS_CLASSDC | CS_HREDRAW | CS_VREDRAW;
  //wcl.hIcon = LoadIcon(0, IDI_APPLICATION);
  //wcl.hIcon = LoadIcon(instance, MAKEINTRESOURCE(1));
  wcl.hIcon = LoadIcon(instance, MAKEINTRESOURCE(1));
  wcl.hCursor=LoadCursor(0, IDC_ARROW);
  wcl.lpszMenuName = 0;
  wcl.cbClsExtra = 0;
  wcl.cbWndExtra = 0;
  //wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
  //wcl.hbrBackground = GetSysColorBrush(WHITE_BRUSH);
  //wcl.hbrBackground = GetSysColorBrush(WHITE_BRUSH);
  wcl.hbrBackground = 0;
  //wcl.hbrBackground = GetSysColorBrush(COLOR_BACKGROUND);
  //wcl.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
  if (!RegisterClass(&wcl)) return 0;

  SString cl;
  WCHAR* _cl = GetCommandLineW();
  if (_cl != 0) {
    SString u16fn ((const char*) _cl, (unsigned int) 2 * wcslen (_cl));
    SEncoder u8("utf-8");
    SEncoder u16("utf-16-le");
    cl = u8.encode (u16.decode (u16fn));
  }
  cl.replaceAll ("\\", "/");
  SStringVector l;
  l.smartSplit (cl);
  int argc = 0;
  unsigned int  msecs = 0;
  bool iswine = false;
  bool  debug = false;
  char **argv = new char*[l.size()+1];
  for (unsigned int i=0;i<l.size(); i++)
  {
    SString s = l[i];
    if (s == "-debug")
    {
       debug = true;
       continue;
    }
    if (s == "-wine")
    {
       iswine = true;
       continue;
    }
    if (s == "-timer" && i+1<l.size())
    {
       i++;
       SString s1 = l[i];
       s1.append ((char)0);
       sscanf (s1.array(), "%u", &msecs);
       continue;
    }
    argv[argc++] = s.cString();
  }
  if (debug)
  {
     FILE* mystderr = freopen ("log.txt", "w", stderr);
     //if (mystderr) stderr = mystderr;
  }
  if (iswine && msecs == 0)
  {
     fprintf (stderr, "Warning: -wine flag needs -timer msec\n");
  }
  argv[argc] = 0;
  accel = LoadAccelerators(instance, "Main");
  int ret;
  HWND hwnd = GetClipboardOwner ();
  if (OpenClipboard (hwnd))
  {
     UINT format = 0;
//      fprintf (stderr, "clipboard format start %d\n", hwnd);
     while (format=EnumClipboardFormats (format))
     {
       char buff[128];
       buff[127] = 0;
       GetClipboardFormatName (format, buff, 127);
//       fprintf (stderr, "clipboard format[%u]=[%s]\n", format, buff);
     }
//      fprintf (stderr, "clipboard format end\n");
     CloseClipboard ();
  }
  else
  {
    fprintf (stderr, "clipboard format error\n");
  }
  {
    SWHandler h(msecs, iswine);
    handler = &h;
    ret=main (argc, argv);
    handler = 0;
  }
  for (unsigned int j=0;j<argc; j++)
  {
    delete argv[j];
  }
  delete argv;
  fclose (stderr);
  return ret;
}


SW32Impl::SW32Impl()
{
}

SW32Impl::~SW32Impl()
{
}

bool
SW32Impl::isOK()
{
  return winOK;
}

SEncoder impEncoder;
void
SW32Impl::setEncoding(const SString& str)
{
  SEncoder enc = SEncoder (str);
  if (!enc.isOK())
  {
    fprintf (stderr, "SWin32 clipboard encoder `%*.*s' unknown\n", 
      SSARGS(str));
  }
  else
  {
    encoder = enc;
    impEncoder = enc;
  }
}

SWindow*
SW32Impl::getWindow (SWindowListener* l, const SString& name)
{
  if (!isOK()) return 0;
  char* nm=name.cString();
  HWND w = CreateWindow(
     windowName, nm,
     SS_YUDIT_TOPLEVEL_STYLE,
     SD_WIN_X, SD_WIN_Y, SD_WIN_W, SD_WIN_H,
     HWND_DESKTOP,
     0,
     instance,
     0);
  delete nm;
  SW32Window* sw = new SW32Window (name, this, (long) w);
  CHECK_NEW (sw);
  listenerHashtable.put ((long) w, l);
  return sw;
}

SW32Window::SW32Window(const SString& n, SW32Impl* i, long _id) 
  : name(n), background ("white"), 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;
  parentID = 0;
  currentFocusWindow = 0;
  shown = false;
  clipRegion = 0;
  engine = 0;
  cdc = 0;
  imname = "";
  impl = i;
  id = _id;
  modalID = 0;
  clipChained = false;
  clipChain = 0;
  dbufferOn = 0;
  dbuffer = 0;
  currentScroll = 0;
  windowHashtable.put (id, this);
}

SW32Window::~SW32Window()
{
  if (dbuffer) delete (SDoubleBuffer*) dbuffer;
  if (engine) delete engine;
  windowHashtable.remove (id);
  listenerHashtable.remove (id);
}

void
SW32Window::show ()
{
  if (!shown) shownWindows++;
  shown = true;
  static bool did=false;
  did = true;

  int dx = 0;
  int dy = 0;
  if (!parentID)
  {
    RECT rect;
    rect.left = getPositionX();
    rect.top = getPositionY();
    rect.right =  rect.left + (int)getWidth();
    rect.bottom =  rect.top + (int)getHeight();
    int style = (modalID) 
        ? SS_YUDIT_DIALOG_STYLE 
        : SS_YUDIT_TOPLEVEL_STYLE;

    if (AdjustWindowRect (&rect, style, false))
    {
      dx = rect.right - (getPositionX() + (int) getWidth());
      dy = rect.bottom - (getPositionY() + (int) getHeight());
      int d2x = (getPositionX() - rect.left);
      int d2y = (getPositionY() - rect.top);
      dx = d2x + dx;
      dy = d2y + dy;
    }
  }

  int posflag =  parentID ? SWP_NOACTIVATE : SWP_NOMOVE;
  if (modalID==0)
  {
     posflag |= SWP_NOZORDER;
  }

  SetWindowPos ((HWND)getID(), (HWND)modalID,
    getPositionX(), getPositionY(), 
    getWidth()+dx, getHeight()+dy,
    posflag | SWP_NOACTIVATE | SWP_NOZORDER
    | SWP_FRAMECHANGED | SWP_SHOWWINDOW);

  if (!parentID)
  {
    //setSize(getWidth() + dx, getHeight()+dy);
    SetActiveWindow ((HWND)modalID);
    BringWindowToTop((HWND)id);
    if (modalID!=0)
    {
      EnableWindow ((HWND)modalID, false);
    }
  }
  
  //ShowWindow((HWND)id, SW_SHOWNORMAL);
  //ShowWindow((HWND)id, did ?SW_SHOW :cmdShow);
  //UpdateWindow((HWND)id);
}

bool
SW32Window::isVisible ()
{
  return IsWindowVisible ((HWND)getID());
}

void
SW32Window::hide ()
{
  if (shown) shownWindows--;
  ShowWindow((HWND)id, SW_HIDE);
  shown = false;
  if (!parentID)
  {
    if (modalID!=0)
    {
      EnableWindow ((HWND)modalID, true);
      SetActiveWindow ((HWND)modalID);
      SetForegroundWindow ((HWND)modalID);
      SW32Window* swid = (SW32Window*) windowHashtable.get(modalID);
      if (swid) 
      {
         SW32Window* top = getToplevelWindow (this);
         SW32Window* foc = (SW32Window*) windowHashtable.get(
               top->currentFocusWindow);
         if (foc) foc->getKeyboardFocus();
      }
      //BringWindowToTop((HWND)modalID);
    }
  }
}

/**
 * This requests a redraw, efficiently after all events got processed.
 * @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
SW32Window::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;
  }
  handler->addRedrawEvent (id, SRedrawEvent (clear, _x, _y, _width, _height)); 
}

/**
 * Reparent the window. 
 * TODO: move it to x y
 * @param p is the parent window
 */
void
SW32Window::setParent (SWindow* p, int x, int y)
{
  SW32Window* otop = getToplevelWindow (this);
  SetParent ((HWND)id, (HWND)((SW32Window*)p)->id);
  SetWindowLong ((HWND)id, GWL_STYLE, SS_YUDIT_CHILD_STYLE);
  parentID = ((SW32Window *)p)->id;
  SW32Window* top = getToplevelWindow (this);
  unsigned int i;
  unsigned int j;
  for (i=0; i<otop->accelerators.size(); i++)
  {
    for (j=0; j<otop->accelerators.size(i); j++)
    {
       SAcceleratorListener* l = otop->accelerators.get (i,j);
       if (l==0) continue;
       const SString& key = otop->accelerators.key (i,j);
       top->accelerators .put (key, l);
    }
  }
  otop->accelerators.clear();

  for (i=0; i<otop->acceleratorTable.size(); i++)
  {
    for (j=0; j<otop->acceleratorTable.size(i); j++)
    {
       long acc = otop->acceleratorTable.get (i,j);
       if (acc==0) continue;
       const SString& key = otop->acceleratorTable.key (i,j);
       top->acceleratorTable .put (key, acc);
    }
  }
  otop->acceleratorTable.clear();
}

void
SW32Window::resize (unsigned int _width, unsigned int _height)
{

  RECT rcClient, rcWindow;
  POINT ptDiff;

  if (GetParent ((HWND)getID()) == 0 && GetClientRect((HWND)getID(), &rcClient) 
    && GetWindowRect((HWND)getID(), &rcWindow))
  {
    // rcClient.left and rcClient.top is 0
    ptDiff.x = (rcWindow.right - rcWindow.left) - (rcClient.right-rcClient.left);
    ptDiff.y = (rcWindow.bottom - rcWindow.top) - (rcClient.bottom-rcClient.top);
    //MoveWindow(hWnd,rcWindow.left, rcWindow.top, 
    //nWidth + ptDiff.x, nHeight + ptDiff.y, TRUE);
    unsigned int w = _width + ptDiff.x;
    unsigned int h = _height+ ptDiff.y;
//    fprintf (stderr, "SetSize=%d %d\n", w, h);
    setSize(_width, _height);
    MoveWindow((HWND)getID(),rcWindow.left, rcWindow.top, w, h, TRUE);
    //SetWindowPos ((HWND)getID(), 0,
    //  getPositionX(), getPositionY(), 
    //  w, h,
    // SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
  }
  else
  {
    if (getWidth() == _width && getHeight() == _height) return;
    setSize(_width, _height);
    SetWindowPos ((HWND)getID(), 0,
      getPositionX(), getPositionY(), 
      getWidth(), getHeight(),
      SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
  }
}

void
SW32Window::move (int _x, int _y)
{
  if (getPositionX() == _x && getPositionY() == _y) return;
  setPosition(_x, _y);
  SetWindowPos ((HWND)getID(), 0,
      getPositionX(), getPositionY(), 
      getWidth(), getHeight(),
      SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}


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

  // Windows98SE
  SString ansi = utf8ToSystem (windowName);
  SetWindowTextA ((HWND)id, ansi.array());
  
  SEncoder u8("utf-8");
  SEncoder u16("utf-16-le");
  SString titleW = u16.encode (u8.decode (windowName));
  SetWindowTextW ((HWND)id, (WCHAR*) titleW.array());
}

KeyData keyData = { 
  SWindowListener::Key_Undefined,
  false, false, false,
  false, false, false
};

LRESULT CALLBACK
_eventHandler (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
  SW32Window* w = 0;
  SWindowListener* l = 0;
  
  switch(message)
  {
  case WM_GETMINMAXINFO:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
   if (w)
   {
     unsigned int minx = minimumSizesX.get((long)hwnd);
     unsigned int miny = minimumSizesY.get((long)hwnd);
     if (minx && miny)
     {
       LPMINMAXINFO info = (LPMINMAXINFO) lParam;
       /* is it enough ? too much trouble getting the client are size ..*/
       info->ptMinTrackSize.x = minx + 10;
       info->ptMinTrackSize.y = miny + 30;
       return 0;
       RECT rect;
       rect.left = 0;
       rect.top = 0;
       rect.right =  minx;
       rect.bottom =  miny;
       int style = (w->modalID) 
           ? SS_YUDIT_DIALOG_STYLE 
           : SS_YUDIT_TOPLEVEL_STYLE;
       if (!AdjustWindowRect (&rect, style, false))
       {
          return 0;
       }
       info->ptMinTrackSize.x = rect.right - rect.left;
       info->ptMinTrackSize.y = rect.bottom - rect.top;
       return 0;
     }
   }
   break;
  case WM_ACTIVATE:
   break; 
  case WM_KILLFOCUS:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
   if (w)
   {
      SW32Window* top = getToplevelWindow (w);
      long oldT = currentTopFocusWindow;
      long oldW = currentFocusWindow;
      currentTopFocusWindow = 0;
      currentFocusWindow = 0;
      if (oldW != 0)
      {
        SW32Window* wo = windowHashtable.get (oldW);
        SWindowListener* lo = listenerHashtable.get (oldW);
        if (wo && lo)
        {
          sendKeyReleased(&keyData, wo, lo);
          lo->lostKeyboardFocus(wo);
        }
      }
      return 0;
   }
   break;
  case WM_SETFOCUS:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
   if (w)
   {
      SW32Window* top = getToplevelWindow (w);
      long oldT = currentTopFocusWindow;
      long oldW = currentFocusWindow;

      currentTopFocusWindow = top->getID();
      currentFocusWindow = top->currentFocusWindow;

      /* if we changed window inside top, generate lost and gained event  */
      if (oldW != currentFocusWindow)
      {
         SW32Window* wo = windowHashtable.get (oldW);
         SWindowListener* lo = listenerHashtable.get (oldW);
         if (wo && lo)
         {
            sendKeyReleased(&keyData, wo, lo);
            lo->lostKeyboardFocus(wo);
         }

         SW32Window* wn = windowHashtable.get (currentFocusWindow);
         SWindowListener* ln = listenerHashtable.get (currentFocusWindow);
         if (wn && ln) ln->gainedKeyboardFocus(wo);
      }
      return 0;
   }
   break;
  case WM_NCCALCSIZE:
   /* forget it. */
   break;
  case WM_RENDERALLFORMATS:
    putClipText();
    putClipUnicodeText();
    putClipUtf8Text();
    return 0;
  case WM_RENDERFORMAT:
    if ((UINT)wParam == UTF8_STRING)
    {
      return putClipUtf8Text();
    }
    else if ((UINT)wParam == CF_UNICODETEXT)
    {
      return putClipUnicodeText();
    }
    else if ((UINT)wParam == CF_TEXT)
    {
      return putClipText();
    }
    break;
  case WM_DRAWCLIPBOARD:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    if (w!=0 && w->clipChain != 0)
    {
       SendMessage ((HWND)w->clipChain,  message, wParam, lParam);
    }
    notifyClip();
    return 0;
  case WM_CHANGECBCHAIN:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    /* repair chain - next is gone */
    if (w!=0 && (long) wParam == w->clipChain)
    {
      w->clipChain = (long) lParam; 
    }
    else if (w!=0 && w->clipChain != 0)
    {
      SendMessage ((HWND)w->clipChain,  message, wParam, lParam);
    }
    return 0;
  case WM_PAINT:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    /* TODO: might be faster with addRedrawingEvent */
    if (w)
    {
       if (GetUpdateRect (hwnd, 0, true))
       {
          PAINTSTRUCT pstr;
          HDC dc = BeginPaint (hwnd, &pstr);
          if (dc == 0) break ;

          if (w->cdc != 0)
          {
             //fprintf (stderr, "PAINT: WON'T...");
          }
          else
          {
#define SD_YUDIT_EVENT_COMPRESSION 1
#if SD_YUDIT_EVENT_COMPRESSION
           /* Windows has crappy event compression - yudit uses its own */
            handler->addRedrawEvent (w->getID(), 
               SRedrawEvent (pstr.fErase, 
                 pstr.rcPaint.left, pstr.rcPaint.top,
                  pstr.rcPaint.right-pstr.rcPaint.left,
                  pstr.rcPaint.bottom-pstr.rcPaint.top));
#else
            w->cdc = dc;

        // w->dbuffer IS 0
        if (w->dbufferOn && w->dbuffer == 0)
        {
          w->dbuffer = ( pstr.rcPaint.bottom == pstr.rcPaint.top 
                 || pstr.rcPaint.left == pstr.rcPaint.right)
            ? new SDoubleBuffer ((HWND) w->id, (HDC) w->cdc, 
               w->background, 0, 0,
               w->getWidth(), w->getHeight())
            : new SDoubleBuffer ((HWND) w->id, (HDC) w->cdc, 
               w->background, pstr.rcPaint.left, pstr.rcPaint.top, 
               pstr.rcPaint.right-pstr.rcPaint.left, 
               pstr.rcPaint.bottom-pstr.rcPaint.top);
        }
            if (w->dbuffer == 0 && pstr.fErase)
            {
              w->repaintBackground (
                pstr.rcPaint.left, pstr.rcPaint.top,
                pstr.rcPaint.right, pstr.rcPaint.bottom);
            }
            l->redraw (w, pstr.rcPaint.left, pstr.rcPaint.top, 
              pstr.rcPaint.right-pstr.rcPaint.left,
              pstr.rcPaint.bottom-pstr.rcPaint.top);
        if (w->dbuffer)
        {
           ((SDoubleBuffer*) w->dbuffer)->copyToScreen (
               (HWND) w->id, 
               (HDC)  w->cdc);
           delete (SDoubleBuffer*) w->dbuffer;
           w->dbuffer = 0;
        }
            w->cdc = 0;
#endif
            EndPaint (hwnd, &pstr);
          }
          return 0;
       }
    }
    break;
  case WM_DEVICECHANGE:
    imageCache.clear();
    maskCache.clear();
    fprintf (stderr, "DeviceChange - Compatible DC deleted\n");
    DeleteDC (compatibleDC);
    compatibleDC = 0;
    break;
  case WM_ERASEBKGND:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    /* TODO: might be faster with addRedrawingEvent */
    if (w)
    {
       RECT rect;
       GetClientRect (hwnd, &rect);
    /* does not work - race condition */
#if 0
        handler->addRedrawEvent (w->getID(), SRedrawEvent (true, 
            rect.left, rect.top,
            rect.right-rect.left, rect.bottom-rect.top));
#else
       w->cdc = ((HDC) wParam);
       w->repaintBackground (
          rect.left, rect.top,
          rect.right, rect.bottom);
       w->cdc = 0;
#endif
       return 1;
    }
       
    break;

  case WM_CAPTURECHANGED:
     lostCapture((long)lParam);
     return 0;
    break;
//what a NAME!
//case WM_NCHITTEST:
  case WM_MOUSEMOVE:
      {
        int fwKeys = (int) wParam;
        short xPos = LOWORD (lParam);
        short yPos = HIWORD (lParam);
        if (fwKeys & MK_LBUTTON)
        {
           buttonDragged ((long) hwnd, 0, xPos, yPos); 
        }
        else if (fwKeys & MK_MBUTTON)
        {
           buttonDragged ((long) hwnd, 1, xPos, yPos); 
        }
        else if (fwKeys & MK_RBUTTON)
        {
           buttonDragged ((long) hwnd, 2, xPos, yPos); 
        }
        else 
        {
           buttonEnter ((long) hwnd);   
        }
      }
      break;
  case WM_RBUTTONDOWN:
      {
        short xPos = LOWORD (lParam);
        short yPos = HIWORD (lParam);
        buttonPressed (long (hwnd), 2, xPos, yPos);
      }
      break;
  case WM_RBUTTONUP:
      {
        short xPos = LOWORD (lParam);
        short yPos = HIWORD (lParam);
        buttonReleased (long (hwnd), 2, xPos, yPos);
      }
      break;
  case WM_MBUTTONDOWN:
      {
        short xPos = LOWORD (lParam);
        short yPos = HIWORD (lParam);
        buttonPressed ((long) hwnd, 1, xPos, yPos);
      }
      break;
  case WM_MBUTTONUP:
      {
        short xPos = LOWORD (lParam);
        short yPos = HIWORD (lParam);
        buttonReleased ((long) hwnd, 1, xPos, yPos);
      }
      break;
  case WM_LBUTTONDOWN:
      {
        short xPos = LOWORD (lParam);
        short yPos = HIWORD (lParam);
        buttonPressed ((long) hwnd, 0, xPos, yPos);
      }
      break;
  case WM_LBUTTONUP:
      {
        short xPos = LOWORD (lParam);
        short yPos = HIWORD (lParam);
        buttonReleased ((long)hwnd, 0, xPos, yPos);
      }
      break;
  case WM_MOUSEWHEEL:
  //case MSH_MOUSEWHEEL:
      {
        // WM_MOUSEWHEEL is only delivered to focus window.
        POINT point;
        point.x = LOWORD(lParam);
        point.y = HIWORD(lParam);
        HWND hwndReal = WindowFromPoint (point);
        // filter out middle button down.
        short butt = LOWORD (wParam);
        if (butt & MK_MBUTTON) break;
        processMouseWheel ((long)hwndReal, (int) GET_WHEEL_DELTA_WPARAM(wParam));
      }
      return 0;
  case WM_IME_CHAR:
      //fprintf (stderr, "WM_IME_COMPOSITION\n");
      {
      }
      break;
  case WM_IME_COMPOSITION:
      w = windowHashtable.get ((long)hwnd);
      l = listenerHashtable.get ((long)hwnd);
      //fprintf (stderr, "WM_IME_CHAR\n");
      if (w && (lParam & GCS_RESULTSTR))
      {
         HIMC himc = ImmGetContext (hwnd);
         if (!himc) break;
         DWORD size = ImmGetCompositionStringW(himc, GCS_RESULTSTR, 0, 0);
         HGLOBAL hglobal = GlobalAlloc (GHND, size + sizeof (WCHAR));
         if (!hglobal)
         {
           ImmReleaseContext (hwnd, himc);
           break;
         }
         LPSTR lpstr = (LPSTR) GlobalLock (hglobal);
         if (lpstr)
         {
           ImmGetCompositionStringW(himc, GCS_RESULTSTR, lpstr, 
             size + sizeof (WCHAR));
           /* copy unicode over */ 
           SV_UCS4 ucs4v;
           SString str (lpstr, (unsigned int)size); 
           for (unsigned int i=0; i+1<str.size(); i=i+2)
           {
              SS_UCS2 u = ((SS_UCS2*) str.array())[i/2];
              ucs4v.append ((SS_UCS4) u);
           }
           /* add surrogates together */
           SEncoder utf8enc ("utf-8");
           SString out = utf8enc.encode (ucs4v);
           keyData.key = SWindowListener::Key_Send;
           sendKeyChar (&keyData, out);
         }
         GlobalUnlock (hglobal);
         GlobalFree (hglobal);
         ImmReleaseContext (hwnd, himc);
         return 0;
      }
      break;
  case WM_CHAR:
  case WM_SYSDEADCHAR:
      {
        unsigned int lKeyData = lParam;
        SString s; s.append ((char)((TCHAR) wParam));
        sendKeyChar (&keyData, s);
      }
      return 0;
  case WM_SYSKEYDOWN:
      /* state, sys, code, down */
      processKey (&keyData, true, wParam, true);
      return 0;
  case WM_SYSKEYUP:
      /* state, sys, code, down */
      processKey (&keyData, true, wParam, false);
      return 0;
  case WM_KEYDOWN:
      /* state, sys, code, down */
      processKey (&keyData, false, wParam, true);
      return 0;
  case WM_KEYUP:
      /* state, sys, code, down */
      processKey (&keyData, false, wParam, false);
      return 0;
  case WM_SIZE:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    if (w)
    {
      unsigned int width = (unsigned int) LOWORD (lParam);
      unsigned int height = (unsigned int) HIWORD (lParam);
      /*
      RECT rcClient;
      if (GetClientRect((HWND)hwnd, &rcClient))
      {
        w->setSize(rcClient.right-rcClient.left, rcClient.bottom-rcClient.top);
      }
      else
      {
        w->setSize(width, height);
      }
      */
      w->setSize(width, height);
      /* no need to send event to children */
      if (!w->parentID)
      {
        l->resized(w, w->getPositionX(), w->getPositionY(), w->getWidth(), w->getHeight());
      }
      return 0;
    }
    break;
  case WM_MOVE:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    if (w)
    {
      short xPos = (unsigned int) LOWORD (lParam);
      short yPos = (unsigned int) HIWORD (lParam);
      w->setPosition(xPos, yPos);
      return 0;
    }
    break;
  case WM_COMMAND:
    break;
  case WM_DESTROY:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    if (w && w->clipChained)
    {
      ChangeClipboardChain (hwnd, (HWND)w->clipChain);
      w->clipChained = false;
    }
    PostQuitMessage (0);
    SEventHandler::exit();
    break;
  case WM_CLOSE:
    w = windowHashtable.get ((long)hwnd);
    l = listenerHashtable.get ((long)hwnd);
    if (w)
    {
      if (w && l && l->windowClose (w))
      {
         w->hide ();
      }
      if (shownWindows == 0)
      {
        if (w && w->clipChained)
        {
          ChangeClipboardChain (hwnd, (HWND)w->clipChain);
          w->clipChained = false;
        }
        PostQuitMessage (0);
        SEventHandler::exit();
      }
      return 0;
    }
    else
    {
      return 0;
    }
    break;
  default:
     return DefWindowProc(hwnd,message,wParam,lParam);
  }
  return DefWindowProc(hwnd,message,wParam,lParam);
}

/**
 * This event loop is especially tailored for wine
 * where we can not wait on objects and we can not
 * wait on sockets. What can we do? Sleep. That is
 * what Word Excel and all ms stuff is doing anyway
 * so we wont be any different.
 */
static int
winWineHack (int readSize, fd_set *ro, int writeSize, fd_set *wo,
  int exceptSize, fd_set *ex, struct timeval* t)
{
  int  millisec = 2000; /* people should notice they did sg wrong. */
  if (t!=0)
  {
     millisec = (DWORD) (t->tv_sec * 1000 + t->tv_usec / 1000);
  }
  Sleep (millisec);
  return 0;
}

/**
 * This is a hack to make select work on windows too by  Gaspar 
 * This routine will hook up int SEventBSD hook.
 */
static int
winSelectHack (int readSize, fd_set *ro, int writeSize, fd_set *wo,
  int exceptSize, fd_set *ex, struct timeval* t)
{
  int maxFd = (readSize > writeSize) ? readSize : writeSize;
  maxFd = (exceptSize > maxFd) ? exceptSize : maxFd;

  /* build events */
  DWORD millisec = WSA_INFINITE;
  if (t!=0)
  {
     millisec = (DWORD) (t->tv_sec * 1000 + t->tv_usec / 1000);
  }
  if (maxFd == 0 && millisec ==0)
  {
    return 0;
  }
  SBinVector<WSAEVENT> events;
  SV_INT vmap;

// Win95 does not have it.
#ifdef HAVE_WS2_32_DLL
  /* Nothing only timer - or not even exceptSize - forget that */
  unsigned int i;
  /* go through read and write */
  for (i=0; i<readSize; i++)
  {
     if (!FD_ISSET (i, ro)) continue;
     WSAEVENT event = WSACreateEvent ();
     WSAEventSelect ((SOCKET)i, event, FD_ACCEPT|FD_READ|FD_CLOSE);
     events.append (events);
     vmap.append (-(int)i);
  }
  for (i=0; i<writeSize; i++)
  {
     if (!FD_ISSET (i, wo)) continue;
     WSAEVENT event = WSACreateEvent ();
     WSAEventSelect ((SOCKET)i, event, FD_WRITE);
     events.append (events);
     vmap.append ((int)i);
  }

  DWORD idx;
  /* maybe WSA_INFINITE should be a timer ... */
  idx = WSAWaitForMultipleEvents(events.size(), events.array(),
    FALSE, millisec, FALSE);
#else
  DWORD idx;
#endif

  /*
   * We need this one because WSAWaitForMultipleEvents 
   * will not react to window events. in case events.array is
   * empty it fails.
   */
  idx = MsgWaitForMultipleObjects(events.size(), (LPHANDLE)events.array(),
    FALSE, millisec, QS_ALLINPUT);
  if (readSize == 0 && ro != 0) FD_ZERO (ro);
  if (writeSize == 0 && wo != 0) FD_ZERO (wo);
  int lerr = GetLastError();
#ifdef HAVE_WS2_32_DLL
  for (i=0; i<events.size(); i++)
  {
    WSACloseEvent (events[i]);
  }
#endif
  if (idx == 0xFFFFFFFF)
  {
    fprintf (stderr, "SEventBSD::WaitForMultipleObjects error (%d)\n", lerr);
    return -1;
  }
  if (idx == WAIT_TIMEOUT)
  {
    //fprintf (stderr, "WAIT TIMEOUT\n"); 
    return 0;
  }
  int eventnum =  idx - WAIT_OBJECT_0;
  if (eventnum == vmap.size()) 
  {
    //fprintf (stderr, "WINDOW EVENT!\n");
    return -2; /* outside event - this is a  window event handled in jo */
  }
  int fd = vmap[eventnum];
  if (fd < 0) // read 
  {
     fd = - fd;
     FD_SET (fd, ro);
     return 1;
  }
  else if ( fd > 0)
  {
     FD_SET (fd, wo);
     return 1;
  }
  return -2; /* what the heck? */
}

void
SW32Window::setBackground(const SColor &color)
{
  background = color;
  pen = SPen(pen.getForeground(), background, pen.getLineWidth());
}

static HBRUSH 
getSolidBrush (const SColor& clr)
{
  SString mvle = SString ((long)(clr.getValue() & 0x00ffffff));
  HBRUSH brush = brushes.get (mvle); 
  if (brush == 0)
  {
    COLORREF ref = PALETTERGB (clr.red, 
      clr.green, clr.blue);
    brush = ::CreateSolidBrush (ref);
    brushes.put (mvle, brush);
  }
  return brush;
}
static HPEN 
getBitPen (const SColor& clr)
{
  SString mvle = SString ((long)(clr.getValue() & 0x00ffffff));
  HPEN hpen = pens.get (mvle); 
  if (hpen == 0)
  {
     hpen = CreatePen (PS_SOLID, 0, RGB(clr.red, clr.green, clr.blue));
     pens.put (mvle, hpen);
  }
  return hpen;
}

void
SW32Window::repaintBackground(int left, int top, 
   int right, int bottom)
{
  /* remove clipping */
  void* oclip = clipRegion;
  clipRegion = 0;
  bitfill (background, left, top, right - left, bottom - top);
  clipRegion = oclip;
}

/**
 * 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
SW32Window::bitfill (const SColor& bg, int _x, int _y, 
 unsigned int _width, unsigned int _height)
{
  bool mydc = dcin();
  RECT rect;
  rect.left = _x;
  rect.top = _y;
  rect.right = _x + (int) _width;
  rect.bottom = _y + (int) _height;

  HBRUSH brush = getSolidBrush (bg);
  int mode = SetMapMode ((HDC)cdc, MM_TEXT);
  FillRect ((HDC)cdc, &rect, brush);
  SetMapMode ((HDC)cdc, mode);
  dcout (mydc);
}

/**
 * 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
SW32Window::bitline (const SColor& fg, int _x, int _y, int _tox, int _toy)
{
  bool mydc = dcin();
  HPEN hpen = getBitPen (fg);
  SelectObject ((HDC)cdc, hpen);
  MoveToEx ((HDC)cdc, _x, _y, 0);
  LineTo ((HDC)cdc, _tox, _toy);
  /* no last point otherwiseon windows */
  LineTo ((HDC)cdc, _tox+1, _toy);
  dcout (mydc);
}

/**
 * Draw a solid point.
 * @param x is the x point
 * @param y is the y point
 */
void
SW32Window::bitpoint (const SColor& clr, int _x, int _y)
{
  bool mydc = dcin();
  COLORREF ref = PALETTERGB (clr.red, clr.green, clr.blue);
  ::SetPixel ((HDC)cdc, _x, _y, ref);
  dcout (mydc);
}

void
SW32Window::bitpoints (const SColor& clr, const int* _x, const int* _y, 
         unsigned int _size)
{
  bool mydc = dcin();
  COLORREF ref = PALETTERGB (clr.red, clr.green, clr.blue);
  for (unsigned int i=0; i<_size; i++)
  {
    ::SetPixel ((HDC)cdc, _x[i], _y[i], ref);
  }
  dcout (mydc);
}

/**
 * After this size things wont be cached.
 */
void
SW32Window::setPixmapCacheSize(unsigned int _size)
{
//  fprintf (stderr, "setPixmapCacheSize=%u\n", _size);
  imageCache.setSize (_size);
  maskCache.setSize (_size);
}
 
/**
 * turn on/off the cache and clear it
 */
void
SW32Window::setPixmapCacheOn (bool _on)
{
  // Win98 grocks under bitmaps.
  imageCache.setOn (_on);
  maskCache.setOn (_on);
}

/**
 * This one can return false if it fails.
 */
void
SW32Window::putImage (int _x, int _y, const SImage& im)
{
  bool mydc = dcin();

  /* bitmap may be selected in a dc . dont delete now thinking....*/
  const SString& ks = (const SString&) im.getID();
  char a[10];
  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 (ks);

  /* Bitmap is always added, mask is on-demand */
  if (compatibleDC == 0)
  {
     compatibleDC = CreateCompatibleDC((HDC)cdc);
     if (compatibleHBitmap!=0)
     {
       DeleteObject (compatibleHBitmap);
     }
     compatibleHBitmap = CreateCompatibleBitmap(compatibleDC, 8, 8);
  }

  SBitmapItem* maskItem = maskCache.get (key);
  SBitmapItem* bitmapItem = imageCache.get (key);
  HBITMAP  bitmap;
  HBITMAP  mask;
  SBitmapItem bitem;
  SBitmapItem mitem;
  bitem.width = im.getWidth(); bitem.height = im.getHeight();
  mitem.width = im.getWidth(); mitem.height = im.getHeight();
  if (maskItem && bitmapItem)
  {
    bitmap = bitmapItem->bitmap;
    mask = maskItem->bitmap;
    bitem.bitmap = bitmap;
    mitem.bitmap = mask;
    bitem.dc = bitmapItem->dc;
    mitem.dc = maskItem->dc;
    bitem.x = bitmapItem->x; bitem.y = bitmapItem->y;
    mitem.x = maskItem->x; mitem.y = maskItem->y;
    bitem.width = bitmapItem->width; bitem.height = bitmapItem->height;
    mitem.width = maskItem->width; mitem.height = maskItem->height;
  }
  else 
  {
    bitmap = 0;
    mask = 0;
    bitem.x = 0; bitem.y = 0;
    mitem.x = 0; mitem.y = 0;
    if (im.getShades() == 0)
    {
      createColoredBitmap (pen, im, &bitmap, &mask, (HDC)cdc, compatibleDC);
    }
    else
    {
      createShadedBitmap (pen, im, &bitmap, &mask, (HDC)cdc, compatibleDC);
    }
    /* cache this */
    SelectObject (compatibleDC, bitmap);
    bitem.dc = compatibleDC;
    bitem.bitmap = bitmap;
    imageCache.put (key, bitem);

    /* cache mask */
    SelectObject (compatibleDC, mask);
    mitem.dc = compatibleDC;
    mitem.bitmap = mask;
    maskCache.put (key, mitem);
  }

  if (mask)
  {
    /* if cached, dc is not same, not necessary */
    if (mitem.dc == bitem.dc) SelectObject (mitem.dc, mask);
    BitBlt ((HDC)cdc, _x, _y, mitem.width, mitem.height, 
         mitem.dc, mitem.x, mitem.y, SRCAND);
  }

  if (bitmap)
  {
    /* if cached, dc is not same, not necessary */
    if (mitem.dc == bitem.dc) SelectObject (bitem.dc, bitmap);
    BitBlt ((HDC)cdc, _x, _y, bitem.width, bitem.height, 
        bitem.dc, bitem.x, bitem.y, SRCPAINT);
  }

  /* cache was used */
  if (maskItem && bitmapItem)
  {
    dcout (mydc);
    return;
  }
  SelectObject (compatibleDC, compatibleHBitmap);
  if (bitmap) DeleteObject (bitmap);
  if (mask) DeleteObject (mask);
  dcout (mydc);
  return;
}

/**
 * Create two bitmaps, one for the image and one for the colored ones.
 * @param p is the image
 * @param m is the shape image. it contains 1's where there is no image.
 * @param dc is a dc that has the original colored bitmap selected in it.
 * @param _dc is a dc we can use - it usually has a 1 depth bitmap in it
 */
static void
createColoredBitmap (const SPen& pen, const SImage& im, 
    HBITMAP* p, HBITMAP* m, HDC odc, HDC _dc)
{
  int imageWidth = (int) im.getWidth();
  int imageHeight = (int) im.getHeight();

  *p = CreateCompatibleBitmap (odc, imageWidth, imageHeight);
  if (*p==0) 
  {
     fprintf (stderr, "can not create colored bitmap width=%u height=%u\n", 
       imageWidth, imageHeight);
     /* this is win98 - another piece of .... */
     return;
  }
  SelectObject (_dc, *p);
  for (int y=0; y<imageHeight; y++)
  {
    for (int x=0; x<imageWidth; x++)
    {
      SColor clr (im.getShade (x, y));
      SColor bg (pen.getBackground());
      if (clr.alpha != 0) bg.blend (clr);
      COLORREF ref = (clr.alpha == 0)
            ? PALETTERGB (0, 0, 0)
            :  PALETTERGB (bg.red, bg.green, bg.blue);
      SetPixel (_dc, x, y, ref);
    }
  }
  
  if (m != 0)
  /* First wipe out the shape */
  {
    *m = CreateCompatibleBitmap (odc, imageWidth, imageHeight);
    if (*m==0) 
    {
       fprintf (stderr, 
        "can not create colored bitmap mask width=%u height=%u\n", 
        imageWidth, imageHeight);
       /* this is win98 - another piece of .... */
       return;
    }
    SelectObject (_dc, *m);
    for (int y=0; y<imageHeight; y++)
    {
      for (int x=0; x<imageWidth; x++)
      {
        SColor c (im.getShade (x, y));
        COLORREF ref = (c.alpha==0)
            ? PALETTERGB (0xff, 0xff, 0xff)
            : PALETTERGB (0, 0, 0);
        SetPixel (_dc, x, y, ref);
      }
    }
  }
}

/**
 * Create two bitmaps, one for the image and one for the shade.
 * @param p is the image
 * @param m is the shape image. it contains 1's where there is no image.
 * @param dc is a dc that has the original colored bitmap selected in it.
 * @param _dc is a dc we can use - it usually has a 1 depth bitmap in it
 */
static void
createShadedBitmap (const SPen& pen, const SImage& im, 
  HBITMAP* p, HBITMAP* m, HDC odc, HDC _dc)
{
  int imageWidth = (int) im.getWidth();
  int imageHeight = (int) im.getHeight();

  int i;
  if (m != 0)
  /* First wipe out the shape */
  {
    *m = CreateCompatibleBitmap (odc, imageWidth, imageHeight);
    if (*m==0) 
    {
       fprintf (stderr, "can not create mask width=%u height=%u\n", 
         imageWidth, imageHeight);
       /* this is win98 - another piece of .... */
       return;
    }
    SelectObject (_dc, *m);
    for (int y=0; y<imageHeight; y++)
    {
      for (int x=0; x<imageWidth; x++)
      {
        SS_WORD32 sh = im.getShade (x, y);
        COLORREF  ref = (sh==0) 
            ? PALETTERGB (0xff, 0xff, 0xff)
            : PALETTERGB (0, 0, 0);
        SetPixel (_dc, x, y, ref);
      }
    }
  }

  int shades = im.getShades();
  COLORREF* colors  = new COLORREF[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());
      SColor fg (pen.getForeground().red,
        pen.getForeground().green, 
        pen.getForeground().blue,
        (unsigned char) (i * 255 /(shades-1)));
      bg.blend (fg);

      colors[i] =  PALETTERGB (bg.red, bg.green, bg.blue);
  } 


  *p = CreateCompatibleBitmap (odc, imageWidth, imageHeight);
  if (*p==0) 
  {
     fprintf (stderr, "can not create bitmap width=%u height=%u\n", 
       imageWidth, imageHeight);
     /* this is win98 - another piece of .... */
     return;
  }
  SelectObject (_dc, *p);
  /* 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);
      COLORREF  ref = (sh==0) 
        ? PALETTERGB (0,0,0) 
        : colors[im.getShade (x, y)];
      SetPixel (_dc, x, y, ref);
    }
  }
  delete colors;
}

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

/**
 * Drawing routines inherited from SCanvas
 */
bool
SW32Window::beginImage (double _x, double _y, const SString& _id, const SColor& back)
{
  pen.setBackground (back);
  if (engine ==0) engine = new SGEngine();
  if (isCacheOn)
  {
    return engine->beginImage ((int)_x, (int)_y, _id);
  }
  else
  {
    return engine->beginImage ((int)_x, (int)_y, "");
  }
}
void
SW32Window::newpath ()
{
  engine->newpath ();
}

void
SW32Window::fill (const SPen& _pen)
{
  if (pen != _pen)
  {
     pen = _pen;
  }
  if (engine==0) return;

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


/**
 * FIXME: fill and return the resulting image for better caching.
 */
void
SW32Window::endImage ()
{
  if (engine==0) return;

  SImage* si= engine->endImage ();
  if (si==0) return; /* offscreen */

  /* Use the putimage that does some caching. */
  if (imageCache.isOn())
  {
    putImage (si->getOrigoX(), si->getOrigoY(), *si);
    delete si;
    return;
  }
  bool mydc = dcin();
  /* 
   *  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.
   */
  if (compatibleDC == 0)
  {
     compatibleDC = CreateCompatibleDC((HDC)cdc);
     if (compatibleHBitmap!=0)
     {
       DeleteObject (compatibleHBitmap);
     }
     compatibleHBitmap = CreateCompatibleBitmap(compatibleDC, 8, 8);
  }
  HBITMAP bitmap;
  HBITMAP mask;
  createShadedBitmap (pen, *si, &bitmap, &mask, (HDC)cdc, compatibleDC);
  if (mask)
  {
    SelectObject (compatibleDC, mask);
    BitBlt ((HDC)cdc, si->getOrigoX(), si->getOrigoY(), 
         si->getWidth(), si->getHeight(), 
         compatibleDC, 0, 0, SRCAND);
  }
  
  if (bitmap)
  {
    SelectObject (compatibleDC, bitmap);
    BitBlt ((HDC)cdc, si->getOrigoX(), si->getOrigoY(), 
         si->getWidth(), si->getHeight(), 
         compatibleDC, 0, 0, SRCPAINT);
  }
  /* can not delete a bitmap if it is selected in */
  SelectObject (compatibleDC, compatibleHBitmap);
  if (mask) DeleteObject (mask);
  if (bitmap) DeleteObject (bitmap);
  delete si;
  dcout (mydc);
}


/**
 * 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
SW32Window::moveto (double x, double y)
{
  if (engine ==0) return;
  engine->moveto (x, y);
}

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

/** 
 *  Draw a cubic beizer curve
 */
void
SW32Window::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
SW32Window::closepath()
{
  if (engine ==0) return;
  engine->closepath();
}
/**
 * TODO: not implemented 
 */
void
SW32Window::rotate (double angle)
{
  if (engine ==0) engine = new SGEngine();
  engine->rotate (angle);
}

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

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

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

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

/**
 * Clear a region (set it to the background)
 * This should work with a clipped region.
 * @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
SW32Window::clear (int _x, int _y, unsigned int _width, unsigned int _height)
{
  bitfill (background, _x, _y, _width, _height);
}

/**
 * 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
SW32Window::copy (int _x, int _y, unsigned int _width, unsigned int _height, 
  int _tox, int _toy)
{
  //XCopyArea (impl->display, (Window) id, (Window) id, 
  //    gc, x, y, width, height, tox, toy);
  bool mydc = dcin();
  BitBlt ((HDC)cdc, _tox, _toy, _width, _height, 
         (HDC)cdc, _x, _y, SRCCOPY);
  handler->moveRedrawEvent (id, _tox-_x, _toy-_y);
  dcout (mydc);
}

/**
 * Assign a rectangualr clip area. Everithing outside this area will be clipped.
 */
void
SW32Window::setClippingArea (int _x, int _y, unsigned int _width, unsigned int _height)
{
  if (clipRegion != 0)
  {
     DeleteObject ((HRGN) clipRegion);
  }
  HRGN rgn = CreateRectRgn (_x, _y, _x + (int) _width, _y + (int) _height);
  clipRegion = rgn;
  if (cdc) SelectClipRgn ((HDC)cdc, rgn);
}

/**
 *  clear the clipping area.
 */
void
SW32Window::removeClippingArea ()
{
  if (!clipRegion)
  {
    if (cdc)
    {
      SelectClipRgn ((HDC)cdc, 0);
    }
    return;
  }
  DeleteObject ((HRGN) clipRegion);
  clipRegion = 0;
  if (cdc)
  {
    SelectClipRgn ((HDC)cdc, 0);
  }
}

static int dcins = 0;

/**
 * Gointo a dc. return true if it is a borrowed resource .
 * this should be passed to dcout when exiting dc
 */
bool
SW32Window::dcin()
{
  if (dcins >0)
  {
     fprintf (stderr, "SW32Window::dcin error %d\n", dcins);
  }
  dcins++;
  if (dbuffer)
  {
    cdc = ((SDoubleBuffer*)dbuffer)->bitmapHDC;
    SelectClipRgn ((HDC)cdc, (HRGN) clipRegion);
    return false;
  }
  if (cdc != 0)
  {
    SelectClipRgn ((HDC)cdc, (HRGN) clipRegion);
    return false;
  }
  cdc = GetDC((HWND)id);
  if (cdc == 0) fprintf (stderr, "DC==NULL\n");
  SelectClipRgn ((HDC)cdc, (HRGN) clipRegion);
  return true;
}
void
SW32Window::dcout(bool wasin)
{
  if (dcins ==0)
  {
     fprintf (stderr, "SW32Window::dcout error %d\n", dcins);
     return;
  }
  else
  {
     dcins--;
  }
  SelectClipRgn ((HDC)cdc, 0);
  if (wasin && dbuffer == 0)
  {
    ReleaseDC ((HWND)id, (HDC)cdc);
    cdc = 0;
  }
}

/**
 */
void
SW32Window::wait ()
{
  handler->doWin32Loop();
  /* we lost the job - we are in the job callback  */

  SJob* sjob = new SJob();
  // SGC BUG in dialogs same job removed.
  // SEventHandler::addJob(handler->job, handler);
   SEventHandler::addJob(sjob, handler);
  while (shown && SEventHandler::next());
  SEventHandler::remove(sjob);
  delete sjob;
}

static int buttonLastX[3];
static int buttonLastY[3];

long lastMouseWindow = 0;

/**
 * Generate a mouse enter and mouse leave event from what we have - 
 * the current window where the mouse is...
 */
static void 
buttonEnter (long hwnd)
{
  if (lastMouseWindow == hwnd) return;
  SW32Window* w=0;
  SWindowListener* l=0;
  if (lastMouseWindow)
  {
    w = windowHashtable.get (lastMouseWindow);
    l = listenerHashtable.get (lastMouseWindow);
    if (w != 0 && l!=0) l->leaveWindow (w);
  }
  lastMouseWindow = hwnd;
  if (lastMouseWindow)
  {
    w = windowHashtable.get (lastMouseWindow);
    l = listenerHashtable.get (lastMouseWindow);
    if (w != 0 && l!=0) l->enterWindow (w);
  }
}

/**
 * Work on buttonFlags buttons and do 
 * the actual delivery of events on actual windows.
 */
static void
buttonPressed (long hwnd, int button, int x, int y)
{
  buttonEnter (hwnd);
  if (buttonFlags[button]) return;
  if (buttonFlags[0] ==0 && buttonFlags[1] == 0 && buttonFlags[2] == 0)
  {
    SetCapture ((HWND)hwnd);
  }
  buttonFlags[button] = hwnd;
  buttonLastX[button] = x;
  buttonLastY[button] = y;
  SW32Window* w = windowHashtable.get ((long)hwnd);
  SWindowListener* l = listenerHashtable.get ((long)hwnd);
  if (w == 0 || l==0) return;
  l->buttonPressed (w, button, x, y);
}

static void
processMouseWheel (long hwnd, int increment) {
  SW32Window* w = windowHashtable.get ((long)hwnd);
  SWindowListener* l = listenerHashtable.get ((long)hwnd);
  if (w == 0 || l==0) return;
  w->currentScroll += increment;
  if (w->currentScroll > WHEEL_DELTA * 20) {
       w->currentScroll =  WHEEL_DELTA * 20;
  }
  if (w->currentScroll <  WHEEL_DELTA * -20) {
       w->currentScroll = WHEEL_DELTA * -20;
  }

  if (w->currentScroll < 0) {
    while (w->currentScroll < WHEEL_DELTA) {
        // caretDown
       l->buttonPressed (w, 4, (int)0, (int)0);
       w->currentScroll += WHEEL_DELTA*2;
    }
  } else {
    while (w->currentScroll > WHEEL_DELTA) {
       // caretUp
       l->buttonPressed (w, 3, (int)0, (int)0);
       w->currentScroll -= WHEEL_DELTA*2;
    }
  }
}

static void
buttonDragged (long nwnd, int button, int x, int y)
{
  buttonEnter (nwnd);
  if (buttonFlags[button] != nwnd) return;
  buttonLastX[button] = x;
  buttonLastY[button] = y;
  long hwnd = buttonFlags[button];
  SW32Window* w = windowHashtable.get ((long)hwnd);
  SWindowListener* l = listenerHashtable.get ((long)hwnd);
  if (w == 0 || l==0) return;
  l->buttonDragged (w, button, x, y);
}

static void
buttonReleased (long nwnd, int button, int x, int y)
{
  buttonEnter (nwnd);
  if (!buttonFlags[button]) return;
  bool samebutton = (nwnd == buttonFlags[button]);
  buttonLastX[button] = x;
  buttonLastY[button] = y;
  long hwnd = buttonFlags[button];
  SW32Window* w = windowHashtable.get (buttonFlags[button]);
  SWindowListener* l = listenerHashtable.get (buttonFlags[button]);
  buttonFlags[button] = 0;
  if (buttonFlags[0] ==0 && buttonFlags[1] == 0 && buttonFlags[2] == 0)
  {
    ReleaseCapture ();
  }
  if (w != 0 && l!=0)
  {
    l->buttonReleased (w, button, x, y);
  }
#if 0
  if (samebutton) return;
  /* this poor guy was at the mercy of this screwy windows */
  w = windowHashtable.get ((long)hwnd);
  l = listenerHashtable.get ((long)hwnd);
  if (w != 0 && l!=0)
  {
    l->buttonReleased (w, button, x, y);
  }
#endif
}

static void
lostCapture (long wid)
{
  for (unsigned int i=0; i<3; i++)
  {
    if (buttonFlags[i] == 0) continue;
    if (wid == buttonFlags[i]) continue;
    SW32Window* w = windowHashtable.get (buttonFlags[i]);
    SWindowListener* l = listenerHashtable.get (buttonFlags[i]);
    int x = buttonLastX[i];
    int y = buttonLastY[i];
    if (w == 0 || l==0) continue;
    l->buttonReleased (w, i, x, y);
    buttonFlags[i] = 0;
  }
  buttonEnter (wid);
}

/* isPrimary is unused, all is primary */
SString
SW32Window::getClipUTF8(bool isPrimary)
{
   SString cld;
   bool ucs = true;
   bool utf8 = true;

   notifyClip ();
   SW32Window* w = windowHashtable.get (clipboardOwner);
   if (w != 0) /* local guy */
   {
      SEncoder utf8enc ("utf-8-s");
      SString out = utf8enc.encode (clipData);
      return SString (out);
   }

   if (OpenClipboard (0))
   {
     HANDLE h = GetClipboardData (UTF8_STRING);
     if (!h)
     {
       utf8 = false;
       h = GetClipboardData (CF_UNICODETEXT);
       if (!h)
       {
         h = GetClipboardData (CF_TEXT);
         ucs = false;
       }
     }
     if (h)
     {
       char* str = (char*) GlobalLock (h);
       if (str)
       {
         unsigned int lsize = GlobalSize (h);
         cld = SString (str, lsize);
         GlobalUnlock (h);
         /* we need to terminate ucs2 if 0 is seen */
         if (ucs)
         {
           for (unsigned int i=0; i+1<lsize; i=i+2)
           {
             if (cld[i] == 0 && cld[i+1]==0)
             {
                cld.truncate (i);
                break;
             }
           }
         }
       }
     }
     else 
     {
       //fprintf (stderr, "<< NOTHING %u bytes\n", cld.size());
     }
     CloseClipboard ();
   }
   if (utf8)
   {
     /**
      * Windows clipboad does not have a clue how big our data is.
      * We put 0xC0, 0x80 for nulls.
      */
     unsigned int ssize = cld.size();
     for (unsigned int i=0; i<ssize; i++)
     {
       if (cld[i] == 0)
       {
          cld.truncate (i);
          break;
       }
     }
     /* C0,80 never found in utf-8*/
     SString nl; nl.append ((char)0);
     cld.replaceAll ("\300\200", nl);
     return SString (cld);
   }
   impl->encoder.clear();
   SEncoder ucsenc("utf-16-le");
   SV_UCS4 ucstext = (ucs) ?  ucsenc.decode (cld) : impl->encoder.decode (cld);

   /* stupid windows does not know how to copy u+0000 - this
    * is the terminating character.
    */
   for (unsigned int i=0; i<ucstext.size(); i++)
   {
     if (ucstext[i] == 0) 
     {
       ucstext.truncate (i);
       break;
     }
   }
   SEncoder utf8enc ("utf-8-s");
   return SString (utf8enc.encode (ucstext));
}

/* isPrimary is unused, all is primary */
void
SW32Window::putClipUTF8(const SString& utf8, bool isPrimary)
{
  SEncoder utf8enc ("utf-8-s");
  clipData = utf8enc.decode (utf8);

  if (!clipChained)
  {
    clipChain = (long) SetClipboardViewer ((HWND) getID());
    clipChained = true;
  }
  if (OpenClipboard ((HWND)getID()))
  {
    EmptyClipboard ();
    SetClipboardData (CF_UNICODETEXT, 0);
    SetClipboardData (CF_TEXT, 0);
    SetClipboardData (UTF8_STRING, 0);
    CloseClipboard ();
  }
}

/**
 * Put text onto clipboard
 * @return 0 on success.
 */
static int
putClipUnicodeText()
{
  // They sell this API for money! Unbelievable!
  if (clipData.size() == 0) return 1;
  SEncoder ucs2enc("utf-16-le");
  SV_UCS4 v = clipData;
  v.append (0);
  SString out = ucs2enc.encode (v);
  HANDLE h = GlobalAlloc (GMEM_DDESHARE, out.size());
  if (!h) return 1;

  char* str = (char*) GlobalLock (h);
  memcpy (str, out.array(), out.size());
  GlobalUnlock (h);

  if (!SetClipboardData (CF_UNICODETEXT, h))
  {
     GlobalFree (h);
     return 1;
  }
  return 0;
}

/**
 * Put text onto clipboard
 * @return 0 on success.
 */
static int
putClipText()
{
  impEncoder.clear();
  // They sell this API for money! Unbelievable!
  SString out = impEncoder.encode (clipData);
  /* add crlf */
  out.replaceAll ("\r\n", "\n");
  out.replaceAll ("\r", "\n");
  out.replaceAll ("\n", "\r\n");
  /* LS 2028 */
  out.replaceAll ("\342\200\250", "\r\n");
  /* PS 2029 */
  out.replaceAll ("\342\200\251", "\r\n");

  SString nl; nl.append ((char)0);
  /* I did not invent this - Microsoft does it too :( */
  out.replaceAll (nl, " ");

  out.append ((char)0);

  HANDLE h = GlobalAlloc (GMEM_DDESHARE, out.size());
  if (!h) return 1;

  char* str = (char*) GlobalLock (h);
  memcpy (str, out.array(), out.size());
  GlobalUnlock (h);
  if (!SetClipboardData (CF_TEXT, h))
  {
     GlobalFree (h);
     return 1;
  }
  return 0;
}

/**
 * Put text onto clipboard
 * This is not really a utf-8 text. It has an integer in front
 * in machine byte-order telling size.
 * @return 0 on success.
 */
static int
putClipUtf8Text()
{
  if (clipData.size() == 0) return 1;
  SEncoder utf8enc ("utf-8-s");
  SString out = utf8enc.encode (clipData);
  /* this is our null */
  SString nl; nl.append ((char)0);
  out.replaceAll (nl, "\300\200");

  HANDLE h = GlobalAlloc (GMEM_DDESHARE, out.size()+1);
  if (!h) return 1;

  char* str = (char*) GlobalLock (h);
  memcpy (str, out.array(), out.size());
  str[out.size()] = 0;
  GlobalUnlock (h);

  if (!SetClipboardData (UTF8_STRING, h))
  {
     GlobalFree (h);
     return 1;
  }
  return 0;
}

/**
 * Notify current clipboardOwner of clip lost, assign new owner.
 */
void
notifyClip ()
{
  /* Before returning to event loop check if clipboard 
   * owner changed. SetClipboardViewer is a crzay 
   * mind's creation treat is as non-existent.
   */ 
  HWND owner  = GetClipboardOwner();
  if ((long)owner != clipboardOwner)
  {
     SW32Window* w = windowHashtable.get (clipboardOwner);
     SWindowListener* l = listenerHashtable.get (clipboardOwner);
     if (w)
     {
       if (w && l) l->lostClipSelection (w);
     }
     clipboardOwner = (long) owner;
  }
}

void
SW32Window::setModal (SWindow* _parent, bool decorated)
{
  /* CHECKME */
  modalFlag = true;

  modalID = ((SW32Window*)_parent)->getID();
  SetWindowLong ((HWND)id, GWL_STYLE, 
     SS_YUDIT_DIALOG_STYLE);
     //WS_POPUPWINDOW | WS_CAPTION);
}
/**
 * put this window in the middle
 */
void
SW32Window::center (SWindow* _window)
{
  HWND root = GetDesktopWindow();
  // dont trust these guys
  if (!root)  return;

  HWND me = root;
  if (_window!=0)
  {
    me = (HWND) ((SW32Window*)_window)->getID();
  }
  RECT myrect;
  if (!GetWindowRect(me, &myrect))
  {
    fprintf (stderr, "ERROR: can not get my window rect\n");
    return;
  }
  RECT rootrect;
  if (!GetWindowRect(root, &rootrect))
  {
    fprintf (stderr, "ERROR: can nto get root window rect\n");
    return;
  }
  /* center point */
  int lx = (myrect.left + myrect.right)/2;  
  int ly = (myrect.top + myrect.bottom)/2;  

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

  int rootWidth = rootrect.right - rootrect.left;
  int rootHeight = rootrect.bottom - rootrect.top;

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

void
SW32Window::getKeyboardFocus ()
{
  SW32Window* top = getToplevelWindow (this);
  top->currentFocusWindow = getID();
  if (top->currentFocusWindow == currentFocusWindow)
  {
    return;
  }
  SetFocus ((HWND)getID());
}

static SW32Window*
getToplevelWindow (SW32Window* w)
{
  SW32Window * wn = w;
  while (wn->parentID)
  {
    SW32Window* swid = (SW32Window*) windowHashtable.get(wn->parentID);
    if (swid == 0) break; /* never happens */ 
    wn = swid;
  }
  return wn;
}

static SW32Window*
getToplevelWindow (long id)
{
  SW32Window* wn = (SW32Window*) windowHashtable.get(wn->parentID);
  if (wn == 0) return 0;
  while (wn->parentID)
  {
    SW32Window* swid = (SW32Window*) windowHashtable.get(wn->parentID);
    if (swid == 0) break; /* never happens */ 
    wn = swid;
  }
  return wn;
}

void
SW32Window::setMinimumSize (unsigned int _width, unsigned int _height)
{
  minimumSizesX.put (id, _width);
  minimumSizesY.put (id, _height);
}

static void
sendKeyChar (KeyData* kd, const SString& _s)
{
  SString s = _s;
  if (s.size() == 0) return;
  unsigned char c0 = (unsigned char) s[0];

  /* TABS and controls are handled in processKey */
  if (s.size() == 1 && c0 < 0x20)
  {
     return;
  }
  /* 
   * Deal with only 'pressed' keys. If focus changes while repeat,
   * it may cause this.
   */
  if (kd->key == SWindowListener::Key_Undefined)
  {
    return;
  }
  bool ctrl = kd->ctrl0 || kd->ctrl1; 
  bool shift = kd->shift0 || kd->shift1; 
  bool meta = kd->meta0 || kd->meta1; 
  if (!sendAcceleratorPressed ((int) kd->key, ctrl, shift, meta))
  {
    SW32Window* wn = windowHashtable.get (currentFocusWindow);
    SWindowListener* ln = listenerHashtable.get (currentFocusWindow);
    if (wn == 0 || ln == 0) return;
    ln->keyPressed (wn, kd->key, s, ctrl, shift, meta);
  }
}

/**
 * Send release key event to those unfortunately windows that
 * lost keyboard focus as windows does not sent it :(
 */
static void
sendKeyReleased (KeyData* kd, SW32Window* wn, SWindowListener* ln)
{
  if (sendAcceleratorReleased ())
  {
    kd->ctrl0 = false;
    kd->ctrl1 = false;
    kd->shift0 = false;
    kd->shift1 = false;
    kd->meta0 = false;
    kd->meta1 = false;
    kd->key = SWindowListener::Key_Undefined;
    return;
  }
  SString s;
  bool ctrl = kd->ctrl0 || kd->ctrl1; 
  bool shift = kd->shift0 || kd->shift1; 
  bool meta = kd->meta0 || kd->meta1; 

  if (kd->ctrl0)
  { 
    ln->keyReleased (wn, SWindowListener::Key_Control_L, s, ctrl, shift, meta);
  } 
  if (kd->ctrl1)
  { 
    ln->keyReleased (wn, SWindowListener::Key_Control_R, s, ctrl, shift, meta);
  } 
  kd->ctrl0 = false;
  kd->ctrl1 = false;
  ctrl = false;

  if (kd->shift0)
  { 
    ln->keyReleased (wn, SWindowListener::Key_Shift_L, s, ctrl, shift, meta);
  } 
  if (kd->shift1)
  { 
    ln->keyReleased (wn, SWindowListener::Key_Shift_R, s, ctrl, shift, meta);
  } 

  kd->shift0 = false;
  kd->shift1 = false;
  shift = false;

  if (kd->meta0)
  { 
    ln->keyReleased (wn, SWindowListener::Key_Meta_L, s, ctrl, shift, meta);
  } 
  if (kd->meta1)
  { 
    ln->keyReleased (wn, SWindowListener::Key_Meta_R, s, ctrl, shift, meta);
  } 
  kd->meta0 = false;
  kd->meta1 = false;
  meta = false;
  kd->key = SWindowListener::Key_Undefined;
}

/**
 * Process a key message. Generate event if necessary.
 * @param kd is a state holder.
 * @param syskey is true is this is a system key (what does this
 * stupid thing mean - I will never know)
 * @param keycod is the VK_KEYCODE
 * @param isdown is true if the key was pressed.
 */
static void
processKey (KeyData* kdin, bool syskey, int keycod, bool isdown)
{
  KeyData kd = { 
    SWindowListener::Key_Undefined,
    false, false, false,
    false, false, false
  };
  if (isdown)
  {
     kd.shift0 =  kdin->shift0; kd.shift1 = kdin->shift1;
     kd.meta0 =  kdin->meta0; kd.meta1 = kdin->meta1;
     kd.ctrl0 =  kdin->ctrl0; kd.ctrl0 = kdin->ctrl0;
  }
  else
  {
     kd.shift0 =  ! kdin->shift0; kd.shift1 = ! kdin->shift1;
     kd.meta0 =  ! kdin->meta0; kd.meta1 = ! kdin->meta1;
     kd.ctrl0 =  ! kdin->ctrl0; kd.ctrl0 = ! kdin->ctrl0;
  }

// VK_SHIFT
// VK_CONTROL
// VK_MENU
  bool ckey=false;

  /* translate keycode first */
  switch (keycod)
  {
  case VK_CONTROL:
    kd.ctrl0 = true; 
    kd.key = SWindowListener::Key_Control_L;
    break;
  case VK_LCONTROL:
    kd.ctrl0 = true; 
    kd.key = SWindowListener::Key_Control_L;
    break;
  case VK_RCONTROL:
    kd.ctrl1 = true; 
    kd.key = SWindowListener::Key_Control_R;
    break;
  case VK_SHIFT: 
     kd.shift0 = true; 
    kd.key = SWindowListener::Key_Shift_R;
    break;
  case VK_LSHIFT: 
    kd.key = SWindowListener::Key_Shift_L;
    kd.shift0 = true; 
    break;
  case VK_RSHIFT: 
    kd.key = SWindowListener::Key_Shift_R;
    kd.shift1 = true; 
    break;

  case VK_MENU: kd.key = SWindowListener::Key_Alt_L; kd.meta0 = true; break;
  case VK_LMENU: kd.key = SWindowListener::Key_Alt_L; kd.meta0 = true; break;
  case VK_RMENU: kd.key = SWindowListener::Key_Alt_R; kd.meta1 = true; break;

//VK_LWIN: kd.key = SWindowListener::Key_Meta_L; kd.meta0 = true; break;
//VK_RWIN: kd.key = SWindowListener::Key_Meta_R; kd.meta1 = true; break;
//VK_LBUTTON
//VK_RBUTTON

  case VK_TAB: kd.key = SWindowListener::Key_Tab; break;
  case VK_RETURN: kd.key = SWindowListener::Key_Return; break;
  case VK_ESCAPE: kd.key = SWindowListener::Key_Escape; break;
  case VK_CLEAR: kd.key = SWindowListener::Key_Clear; break;
  case VK_SPACE: kd.key = SWindowListener::Key_Space; break;
  case VK_PRIOR: kd.key = SWindowListener::Key_Prior; break;
  case VK_NEXT: kd.key = SWindowListener::Key_Next; break;
  case VK_END: kd.key = SWindowListener::Key_End; break;
  case VK_HOME: kd.key = SWindowListener::Key_Home; break;
  case VK_LEFT: kd.key = SWindowListener::Key_Left; break;
  case VK_UP: kd.key = SWindowListener::Key_Up; break;
  case VK_RIGHT: kd.key = SWindowListener::Key_Right; break;
  case VK_DOWN: kd.key = SWindowListener::Key_Down; break;
  case VK_DELETE: kd.key = SWindowListener::Key_Delete; break;
  case VK_BACK: kd.key = SWindowListener::Key_BackSpace; break;
  case VK_F1: kd.key = SWindowListener::Key_F1; break;
  case VK_F2: kd.key = SWindowListener::Key_F2; break;
  case VK_F3: kd.key = SWindowListener::Key_F3; break;
  case VK_F4: kd.key = SWindowListener::Key_F4; break;
  case VK_F5: kd.key = SWindowListener::Key_F5; break;
  case VK_F6: kd.key = SWindowListener::Key_F6; break;
  case VK_F7: kd.key = SWindowListener::Key_F7; break;
  case VK_F8: kd.key = SWindowListener::Key_F8; break;
  case VK_F9: kd.key = SWindowListener::Key_F9; break;
  case VK_F10: kd.key = SWindowListener::Key_F10; break;
  case VK_F11: kd.key = SWindowListener::Key_F11; break;
  case VK_F12: kd.key = SWindowListener::Key_F12; break;
/* VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */
/* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */
  case 'A': kd.key = SWindowListener::Key_A; ckey=true; break;
  case 'B': kd.key = SWindowListener::Key_B; ckey=true; break;
  case 'C': kd.key = SWindowListener::Key_C; ckey=true; break;
  case 'D': kd.key = SWindowListener::Key_D; ckey=true; break;
  case 'E': kd.key = SWindowListener::Key_E; ckey=true; break;
  case 'F': kd.key = SWindowListener::Key_F; ckey=true; break;
  case 'G': kd.key = SWindowListener::Key_G; ckey=true; break;
  case 'H': kd.key = SWindowListener::Key_H; ckey=true; break;
  case 'I': kd.key = SWindowListener::Key_I; ckey=true; break;
  case 'J': kd.key = SWindowListener::Key_J; ckey=true; break;
  case 'K': kd.key = SWindowListener::Key_K; ckey=true; break;
  case 'L': kd.key = SWindowListener::Key_L; ckey=true; break;
  case 'M': kd.key = SWindowListener::Key_M; ckey=true; break;
  case 'N': kd.key = SWindowListener::Key_N; ckey=true; break;
  case 'O': kd.key = SWindowListener::Key_O; ckey=true; break;
  case 'P': kd.key = SWindowListener::Key_P; ckey=true; break;
  case 'Q': kd.key = SWindowListener::Key_Q; ckey=true; break;
  case 'R': kd.key = SWindowListener::Key_R; ckey=true; break;
  case 'S': kd.key = SWindowListener::Key_S; ckey=true; break;
  case 'T': kd.key = SWindowListener::Key_T; ckey=true; break;
  case 'U': kd.key = SWindowListener::Key_U; ckey=true; break;
  case 'X': kd.key = SWindowListener::Key_X; ckey=true; break;
  case 'Y': kd.key = SWindowListener::Key_Y; ckey=true; break;
  case 'V': kd.key = SWindowListener::Key_V; ckey=true; break;
  case 'W': kd.key = SWindowListener::Key_W; ckey=true; break;
  case 'Z': kd.key = SWindowListener::Key_Z; ckey=true; break;
  default:
    kd.key = SWindowListener::Key_Send; ckey=true; break;
  }
  kdin-> key = kd.key;
  if (isdown)
  {
    kdin->shift0 = kd.shift0; kdin->shift1 = kd.shift1;
    kdin->meta0 = kd.meta0; kdin->meta1 = kd.meta1;
    kdin->ctrl0 = kd.ctrl0; kdin->ctrl0 = kd.ctrl0;
  }
  else
  {
    kdin->shift0 =  ! kd.shift0; kdin->shift1 = ! kd.shift1;
    kdin->meta0 =  ! kd.meta0; kdin->meta1 = ! kd.meta1;
    kdin->ctrl0 =  ! kd.ctrl0; kdin->ctrl0 = ! kd.ctrl0;
  }

  /*  */
  bool ctrl = kdin->ctrl0 || kdin->ctrl1; 
  bool shift = kdin->shift0 || kdin->shift1; 
  bool meta = kdin->meta0 || kdin->meta1; 

  SW32Window* wn = windowHashtable.get (currentFocusWindow);
  SWindowListener* ln = listenerHashtable.get (currentFocusWindow);

  if (wn != 0 || ln != 0)
  {
    SString s;
    /* WM_IME_COMPOSITION does not give us TAB */
    if (keycod == VK_TAB && isdown) 
    {
      s.append ("\t");
    }
    if (isdown)
    { 
      if (!sendAcceleratorPressed ((int) kdin->key, ctrl, shift, meta))
      {
         ln->keyPressed (wn, kdin->key, s, ctrl, shift, meta);
      }
    }
    else
    {
      if (!sendAcceleratorReleased ())
      {
        ln->keyReleased (wn, kdin->key, s, ctrl, shift, meta);
      }
    }
  }
}

SAccelerator  currentAccelerator;
bool accelPressed = false;

/**
 * add and remove keyboard accelerator
 */
void
SW32Window::addAccelerator (const SAccelerator& a, SAcceleratorListener* l)
{
  SW32Window* top = getToplevelWindow (this);
  top->accelerators.put (a.toString(), l);
  top->acceleratorTable.put (a.toString(), id);
}

void
SW32Window::removeAccelerator (const SAccelerator& a, SAcceleratorListener* l)
{
  SW32Window* top = getToplevelWindow (this);
  top->accelerators.remove (a.toString());
  top->acceleratorTable.remove (a.toString());
}

static bool
sendAcceleratorPressed (int key, bool ctrl, bool shift, bool meta)
{
  if (currentTopFocusWindow==0) return true;
  if (accelPressed) return true;

  SW32Window* top = windowHashtable.get (currentTopFocusWindow);
  if (top ==0) return true;

  currentAccelerator = SAccelerator (key, ctrl, shift, meta);
  long id = top->acceleratorTable.get (currentAccelerator.toString());

  if (id ==0)
  {
    return false;
  }
  SAcceleratorListener* l = top->accelerators.get (
          currentAccelerator.toString());
  if (l ==0)
  {
    return false;
  }
  accelPressed = true;
  l->acceleratorPressed (currentAccelerator);
  return true;
}

static bool
sendAcceleratorReleased ()
{
  if (currentTopFocusWindow==0) return false;
  if (!accelPressed) return false;

  SW32Window* top = windowHashtable.get (currentTopFocusWindow);
  if (top ==0)
  {
     accelPressed = false;
     return true;
  }

  long id = top->acceleratorTable.get (currentAccelerator.toString());
  SAcceleratorListener* l = top->accelerators.get (
          currentAccelerator.toString());
  if (l==0 || id ==0)
  { 
     accelPressed = false;
     return true;
  }
  accelPressed = false;
  l->acceleratorReleased (currentAccelerator);
  return true;
}
/**
 * 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
SW32Window::startInputMethod (const SString& name, const SProperties& prop, SPreEditor* preEditor)
{
  if (name == "x-none" || name == "x-ascii" || name == "x-utf-8")
  {
    if (imname == name) return true;
    stopInputMethod();
    imname = name;
    return true;
  }
  HIMC himc = ImmGetContext((HWND)id);
  if (!himc) return false;
  /* ImmSetOpenStatus */

  /* stop previous one */
  if (imname.size())
  {
     ImmSetOpenStatus (himc, false);
  }
  getKeyboardFocus();
  ImmSetOpenStatus (himc, true);
  setInputMethodProperties (prop);
  imname = name;
  return true;
}

void
SW32Window::stopInputMethod ()
{
  HIMC himc = ImmGetContext((HWND)id);
  if (!himc) return;
  /* stop previous one */
  if (imname.size())
  {
     ImmSetOpenStatus (himc, false);
  }
  imname = "";
}

/**
 * Change properties of the input method on the fly.
 * @param prop contains properties like:
 * InputStyle: root over-the-spot off-the-spot
 */
void
SW32Window::setInputMethodProperties (const SProperties& properties)
{
  if (!isVisible()) return;
  HIMC himc = ImmGetContext((HWND)id);
  if (!himc) return;

  if (properties.get ("InputStyle")==0)
  {
    fprintf (stderr, "InputStyle is not present in properties.\n");
    return;
  }

  SString s = properties["InputStyle"];

  /* ok. now I can tell you windows can not set InputStyle sorry */
   

  if (properties.get ("LineSpacing"))
  {
    SString lsp = properties["LineSpacing"];
    lsp.append ((char)0);
    int spacing;
    sscanf (lsp.array(), "%d", &spacing);
  }

  /* What to do with this? */
  if (properties.get ("InputClientColor"))
  {
    SString col = properties["InputClientColor"];
    col.append ((char)0);
    unsigned long bg, fg;
    sscanf (col.array(), "%lu,%lu", &bg, &fg);
    SColor xbg = SColor((SS_WORD32)bg);
    SColor xfg = SColor((SS_WORD32)fg);
  }

  /* XXX: no idea how to do this... */
  if (s == "preedit-over-status-under" 
       && properties.get ("InputSpot")
       && properties.get ("InputStatusLocation")
       && properties.get ("InputStatusSize")
     )
  {
    SString spotLocation = properties["InputSpot"];
    spotLocation.append ((char)0);
    int _x, _y;
    sscanf (spotLocation.array(), "%d,%d", &_x, &_y);
    COMPOSITIONFORM form;
    form.dwStyle = CFS_POINT;
    form.ptCurrentPos.x = _x;
    form.ptCurrentPos.y = _y;
    form.rcArea.left = 0;
    form.rcArea.top = 0;
    form.rcArea.right =  (int) getWidth();
    form.rcArea.bottom = (int) getHeight();
    ImmSetCompositionWindow (himc, &form);

    SString sl = properties["InputStatusLocation"];
    sl.append ((char)0);
    int statusX, statusY;
    sscanf (sl.array(), "%d,%d", &statusX, &statusY);

    SString ss = properties["InputStatusSize"];
    ss.append ((char)0);
    int statusWidth, statusHeight;
    sscanf (ss.array(), "%d,%d", &statusWidth, &statusHeight);

    POINT point;
    point.x = statusX;
    point.y = statusY;
    ImmSetStatusWindowPos(himc, &point);
  }
  else if (s == "preedit-under-status-under" 
       && properties.get ("InputSpot")
       && properties.get ("InputStatusLocation")
       && properties.get ("InputStatusSize")
       && properties.get ("InputClientLocation")
       && properties.get ("InputClientSize")
       )
  {

    SString spotLocation = properties["InputSpot"];
    spotLocation.append ((char)0);
    int _x, _y;
    sscanf (spotLocation.array(), "%d,%d", &_x, &_y);

    SString sl = properties["InputStatusLocation"];
    sl.append ((char)0);
    int statusX, statusY;
    sscanf (sl.array(), "%d,%d", &statusX, &statusY);

    SString ss = properties["InputStatusSize"];
    ss.append ((char)0);
    int statusWidth, statusHeight;
    sscanf (ss.array(), "%d,%d", &statusWidth, &statusHeight);

    SString cl = properties["InputClientLocation"];
    cl.append ((char)0);
    int clientX, clientY;
    sscanf (cl.array(), "%d,%d", &clientX, &clientY);

    SString cs = properties["InputClientSize"];
    cs.append ((char)0);
    int clientWidth, clientHeight;
    sscanf (cs.array(), "%d,%d", &clientWidth, &clientHeight);

    POINT point;
    point.x = statusX;
    point.y = statusY;
    ImmSetStatusWindowPos(himc, &point);

    COMPOSITIONFORM form;
    form.dwStyle = CFS_RECT;
    form.ptCurrentPos.x = clientX; // starting from.
    form.ptCurrentPos.y = clientY;
    form.rcArea.left = clientX;    // next line
    form.rcArea.top = clientY;
    form.rcArea.right =  clientX +  clientWidth;
    form.rcArea.bottom =  clientY + clientHeight;
    ImmSetCompositionWindow (himc, &form);
  }
  else if (s == "preedit-root-status-root")
  {
    COMPOSITIONFORM form;
    form.dwStyle = CFS_DEFAULT;
    form.ptCurrentPos.x = (int) getWidth();
    form.ptCurrentPos.y = (int) getHeight();
    form.rcArea.left = 0;
    form.rcArea.top = 0;
    form.rcArea.right =  (int) getWidth();
    form.rcArea.bottom = (int) getHeight();
    ImmSetCompositionWindow (himc, &form);
  }
  /* All the input styles */
  else if (s == "preedit-over-status-over" && properties.get ("InputSpot"))
  {
    SString spotLocation = properties["InputSpot"];
    spotLocation.append ((char)0);
    int _x, _y;
    sscanf (spotLocation.array(), "%d,%d", &_x, &_y);
    COMPOSITIONFORM form;
    form.dwStyle = CFS_POINT;
    form.ptCurrentPos.x = _x;
    form.ptCurrentPos.y = _y;
    form.rcArea.left = 0;
    form.rcArea.top = 0;
    form.rcArea.right =  (int) getWidth();
    form.rcArea.bottom = (int) getHeight();
    ImmSetCompositionWindow (himc, &form);
  }
}

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

unsigned long
SW32Window::getWindowID() const
{
  return (unsigned long) id;
}

// FIXME
void
SW32Window::setDoubleBuffer (bool isOn)
{
  dbufferOn = isOn;
}

bool
SW32Window::isDoubleBufferEnabled () const
{
  return dbufferOn;
}
// Canvas became virtual.
bool
SW32Window::cacheOn(bool on)
{
  bool old = isCacheOn;
  isCacheOn = on;
  return old;
}

SS_Matrix2D
SW32Window::getCurrentMatrix() const
{
  if (engine!=0) return engine->getCurrentMatrix();
  return SS_Matrix2D();
}
