// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_Windows_WinTk
#define tools_Windows_WinTk

#include "../Windows/tools"  //and not "tools", because this fancy VisualC++ will not find the file!

#include <tools/touplow>
#include <tools/words>
#ifdef TOOLS_MEM
#include <tools/mem>
#endif

#include <vector>

namespace tools {
namespace WinTk {

class Component;

class CallbackData {
public:
  CallbackData():x(0),y(0),wparam(0),lparam(0){}
public:
  std::string value;
  int x;
  int y;
  WPARAM wparam;
  LPARAM lparam;
};

typedef void (*Callback)(Component&,CallbackData&,void*);

class Component {
  typedef std::vector< std::pair<Callback,void*> > Callbacks;
  typedef std::pair<std::string,Callbacks> NamedCallbacks;
#ifdef TOOLS_MEM
  TOOLS_SCLASS(exlib::WinTk::Component)
#endif  
public:
  Component(const std::string& aType):fType(aType),fWindow(0),fParent(0){
#ifdef TOOLS_MEM
    tools::mem::increment(s_class().c_str());
#endif
  }
  Component(const std::string& aType,Component& aParent):fType(aType),fWindow(0),fParent(&aParent) {
#ifdef TOOLS_MEM
    tools::mem::increment(s_class().c_str());
#endif
  }
  virtual ~Component() {
#ifdef TOOLS_MEM
    tools::mem::decrement(s_class().c_str());
#endif
  }
protected:
  Component(Component&) {
#ifdef TOOLS_MEM
    tools::mem::increment(s_class().c_str());
#endif
  }
  Component& operator=(Component&) {return *this;}
public:
  std::string name() const {return fName;}
  void setName(const std::string& aName) {fName = aName;}

  const std::string& type() const {return fType;}
  HWND nativeWindow() {return fWindow;}
  Component* parent() const {return fParent;}
  virtual void show() { 
    //if(!fWindow) return;
    //::ShowWindow(fWindow,SW_SHOWDEFAULT);
  }
  void hide() { 
    if(!fWindow) return;
    ::ShowWindow(fWindow,SW_HIDE);
  }
  
  virtual bool size(unsigned int& aWidth,unsigned int& aHeight) { 
    aWidth = 0;
    aHeight = 0;
    if(!fWindow) return false;
    RECT rect;
    ::GetClientRect(fWindow,&rect);
    aWidth = rect.right-rect.left;
    aHeight = rect.bottom-rect.top;
    return true;
  }
  virtual bool position(int& aX,int& aY) { 
    aX = 0;
    aY = 0;
    if(!fWindow) return false;
    RECT rect;
    ::GetClientRect(fWindow,&rect);
    aX = rect.left;
    aY = rect.top;
    return true;
  }
  Component* findFather(const std::string& aType) { 
    if(aType==type()) return this;
    Component* parent = fParent;
    while(parent) {
      if(aType==parent->type()) return parent;
      parent = parent->fParent;
    }
    return 0;
  }

