// HQWidget.cpp - Implementation of the HOOPS/Qt class HQWidget
//
// More about this class
 

// Headers

// System Includes
#include <stdlib.h>
#include <stdio.h>
 

// hoops_mvo includes
#include "HBaseOperator.h"
#include "HDB.h"
#include "HBaseView.h"
#include "HEventInfo.h"

// hoops includes
#include "hc.h"
 

#if IS_X11
// X11 Includes
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/Xmu/StdCmap.h>

#if USE_GLX_VISUAL

#define INT8  dummy_INT8
#define INT32 dummy_INT32

#include <GL/glx.h>

#undef  INT8
#undef  INT32

#endif // USE_GLX_VISUAL

#include "qintdict.h"

#endif // IS_X11

// qt includes
#include "qevent.h"

// HOOPS/Qt Include
#include "HQWidget.h"

// pointer to an HOOPS/MVO HDB object 
// needs to be created first in the application
extern HDB * m_pHDB;
 

HQWidget::HQWidget( QWidget *parent, const char *name, WFlags f )
    : QWidget(parent, name, f)
{

 // More about this class

 // make sure that no Paint or Resize events call HC_Update_Display
 // before we finish initializing the object
 initDone = false;

 // Make certain we have a valid paint device
 if ( this == 0 ) {
  warning( "HQWidget: Paint device cannot be null" );
 }
 if ( this->devType() != PDT_WIDGET && 
     this->devType() != PDT_PIXMAP ) {
    warning( "HQWidget: Unsupported paint device type" );
 }

 // Initialize the pointers to associated HOOPS/MVO 
 // Model and View objects
 m_pHBaseModel = 0;
 m_pHView = 0; 

 // Initialize Colormap and Window ID resources
 my_colormap = 0;
 my_windowid = 0;

 // this does all the work for palettes and windowid
 setup_window();

 setBackgroundMode( NoBackground );
}

HQWidget::~HQWidget()
{
    // empty destructor
}

long HQWidget::GetColorMap()
{
 // Accessor method for private member
 return my_colormap;
}

long HQWidget::GetWindowId()
{
 // Accessor method for private member
 return my_windowid;
}

void HQWidget::paintEvent( QPaintEvent * )
{

 // make certain the constructor was able to finish initialization
 // which means the Window Id and color map are valid and we can 
 // create a HOOPS/MVO HBaseView object. 
 if ( !initDone ) {

  // Say Base blahh - View init can finally happen
  Init();
  initDone = true;
 }

 // initiate the HOOPS/3D Graphics System update cycle
 HC_Update_Display();

}

void HQWidget::resizeEvent( QResizeEvent * )
{
 // make certain the constructor was able to finish initialization
 if ( initDone ) 
   // initiate the HOOPS/3D Graphics System update cycle
   HC_Update_Display();
}
 

unsigned long HQWidget::state2flags( unsigned long state )
{
 // Map Qt Mouse button state to HOOPS/MVO flags 

 unsigned long flags=0;

 // map the qt event's state to HOOPS/MVO flags
 if(state & LeftButton) flags|=MVO_LBUTTON;
 if(state & RightButton) flags|=MVO_RBUTTON;
 if(state & MidButton) flags|=MVO_MBUTTON;
 if(state & ShiftButton) flags|=MVO_SHIFT;
 if(state & ControlButton) flags|=MVO_CONTROL;

 return flags;
}

void HQWidget::mousePressEvent( QMouseEvent * e)
{
 // The HQWidget Object has an associated HOOPS/MVO View Object
 // Pass the Qt Mouse Left Button Events to the associated 
 // HOOPS/MVO View Object's current Operator. Right Button events 
 // are sent to a Pop-Up dialog elsewhere and should not make it this 
 // far - if they do make it this far, they are sent to a null method. 

 // Left Button Mouse Event
 if(e->button() == LeftButton) {

  // Find the HOOPS/MVO View associated with the HQWidget and call
  // its method for Left Button Down with 
  // the Qt event's state information
  if (m_pHView) {
   HEventInfo event(m_pHView);
   HBaseOperator *op = m_pHView->GetCurrentOperator();

   if (op) {
    unsigned long flags = state2flags(e->state());
    event.SetPoint(HE_LButtonDown, e->x(), e->y(), flags);
    op->OnLButtonDown(event);
   }
  }
 }else{

 // Right Button Mouse Event
 if(e->button() == RightButton) 

  // call HQWidget's Right Button Down method - a no-op
  OnRightButtonDown();

  // Note: if you want to use the HOOPS/MVO Operator's 
  // Right Mouse Down Event Handler, call it instead 
  // using the same approach used to call op->OnLButtonDown above.
 

 }
}
 