  bool setBackground(double aR,double aG,double aB){ 
    if(!fWindow) return false;

    int r = int(aR*255.0);
    if((r<0)||(r>255)) return false;
    int g = int(aG*255.0);
    if((g<0)||(g>255)) return false;
    int b = int(aB*255.0);
    if((b<0)||(b>255)) return false;
 
/*
    //COLORREF cr = RGB(r,g,b);
    HBRUSH brush = ::CreateSolidBrush(RGB(r,g,b));
    ::printf("debug : WinTk::Component::setBackground : brush  %lu\n",brush);
  
    HBRUSH prev = (HBRUSH)::GetClassLongPtr(fWindow,GCLP_HBRBACKGROUND);
    ::printf("debug : WinTk::Component::setBackground : prev brush  %lu\n",prev);
  
   {WNDCLASSEX wc;
    ::GetClassInfoEx(::GetModuleHandle(NULL),WC_STATIC,&wc);
    ::printf("debug : prev brush xxx %lu\n",wc.hbrBackground);}
  
    ULONG_PTR stat = 
      ::SetClassLongPtr(fWindow,GCLP_HBRBACKGROUND,(LONG_PTR)brush);
    if(prev && !stat) {
      ::printf("debug : WinTk::Component::setBackground : SetClassLongPtr failed.\n");
      return false;
    }
  
   {ULONG_PTR stat = 
    ::SetClassLongPtr(fWindow,GCLP_HBRBACKGROUND,(LONG_PTR)brush);
    ::printf("debug : WinTk::Component::setBackground : SetClassLongPtr failed.yyy %lu.\n",stat);}
  
   {WNDCLASSEX wc;
    ::GetClassInfoEx(::GetModuleHandle(NULL),WC_STATIC,&wc);
    ::printf("debug : prev brush yyy %lu\n",wc.hbrBackground);}
  
    if(!::InvalidateRect(fWindow,NULL,TRUE)) {
      ::printf("debug : WinTk::Component::setBackground : InvalidateRect failed.\n");
      return false;
    }
  
    //::DeleteObject(brush);
    return true;
  */
    return false;
  }
public:  
  void addCallback(const std::string& aName,Callback aFunction,void* aTag) {
    NamedCallbacks* cbks = 0;
    std::vector<NamedCallbacks>::iterator it;
    for(it=fCallbacks.begin();it!=fCallbacks.end();++it) {
      if(aName==(*it).first) {
        cbks = &(*it);
        break;
      }
    }
    if(!cbks) {
      fCallbacks.push_back(std::pair<std::string,Callbacks>(aName,Callbacks()));
      cbks = &(fCallbacks.back());
    }
    cbks->second.push_back(std::pair<Callback,void*>(aFunction,aTag));
  }
  void removeCallback(const std::string& aName,Callback aFunction,void* aTag) {
    NamedCallbacks* cbks = 0;
    std::vector<NamedCallbacks>::iterator it;
    for(it=fCallbacks.begin();it!=fCallbacks.end();++it) {
      if(aName==(*it).first) {
        cbks = &(*it);
        break;
      }
    }
    if(cbks) {
      Callbacks::iterator it;
      for(it=cbks->second.begin();it!=cbks->second.end();++it) {
        if( (aFunction==(*it).first) && (aTag==(*it).second) ) {
          cbks->second.erase(it);
          return;
        }
      }
    }
  }