void HQWidget::mouseReleaseEvent( QMouseEvent * e)
{
 // The HQWidget Object has an associated HOOPS/MVO View Object
 // Pass the Qt Mouse Left Button Events to the associated 
 // HOOPS/MVO View Object's current Operator. 
 // Right Button events are sent to a Pop-Up dialog elsewhere and 
 // should not make it this far - if they do make it this far, they 
 // are sent to a null method. 

 // Left Button Mouse Event
 if(e->button() == LeftButton) {

  // Find the HOOPS/MVO View associated with the HQWidget and call
  // its method for Left Button Up with 
  // the Qt event's state information
  if (m_pHView) {
   HEventInfo event(m_pHView);
   HBaseOperator *op = m_pHView->GetCurrentOperator();

   if (op) {
    unsigned long flags = state2flags(e->state());
    event.SetPoint(HE_LButtonUp, e->x(), e->y(), flags);
    op->OnLButtonUp(event);
   }
  }
 }
 // Right Button Mouse Event
 else{
  // call HQWidget's Right Button Up method - a no-op
  if(e->button() == RightButton) OnRightButtonUp();
 }
}

void HQWidget::OnRightButtonDown()
{
 // Virtual method - overload in derived class to handle 
 // this event
}

void HQWidget::OnRightButtonUp()
{
 // Virtual method - overload in derived class to handle 
 // this event
}

void HQWidget::mouseMoveEvent( QMouseEvent * e)
{

 // The HQWidget Object has an associated HOOPS/MVO View Object
 // Pass the Qt Mouse Move Button Events to the associated 
 // HOOPS/MVO  View Object's current Operator. 

  // Find the HOOPS/MVO View associated with the HQWidget and call
  // its method for OnMouseMove with the Qt event's state information
  if (m_pHView) {
  HEventInfo event(m_pHView);
  HBaseOperator *op = m_pHView->GetCurrentOperator();

  if (op) {
   unsigned long flags = state2flags(e->state());
   event.SetPoint(HE_MouseMove, e->x(), e->y(), flags);
   op->OnMouseMove(event);
  }
 }
}
 
 

void HQWidget::keyPressEvent ( QKeyEvent * e )
{
 // The HQWidget Object has an associated HOOPS/MVO View Object
 // Pass the Qt Key Press Events to the associated HOOPS/MVO View
 // Object's current Operator. 

 // Find the HOOPS/MVO View associated with the HQWidget and call
 // its method for OnKeyDown with the Qt event's state information
 if (m_pHView) {
  HEventInfo  event(m_pHView);
  HBaseOperator *op = m_pHView->GetCurrentOperator();

  if (op) {
   unsigned long flags = state2flags(e->state());

   //these casts are nessary to get the right Set
   event.SetKey(HE_KeyDown, (unsigned int)e->ascii(), 
        (unsigned int)1, flags);
   op->OnKeyDown(event);
  }
 }
}

void HQWidget::keyReleaseEvent ( QKeyEvent * e )
{

 // The HQWidget Object has an associated HOOPS/MVO View Object
 // Pass the Qt Key Release Events to the associated HOOPS/MVO View
 // Object's current Operator. 

 // Find the HOOPS/MVO View associated with the HQWidget and call
 // its method for OnKeyDown with the Qt event's state information
 if (m_pHView) {
  HEventInfo  event(m_pHView);
  HBaseOperator *op = m_pHView->GetCurrentOperator();

  if (op) {
   unsigned long flags = state2flags(e->state());

   //these casts are nessary to get the right Set
   event.SetKey(HE_KeyUp, (unsigned int)e->ascii(), 
        (unsigned int)1, flags);
   op->OnKeyUp(event);
  }
 }
}
 