  bool executeCallbacks(const std::string& aName,CallbackData& aData) {
    std::vector<NamedCallbacks>::const_iterator it;
    for(it=fCallbacks.begin();it!=fCallbacks.end();++it) {
      if(aName==(*it).first) {
        const Callbacks& cbks = (*it).second;
        Callbacks::const_iterator it2;
        for(it2=cbks.begin();it2!=cbks.end();++it2) {
          Callback cbk = (*it2).first;
          if(cbk) {
            HWND back_fWindow = fWindow;
            cbk(*this,aData,(*it2).second);
            // The callback may have destroyed the Component and
            // the native HWND. The below checks that.
            // In a proc, it is better to not use the HWND
            // (or the Component) after calling executeCallbacks.
            if(back_fWindow && !::GetWindowLongPtr(back_fWindow,GWLP_USERDATA)) {
              //::printf("WinTk::Component::executeCallbacks : WARNING : A callback destroyed the native HWND window !\n");
              return false;
            }
          }
        }      
        return true;
      }
    }
    return false;
  }
  bool hasCallbacks(const std::string& aName) const {
    std::vector<NamedCallbacks>::const_iterator it;
    for(it=fCallbacks.begin();it!=fCallbacks.end();++it) {
      if(aName==(*it).first) return true;
    }
    return false;
  }
protected:
  static void wm__destroy(HWND aWindow) { 
    Component* This = (Component*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
    if(This) { //How to be sure that we have a Component* ???
      if(This->fWindow!=aWindow) {
        ::printf("WinTk::Component::wm_destroy : HWND mismatch !\n");
      }
      if(This->hasCallbacks("WM_DESTROY")) {
        CallbackData data;
        This->executeCallbacks("WM_DESTROY",data);
      }
      This->fWindow = 0;
    }
    ::SetWindowLongPtr(aWindow,GWLP_USERDATA,LONG_PTR(NULL));
  }
protected:
  // WM_PARENTNOTIFY is sent within the CreateWindow of the child.
  // When received by the parent, the parent Post (not Send) to itself
  // a WM_TK_CHILDCREATED in order to be notify of the child creation
  // out of any CreateWindow.
#define WM_TK_CHILDCREATED ((WM_USER)+888)
#define WM_TK_CHILDDELETED ((WM_USER)+889)
#define TK_KEY 127 // To be sure that it is a message of our own.
  static LRESULT CALLBACK containerProc(HWND aWindow,UINT aMessage,WPARAM aWParam,LPARAM aLParam) {
    //////////////////////////////////////////////////////////////////////////////
    //  Some child send notification message to their parent (instead of sending
    // the message to themselves !). This procedure is used by containers 
    // to forward these messages to the child.
    //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
    switch(aMessage) { 
    case WM_PARENTNOTIFY:{
      if(aWParam==WM_CREATE) {
        ::PostMessage(aWindow,WM_TK_CHILDCREATED,TK_KEY,0);
        return 0;
      } else if(aWParam==WM_DESTROY) {
        ::PostMessage(aWindow,WM_TK_CHILDDELETED,TK_KEY,0);
        return 0;
      }
    }break;
    case WM_TK_CHILDCREATED:{
      if(aWParam==TK_KEY) {
        RECT rect;
        ::GetClientRect(aWindow,&rect);
        LPARAM size = MAKELPARAM(rect.right-rect.left,rect.bottom-rect.top);
        ::SendMessage(aWindow,WM_SIZE,(WPARAM)0,size);
        return 0;
      }
    }break;
    case WM_TK_CHILDDELETED:{
      if(aWParam==TK_KEY) {
      }
    }break;
    case WM_NOTIFY:{ //Coming from a child (combobox, scrollbar, toolbar).
      NMHDR* nmhdr = (NMHDR*)aLParam;
      HWND from_win = nmhdr->hwndFrom;
      if(from_win!=aWindow) {
        ::SendMessage(from_win,aMessage,aWParam,aLParam);
      }
    }return 0;
    case WM_COMMAND:
    case WM_VSCROLL: 
    case WM_HSCROLL:{ 
      ::SendMessage((HWND)aLParam,aMessage,aWParam,(LPARAM)0);
    }return 0;
    }
    return TRUE; //Not handled.
  }
#undef WM_TK_CHILDCREATED
#undef WM_TK_CHILDDELETED
#undef TK_KEY
protected:
  void destroy() { 
    if(hasCallbacks("delete")) {
      CallbackData data;
      executeCallbacks("delete",data);
    }
    if(fWindow) {
      ::SetWindowLongPtr(fWindow,GWLP_USERDATA,LONG_PTR(NULL));
      ::DestroyWindow(fWindow);
      fWindow = 0;
    }
  }
  void rubberDrawLine(POINT& aBegin,POINT& aEnd) { 
    if(!fWindow) return;
    HPEN pen = ::CreatePen(PS_SOLID,5,RGB(0,0,0));
    HDC hdc = ::GetWindowDC(fWindow);
    HPEN oldPen = SelectPen(hdc,pen);
    int oldROP = ::SetROP2(hdc,R2_NOT);
    POINT pt;
    ::MoveToEx(hdc,aBegin.x,aBegin.y,&pt);
    ::LineTo(hdc,aEnd.x,aEnd.y);
    ::SetROP2(hdc,oldROP);
    SelectPen(hdc,oldPen);
    ::ReleaseDC(fWindow,hdc);
    ::DeletePen(pen);
  }
  void rubberDrawRect(POINT& aBegin,POINT& aEnd) { 
    if(!fWindow) return;
    HPEN pen = ::CreatePen(PS_SOLID,0,RGB(0,0,0));
    HDC hdc = ::GetWindowDC(fWindow);
    HPEN oldPen = SelectPen(hdc,pen);
    int oldROP = ::SetROP2(hdc,R2_NOT);
    POINT pt;
    ::MoveToEx(hdc,aBegin.x,aBegin.y,&pt);
    ::LineTo(hdc,aBegin.x,aEnd.y);
    ::LineTo(hdc,aEnd.x,aEnd.y);
    ::LineTo(hdc,aEnd.x,aBegin.y);
    ::LineTo(hdc,aBegin.x,aBegin.y);
    ::SetROP2(hdc,oldROP);
    SelectPen(hdc,oldPen);
    ::ReleaseDC(fWindow,hdc);
    ::DeletePen(pen);
  }
private:
  std::string fType;
protected:
  HWND fWindow;
  Component* fParent;
  std::vector<NamedCallbacks> fCallbacks;
  std::string fName;
};

class Shell;
typedef void(*SetFocusCallback)(Shell*,void*);

class Shell : public Component {
public:
  Shell(unsigned int aMask)
  :Component("Shell")
  ,fFocusWindow(0)
  ,fSetFocusCallback(0)
  ,fSetFocusTag(0)
  ,fAcceleratorTable(0)
  {
    static char sWindowClassName[] = "WinTk::Shell";
    static bool done = false;
    if(!done) {
      WNDCLASS         wc;
      wc.style         = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc   = (WNDPROC)Shell::proc;
      wc.cbClsExtra    = 0;
      wc.cbWndExtra    = 0;
      wc.hInstance     = ::GetModuleHandle(NULL);
      wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
      wc.hCursor       = LoadCursor(NULL,IDC_ARROW);
      wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
      wc.lpszMenuName  = sWindowClassName;
      wc.lpszClassName = sWindowClassName;
      ::RegisterClass(&wc);
      done = true;
    }
  
    fWindow = ::CreateWindow(sWindowClassName,
                             NULL,
                             aMask, //WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT,CW_USEDEFAULT, 
                             400,400,
                             NULL,NULL,
                             ::GetModuleHandle(NULL),
                             NULL);
    if(!fWindow) return;
    ::SetWindowLongPtr(fWindow,GWLP_USERDATA,LONG_PTR(this));
  }
  virtual ~Shell() { 
    if(fAcceleratorTable) {
      ::DestroyAcceleratorTable(fAcceleratorTable);
      fAcceleratorTable = 0;
    }
    destroy();
  }
protected:
  Shell(Shell& a_from):Component(a_from) {}
  Shell& operator=(Shell&) {return *this;}
public:  
  virtual void show()  { 
    if(!fWindow) return;
    ::SetForegroundWindow(fWindow);
    ::ShowWindow(fWindow,SW_SHOWDEFAULT);
    ::UpdateWindow(fWindow);
    ::DrawMenuBar(fWindow);
  }
  void setTitle(const std::string& aString) { 
    if(!fWindow) return;
    ::SetWindowText(fWindow,aString.c_str());
  }
  void setGeometry(int aX,int aY,unsigned int aWidth,unsigned int aHeight) { 
    //////////////////////////////////////////////////////////////////////////////
    // Given width, height is the desired client area.
    //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
    if(!fWindow) return;
    int hborder = ::GetSystemMetrics(SM_CYCAPTION);
    if(::GetWindowLongPtr(fWindow,GWLP_ID)) // Has a menubar.
      hborder += ::GetSystemMetrics(SM_CYMENU);
    ::MoveWindow(fWindow,aX,aY,aWidth,hborder + aHeight,TRUE);
  }
  void setSize(unsigned int aWidth,unsigned int aHeight) { 
    //////////////////////////////////////////////////////////////////////////////
    // Given width, height is the desired client area.
    //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
    if(!fWindow) return;
    int hborder = ::GetSystemMetrics(SM_CYCAPTION);
    RECT rect;
    ::GetWindowRect(fWindow,&rect);
    ::MoveWindow(fWindow,rect.left,rect.top,aWidth,hborder + aHeight,TRUE);
  }
  void setFocusWindow(HWND aWindow) {fFocusWindow = aWindow;}
  HWND focusWindow() {return fFocusWindow;}