void HQWidget::Init()
{
 // this is overloaded by the derived class MyHQWidget and the actual
 // creation of the HBaseView object is performed there. (Go there.)
 // This gives maximum flexibility of how the application connects
 // HBaseModel, HBaseView and HQWidget objects to each other.

}

#if IS_WIN
// Windows window id and color palette configuration
void HQWidget::setup_window()
{

 // Find the MS Windows Window ID; to be passed to the 
 // HOOPS/3D Graphics Systemt

 my_windowid = (long)((QWidget *) this)->winId();

 // Colormap set to null
 my_colormap = 0;

}
#endif
 

#if IS_X11
// below is the ugly stuff for X11 window id and palette

#ifndef USE_GLX_VISUAL
 

#ifdef __cplusplus
#   define get_visual_class(vis_ptr)    ((vis_ptr)->c_class)
#else
#   define get_visual_class(vis_ptr)    ((vis_ptr)->class)
#endif

#define INITIAL_VALUE       (-1)

static void get_best_visual (
 Display     *display,
 XVisualInfo *visual) {
 XVisualInfo     f_template;
 XVisualInfo *   visuals;
 int         visual_count;
 int     best = INITIAL_VALUE;

 // Find the best X11 visual available; used to configure the X11
 // Window into which the HOOPS/3D Graphics System will draw

 // Get a list of the deepest possible visuals
 f_template.screen = DefaultScreen (display);
 f_template.depth = 24;

 do
  visuals = XGetVisualInfo (display, VisualScreenMask | VisualDepthMask,
  &f_template, &visual_count);
  while(! (visual_count > 0 || --f_template.depth == 0));

 while(! (visual_count-- == 0)) {

  if (visuals[visual_count].depth > 8) {
   switch (get_visual_class (&visuals[visual_count])) {
   case TrueColor: {
   best = visual_count;
   }   break;

   case DirectColor: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == PseudoColor ||
   get_visual_class (&visuals[best]) == StaticColor ||
   get_visual_class (&visuals[best]) == GrayScale ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case PseudoColor: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == StaticColor ||
   get_visual_class (&visuals[best]) == GrayScale ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case StaticColor: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == GrayScale ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case GrayScale: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case StaticGray: {
   if (best == INITIAL_VALUE)
   best = visual_count;
   }   break;
   }
   }
   else {
   switch (get_visual_class (&visuals[visual_count])) {
   case PseudoColor: {
   best = visual_count;
   }   break;

   case StaticColor: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == TrueColor ||
   get_visual_class (&visuals[best]) == DirectColor ||
   get_visual_class (&visuals[best]) == GrayScale ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case TrueColor: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == DirectColor ||
   get_visual_class (&visuals[best]) == GrayScale ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case DirectColor: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == GrayScale ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case GrayScale: {
   if (best == INITIAL_VALUE ||
   get_visual_class (&visuals[best]) == StaticGray)
   best = visual_count;
   }   break;

   case StaticGray: {
   if (best == INITIAL_VALUE)
   best = visual_count;
   }   break;
   }/*switch*/
  }/*if*/
 } while (--visual_count >= 0);/*until*/

 if (best != INITIAL_VALUE) {
  //*visual = visuals[best].visual;
  memcpy((void*)visual, (void*) &(visuals[best]), sizeof(XVisualInfo));
  XFree ((char *) visuals);
 } 
}

#endif

struct CMapEntry {
 // colormap accounting for x11
 // the msdev browser show this in err it's in #ifdef IS_X11

   CMapEntry( Colormap m, bool a ) : cmap(m), alloc(a) {}
   ~CMapEntry();
    Colormap cmap;
    bool     alloc;
};

CMapEntry::~CMapEntry()
{
    if ( alloc )
  XFreeColormap( QPaintDevice::x__Display(), cmap );
}

static bool cmap_init = FALSE;
static QIntDict<CMapEntry> *cmap_dict = 0;

static void cleanup_cmaps()
{
    if ( !cmap_dict ) return;

    cmap_dict->setAutoDelete( TRUE );
    delete cmap_dict;
    cmap_dict = 0;
}

static Colormap choose_cmap( Display *dpy, XVisualInfo *vi )
{
    if ( !cmap_init ) {
  cmap_init = TRUE;
  cmap_dict = new QIntDict<CMapEntry>;
  qAddPostRoutine (cleanup_cmaps);
 }

 CMapEntry *x = cmap_dict->find( (long)vi->visualid );

 // found colormap for visual
 if ( x ) 
  return x->cmap;

 Colormap cmap = 0;

 bool alloc = false;
 XStandardColormap *c;
 int n, i;

 // is this the delault vis 
 if ( vi->visual==DefaultVisual(dpy,vi->screen) )
  return DefaultColormap( dpy, vi->screen );

 // try to find a shared colormap
 if ( !cmap ) {
  if ( XmuLookupStandardColormap(dpy,vi->screen,vi->visualid,vi->depth,
       XA_RGB_DEFAULT_MAP,FALSE,TRUE) ) {
   if ( XGetRGBColormaps(dpy,RootWindow(dpy,vi->screen),&c,&n,
      XA_RGB_DEFAULT_MAP) ) {

    i = 0;
    while ( i < n && cmap == 0 ) {
     if ( c[i].visualid == vi->visualid )
     cmap = c[i].colormap;
     i++;
    }

    XFree( (char *)c );
   }
  }
 }

 // no shared cmap found
 if ( !cmap ) {
  cmap = XCreateColormap( dpy, RootWindow(dpy,vi->screen), vi->visual,
    AllocNone );
  alloc = TRUE;
 }

 // add our new colormap to the lookup for next time
 x = new CMapEntry( cmap, alloc ); 
 cmap_dict->insert( (long)vi->visualid, x );

 return cmap;
}
 
 

void HQWidget::setup_window()
{
 // Find the right X11 visual and colormap. 
 // Different approach needed if using OpenGL - handle this case.

 Colormap cmap;

 bool visible = isVisible();

 if (visible) hide();

 Display * dpy=this->x11Display();

#if USE_GLX_VISUAL
 static int dbvisual[] = {
    GLX_RGBA,
    GLX_RED_SIZE, 1,
    GLX_GREEN_SIZE, 1,
    GLX_BLUE_SIZE, 1,
    GLX_DEPTH_SIZE, 1,
    GLX_DOUBLEBUFFER,
    None
};

 XVisualInfo *vi = glXChooseVisual(dpy, DefaultScreen(dpy), dbvisual);

#else 

 // get our own X visual
 XVisualInfo visual;
 get_best_visual(dpy, &visual);
 XVisualInfo *vi = &visual;

#endif

 // create and initialize XSetWindowAttributes object
 XSetWindowAttributes a;

 // find best colormap
 cmap = choose_cmap( dpy, vi );
 a.colormap = cmap;

 // Set Background and border color
 a.background_pixel = backgroundColor().pixel();
 a.border_pixel = black.pixel();

 // Create and initialize an X11 Window
 Window p = RootWindow( dpy, DefaultScreen(dpy) );
 if ( parentWidget() )
 p = parentWidget()->winId();

 Window w = XCreateWindow( dpy, p,  x(), y(), width(), height(),
         0, vi->depth, InputOutput,  vi->visual,
         CWBackPixel|CWBorderPixel|CWColormap, &a );

 // record for later
 my_windowid = w;

 // X11 Color Map Configuration 
 Window wtl = topLevelWidget()->winId();
 Window *cmw;
 Window *cmwret;
 int count;

 // Get list of display's X11 Color Map Windows 
 if ( XGetWMColormapWindows(dpy, wtl,&cmwret,&count) ){

  cmw = new Window[count+1];
  memcpy( (char *)cmw, (char *)cmwret, sizeof(Window)*count );

  XFree( (char *)cmwret );

  int i;
  for ( i=0; i<count; i++ ) {
   if ( cmw[i] == winId() ) {  // replace old window
   cmw[i] = w;
   break;
   }
 }

 if ( i >= count )   // append new window
  cmw[count++] = w;
 } 
 else {
  count = 1;
  cmw = new Window[count];
  cmw[0] = w;
 }

    create( w );

    XSetWMColormapWindows( dpy, topLevelWidget()->winId(), cmw, count );

    delete [] cmw;

    if ( visible ) show();

    XFlush(dpy);

 //record for later
 my_colormap = cmap;

}

#endif //IS_X11