  void setSetFocusCallback(SetFocusCallback aCallback,void* aTag) { 
    fSetFocusCallback = aCallback;
    fSetFocusTag = aTag;
  }
  void callSetFocusCallback() { 
    if(!fSetFocusCallback) return;
    fSetFocusCallback(this,fSetFocusTag);
  }
  HACCEL nativeAcceleratorTable() {return fAcceleratorTable;}

  bool addAccelerator(const std::string& aString,int aID) { 
    //printf("debug : accel : %s %d\n",aString.c_str(),aID);
    // aString should be of the form : "a" or "Ctrl+a".
    ACCEL acc;
    //acc.fVirt : FALT, FCONTROL, FNOINVERT, FSHIFT, FVIRTKEY
   {acc.cmd = aID;
    std::vector<std::string> words;
    std::string s(aString);
    tools::touppercase(s);
    tools::words(s,"+",false,words);
    if(words.size()==1) {
      acc.fVirt = FVIRTKEY;
      if(!string_to_VK(words[0],acc.key)) return false;
    } else if((words.size()==2)&&(words[1].size())) {
      if(words[0]=="CTRL") {
        acc.fVirt = FCONTROL;
      } else if(words[0]=="ALT") {
        acc.fVirt = FALT;
      } else if(words[0]=="SHIFT") {
        acc.fVirt = FSHIFT;
      } else {
        return false;
      }
      acc.fVirt |= FVIRTKEY;
      if(!string_to_VK(words[1],acc.key)) return false;
    } else {
      return false;
    }}
  
    if(!fAcceleratorTable) {
      fAcceleratorTable = ::CreateAcceleratorTable(&acc,1);
    } else {
      int n = ::CopyAcceleratorTable(fAcceleratorTable,NULL,0);
      ACCEL* tbl = new ACCEL[n+1];
      ::CopyAcceleratorTable(fAcceleratorTable,tbl,n);
      ACCEL& tacc = tbl[n];
      tacc.fVirt = acc.fVirt;
      tacc.key = acc.key;
      tacc.cmd = acc.cmd;
      ::DestroyAcceleratorTable(fAcceleratorTable);
      fAcceleratorTable = ::CreateAcceleratorTable(tbl,n+1);
      delete [] tbl;
    }

    return true;
  }
private:

  static bool string_to_VK(const std::string& aString,WORD& a_VK) {
    //VK_xxx are defined in WinUser.h
    if(aString.size()==1) { a_VK = aString[0];return true;} //WM_W is 'W'

#define S_VK(aWhat) \
  if(!::strcmp(aString.c_str(),#aWhat)) { a_VK = VK_##aWhat;return true;}

    S_VK(BACK)
    S_VK(TAB)
    S_VK(CLEAR)
    S_VK(RETURN)
    S_VK(ESCAPE)
    S_VK(SPACE)

    S_VK(UP)
    S_VK(DOWN)
    S_VK(LEFT)
    S_VK(RIGHT)

    S_VK(F1)
    S_VK(F2)
    S_VK(F3)
    S_VK(F4)
    S_VK(F5)
    S_VK(F6)
    S_VK(F7)
    S_VK(F8)
    S_VK(F9)
    S_VK(F10)
    S_VK(F11)
    S_VK(F12)
    S_VK(F13)
    S_VK(F14)
    S_VK(F15)
    S_VK(F16)
    S_VK(F17)
    S_VK(F18)
    S_VK(F19)
    S_VK(F20)
    S_VK(F21)
    S_VK(F22)
    S_VK(F23)
    S_VK(F24)
  
    //FIXME : handle other keys ? NUMPAD[0-9]
  
#undef S_VK

    return false;
  }

  static LRESULT CALLBACK proc(HWND aWindow,UINT aMessage,WPARAM aWParam,LPARAM aLParam) {
    switch (aMessage) { 
    // Same logic as containerProc :
    case WM_NOTIFY:{ //Coming from a child (combobox, scrollbar, toolbar).
      NMHDR* nmhdr = (NMHDR*)aLParam;
      HWND from_win = nmhdr->hwndFrom;
      if(from_win!=aWindow) {
        ::SendMessage(from_win,aMessage,aWParam,aLParam);
      }
    }return 0;
    // Else :
    case WM_SIZE:{ // Assume one child window ! FIXME : have a message if not.
      int width = LOWORD(aLParam);
      int height = HIWORD(aLParam);
      HWND hwnd = GetFirstChild(aWindow);
      if(hwnd) {
        //FIXME : have to treat the case of TOOLBAR not being the first.
        if(GetClassName(hwnd)==std::string(TOOLBARCLASSNAME)) {
          HWND next = GetNextSibling(hwnd);
          // Share area between toolbar and second child :
          RECT rect;
          ::GetWindowRect(hwnd,&rect);
          int htb = rect.bottom-rect.top;
          SetGeometry(next,0,htb,width,height-htb);
        } else {
          SetGeometry(hwnd,0,0,width,height);
        }
      }
    }return 0;
    case WM_SETFOCUS:{
      Shell* This = (Shell*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
      if(This) {
        This->callSetFocusCallback();
        HWND win = This->focusWindow();
        if(win) ::SetFocus(win);
      }
    }return 0;
    case WM_INITMENUPOPUP:{ //0x0117
      //aWParam is the HMENU (from CreatePopupMenu)
      //aLParam is the MenuItemCount
      Shell* This = (Shell*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
      if(This) {
        CallbackData data;
        data.wparam = aWParam;
        This->executeCallbacks("cascading",data);
      }
      } return 0;
    case WM_COMMAND:{ //Coming from a menu item, a toolbar !
      if(aLParam) {
        // From a toolbar ; send it back to it :   
        ::SendMessage((HWND)aLParam,aMessage,aWParam,0);
      } else {
        //if(HIWORD(aWParam)==1) { //From an accelerator.
        //}
        // From a menu item :
        Shell* This = (Shell*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
        if(This) {
          CallbackData data;
          data.wparam = LOWORD(aWParam);
          This->executeCallbacks("activate",data);
        }
      }
    }return 0;
    case WM_CLOSE:{
      Shell* This = (Shell*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
      if(This) {
        if(This->hasCallbacks("close")) {
          CallbackData data;
          This->executeCallbacks("close",data);
        }
      }
    }break; //NOTE : can't be return 0.
    case WM_DESTROY:wm__destroy(aWindow);return 0;
    }
    return (DefWindowProc(aWindow,aMessage,aWParam,aLParam));
  }
private:
  HWND fFocusWindow;
  SetFocusCallback fSetFocusCallback;
  void* fSetFocusTag;
  HACCEL fAcceleratorTable;
};

class OpenGLArea : public Component {
public:
  OpenGLArea(Component& aParent)
  :Component("OpenGLArea",aParent)
  ,fContext(0)
  ,fHDC(0)
  {
    if(!fParent || !fParent->nativeWindow()) return;
    HWND parent = fParent->nativeWindow();

    static char sOpenGLAreaClassName[] = "WinTk::OpenGLArea";
    static bool done = false;
    if(!done) {
      WNDCLASS         wc;
      wc.style         = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc   = (WNDPROC)OpenGLArea::proc;
      wc.cbClsExtra    = 0;
      wc.cbWndExtra    = 0;
      wc.hInstance     = GetWindowInstance(parent),
      wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
      wc.hCursor       = LoadCursor(NULL,IDC_ARROW);
      wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
      wc.lpszMenuName  = sOpenGLAreaClassName;
      wc.lpszClassName = sOpenGLAreaClassName;
      ::RegisterClass(&wc);
      done = true;
    }
    
    // The WS_BORDER is needed. Else probleme of size at startup.
    RECT rect;
    ::GetClientRect(parent,&rect);
    fWindow = ::CreateWindow(sOpenGLAreaClassName,
                             NULL,
                             WS_CHILD | WS_VISIBLE,
                             0,0,
                             rect.right-rect.left,
                             rect.bottom-rect.top,
                             parent,NULL,
                             GetWindowInstance(parent),
                             NULL);
    if(!fWindow) return;
    ::SetWindowLongPtr(fWindow,GWLP_USERDATA,LONG_PTR(this));
  
    // initialize OpenGL rendering :
    fHDC = ::GetDC(fWindow);
    if( fHDC && SetWindowPixelFormat(fHDC) ) {
      fContext = ::wglCreateContext(fHDC);
    }
  }
  virtual ~OpenGLArea() { 
    if(wglGetCurrentContext()!=NULL) wglMakeCurrent(NULL,NULL);
    if(fContext)        {
      wglDeleteContext(fContext);
      fContext = 0;
    }
    destroy();
  }
protected:
  OpenGLArea(OpenGLArea& a_from):Component(a_from) {}
  OpenGLArea& operator=(OpenGLArea&) {return *this;}
public:
  bool write_gl2ps(const std::string&,const std::string&);
private:
  static LRESULT CALLBACK proc(HWND aWindow,UINT aMessage,WPARAM aWParam,LPARAM aLParam) {
    switch (aMessage) { 
    case WM_PAINT:{
      PAINTSTRUCT ps;
      HDC hDC = BeginPaint(aWindow,&ps);
      OpenGLArea* This = (OpenGLArea*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
      if(This) {
        HDC hdc = This->fHDC;
        HGLRC context = This->fContext;
        if(hdc && context) {
          ::wglMakeCurrent(hdc,context);
          // User OpenGL paint code :
  
          CallbackData data;
          data.wparam = aWParam;
          data.lparam = aLParam;
          This->executeCallbacks("paint",data);
  
          ::SwapBuffers(hdc);
        }
      }
      EndPaint(aWindow,&ps);
    }return 0;
    case WM_LBUTTONDOWN:{
      OpenGLArea* This = (OpenGLArea*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
      if(This) {
        CallbackData data;
        data.wparam = aWParam;
        data.lparam = aLParam;
        data.value = "ButtonPress";
        data.x = LOWORD(aLParam);
        data.y = HIWORD(aLParam);
        This->executeCallbacks("event",data);
      }
    }return 0;
    case WM_LBUTTONUP:{
      OpenGLArea* This = (OpenGLArea*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
      if(This) {
        CallbackData data;
        data.wparam = aWParam;
        data.lparam = aLParam;
        data.value = "ButtonRelease";
        data.x = LOWORD(aLParam);
        data.y = HIWORD(aLParam);
        This->executeCallbacks("event",data);
      }
    } return 0;
    case WM_MOUSEMOVE:{
      unsigned int state = aWParam;
      if((state & MK_LBUTTON)==MK_LBUTTON) {
        OpenGLArea* This = (OpenGLArea*)::GetWindowLongPtr(aWindow,GWLP_USERDATA);
        if(This) {
          CallbackData data;
          data.wparam = aWParam;
          data.lparam = aLParam;
          data.value = "MotionNotify";
          data.x = LOWORD(aLParam);
          data.y = HIWORD(aLParam);
          This->executeCallbacks("event",data);
        }
      }
    }return 0;
    case WM_DESTROY:wm__destroy(aWindow);return 0;
    }
    return (DefWindowProc(aWindow,aMessage,aWParam,aLParam));
  }
  static bool SetWindowPixelFormat(HDC aHdc) {
    PIXELFORMATDESCRIPTOR pfd;
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion = 1;
    pfd.dwFlags = 
      PFD_DRAW_TO_WINDOW | 
      PFD_SUPPORT_OPENGL | 
      PFD_DOUBLEBUFFER | 
      PFD_STEREO_DONTCARE;  
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;
    pfd.cRedBits = 8;
    pfd.cRedShift = 16;
    pfd.cGreenBits = 8;
    pfd.cGreenShift = 8;
    pfd.cBlueBits        = 8;
    pfd.cBlueShift = 0;
    pfd.cAlphaBits = 0;
    pfd.cAlphaShift = 0;
    pfd.cAccumBits = 64;        
    pfd.cAccumRedBits = 16;
    pfd.cAccumGreenBits = 16;
    pfd.cAccumBlueBits = 16;
    pfd.cAccumAlphaBits = 0;
    pfd.cDepthBits = 32;
    pfd.cStencilBits = 8;
    pfd.cAuxBuffers = 0;
    pfd.iLayerType = PFD_MAIN_PLANE;
    pfd.bReserved        = 0;
    pfd.dwLayerMask = 0;
    pfd.dwVisibleMask = 0;
    pfd.dwDamageMask = 0;
    
    int pixelIndex = ::ChoosePixelFormat(aHdc,&pfd);
    if (pixelIndex==0) {
      // Let's choose a default index.
      pixelIndex = 1;        
      if (::DescribePixelFormat(aHdc, 
                                pixelIndex, 
                                sizeof(PIXELFORMATDESCRIPTOR), 
                                &pfd)==0) {
        return false;
      }
    }
  
    if (::SetPixelFormat(aHdc,pixelIndex,&pfd)==FALSE) return false;
    return true;
  }
private:
  HGLRC fContext;
  HDC fHDC;
};

}}

#endif
