/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2000-2002 by Andreas Zehender
    email                : zehender@kde.org
**************************************************************************

**************************************************************************
*                                                                        *
*  This program is free software; you can redistribute it and/or modify  *
*  it under the terms of the GNU General Public License as published by  *
*  the Free Software Foundation; either version 2 of the License, or     *
*  (at your option) any later version.                                   *
*                                                                        *
**************************************************************************/

#include "pmglview.h"
#include "pmpart.h"
#include "pmrendermanager.h"
#include "pmcamera.h"
#include "pmglviewconnector.h"
#include "pmscene.h"
#include "pmdatachangecommand.h"

#include <math.h>
#include <qpopupmenu.h>
#include <qpainter.h>
#include <qapplication.h>
#include <qcursor.h>
#include <qcolor.h>
#include <qglobal.h>
#include <qlayout.h>
#include <qlabel.h>

#include <kxmlguifactory.h>
#include <kaction.h>
#include <kconfig.h>
#include <kstaticdeleter.h>
#include <klocale.h>

#include <kdebug.h>
#include "pmglobals.h"
#include "pmdefaults.h"

#include <GL/glx.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/Xmu/StdCmap.h>

const double c_sizeFactor = log( 2 ) / 100.0;
const int c_mouseChangeDelayPixel = 3;
const int c_mouseChangeDelayMs = 300;
const int c_multipleSelectDelayPixel = 3;
const double c_defaultAutoScrollSpeed = 200; // pixels per second
const int c_minAutoScrollUpdateTime = 30; //ms

const double keyMoveSpeed = 40.0;
const double keyScaleFactor = 1.4;

static int glxAttributeList[] = { GLX_LEVEL, 0,
                                  GLX_DOUBLEBUFFER,
                                  GLX_RGBA,
                                  GLX_RED_SIZE, 1,
                                  GLX_GREEN_SIZE, 1,
                                  GLX_BLUE_SIZE, 1,
                                  //GLX_ALPHA_SIZE, 1,
                                  GLX_DEPTH_SIZE, 1,
                                  None };

class PMGLViewStatic
{
public:
   PMGLViewStatic( )
   {
      m_colormap = 0;
      m_context = NULL;
      m_colormapAllocated = false;
      m_display = 0;
      m_visualInfo = 0;
   }
   ~PMGLViewStatic( )
   {
      if( m_colormapAllocated )
         XFreeColormap( m_display, m_colormap );
      if( m_context != NULL )
         glXDestroyContext( m_display, m_context );
      if( m_visualInfo )
         XFree( m_visualInfo );
   }
   
   Colormap m_colormap;
   GLXContext m_context;
   bool m_colormapAllocated;
   Display* m_display;
   XVisualInfo* m_visualInfo;
};

static PMGLViewStatic* s_pSharedData = 0;
static KStaticDeleter<PMGLViewStatic> s_staticDeleter;


PMGLView::PMGLView( PMPart* part, PMGLViewConnector* conn, PMViewType t,
                    QWidget* parent, const char* name, WFlags f )
      : QWidget( parent, name, f | Qt::WWinOwnDC | Qt::WRepaintNoErase )
{
   m_pPart = part;
   m_pConnector = conn;
   m_type = t;
   m_bScaleMode = false;
   m_scaleIntX = 0.0;
   m_scaleIntY = 0.0;
   m_bTranslateMode = false;
   m_bGraphicalChangeMode = false;
   m_bMousePressed = false;
   m_bMidMousePressed = false;
   m_dTransX = 0.0;
   m_dTransY = 0.0;
   m_dScale = 30.0;
   m_bInverseValid = false;
   m_pActiveObject = part->activeObject( );
   m_bMementoCreated = false;
   m_bSelectUnderMouse = false;
   m_bDeselectUnderMouse = false;
   m_bMultipleSelectionMode = false;
   m_bSelectionStarted = false;
   m_bAutoScroll = false;
   m_autoScrollSpeed = c_defaultAutoScrollSpeed;
   m_bAboutToUpdate = false;
   m_projectionUpToDate = false;
   m_contextClickPosition = PMVector( 0.0, 0.0 );

   m_controlPointsPosition.setAutoDelete( true );
   m_pUnderMouse = 0;

   setCamera( m_pPart->firstCamera( ) );

   initializeGL( );

   setMouseTracking( true );
   setFocusPolicy( WheelFocus );

   PMRenderManager* rm = PMRenderManager::theManager( );
   rm->viewCreated( );

   setMinimumSize( 50, 50 );

   connect( part, SIGNAL( refresh( ) ), SLOT( slotRefresh( ) ) );
   connect( part, SIGNAL( clear( ) ), SLOT( slotClear( ) ) );
   connect( this, SIGNAL( objectChanged( PMObject*, const int, QObject* ) ),
            part, SLOT( slotObjectChanged( PMObject*, const int, QObject* ) ) );
   connect( part, SIGNAL( activeRenderModeChanged( ) ),
            SLOT( slotActiveRenderModeChanged( ) ) );


   // Connect this slot to the view and not to the part.
   // The class PMGLViewConnector updates the control points in
   // slotObjectChanged, and emits the signals newControlPoints and
   // glViewObjectChanged.
   connect( conn, SIGNAL( glViewObjectChanged( PMObject*, const int, QObject* ) ),
            SLOT( slotObjectChanged( PMObject*, const int, QObject* ) ) );
   connect( conn, SIGNAL( newControlPoints( const PMControlPointList&, const PMMatrix& ) ),
            SLOT( slotNewControlPoints( const PMControlPointList&, const PMMatrix& ) ) );
   connect( conn, SIGNAL( stopRendering( ) ), SLOT( slotStopRendering( ) ) );
   connect( this, SIGNAL( viewActivated( PMGLView* ) ),
            conn, SLOT( slotViewActivated( PMGLView* ) ) );
   connect( this, SIGNAL( controlPointSelectionChanged( QObject* ) ),
            conn, SLOT( slotControlPointSelectionChanged( QObject* ) ) );
   connect( conn, SIGNAL( controlPointSelectionChanged( QObject* ) ),
            this, SLOT( slotControlPointSelectionChanged( QObject* ) ) );
   connect( this, SIGNAL( contextMenuAboutToShow( ) ),
            conn, SLOT( slotContextMenuAboutToShow( ) ) );

   connect( &m_startTimer, SIGNAL( timeout( ) ),
            SLOT( slotMouseChangeTimer( ) ) );
   connect( &m_autoScrollTimer, SIGNAL( timeout( ) ),
            SLOT( slotAutoScroll( ) ) );

   connect( rm, SIGNAL( renderingStarted( PMGLView* ) ),
            SLOT( slotRenderingStarted( PMGLView* ) ) );
   connect( rm, SIGNAL( aboutToUpdate( PMGLView* ) ),
            SLOT( slotAboutToUpdate( PMGLView* ) ) );
   connect( rm, SIGNAL( renderingFinished( PMGLView* ) ),
            SLOT( slotRenderingFinished( PMGLView* ) ) );
   connect( rm, SIGNAL( renderingSettingsChanged( ) ),
            SLOT( slotRefresh( ) ) );
   
   connect( this, SIGNAL( controlPointMessage( const QString& ) ),
            m_pPart, SIGNAL( controlPointMessage( const QString& ) ) );

   m_pPart->addGLView( this );
   connect( this, SIGNAL( destroyed( PMGLView* ) ),
            part, SLOT( removeGLView( PMGLView* ) ) );
   m_pConnector->addGLView( this );
   connect( this, SIGNAL( destroyed( PMGLView* ) ),
            conn, SLOT( removeGLView( PMGLView* ) ) );

   slotNewControlPoints( conn->controlPoints( ), conn->transformation( ) );
}

void PMGLView::initializeGL( )
{
   Display* display = x11Display( );
   int screen = x11Screen( );
   int i;

   if( !s_pSharedData )
   {
      s_staticDeleter.setObject( s_pSharedData, new PMGLViewStatic );
      s_pSharedData->m_display = display;

      if( PMRenderManager::hasOpenGL( ) )
      {
         // get an appropriate visual
         XVisualInfo* vi = glXChooseVisual( display, screen, glxAttributeList );
         s_pSharedData->m_visualInfo = vi;

         if( vi )
         {
            kdDebug( PMArea ) << "PMGLView: XVisual found\n";
         
            // create a color map (from qgl_x11.cpp)
            // check if we can use the global colormap
            // should be the default ?
            if( vi->visualid ==
                XVisualIDFromVisual( ( Visual* ) QPaintDevice::x11AppVisual( ) ) )
            {
               kdDebug( PMArea ) << "PMGLView: Default colormap used\n";
               s_pSharedData->m_colormap = QPaintDevice::x11AppColormap();
               s_pSharedData->m_colormapAllocated = false;
            }

            if( !s_pSharedData->m_colormap )
            {
               const char* v = glXQueryServerString( display, vi->screen,
                                                     GLX_VERSION );
               bool mesa_gl = false;
               if( v )
                  mesa_gl = strstr( v, "Mesa" ) != 0;
               if( mesa_gl )
               {
                  XStandardColormap* c;
                  int n;
                  Atom hp_cmaps = XInternAtom( display, "_HP_RGB_SMOOTH_MAP_LIST",
                                               TRUE );
               
                  if( hp_cmaps && ( vi->visual->c_class == TrueColor )
                      && ( vi->depth == 8 ) )
                  {
                     if( XGetRGBColormaps( display, RootWindow( display,vi->screen ),
                                           &c, &n, hp_cmaps ) )
                     {
                        i = 0;
                        while( ( i < n ) && ( s_pSharedData->m_colormap == 0 ) )
                        {
                           if( c[i].visualid == vi->visual->visualid )
                           {
                              s_pSharedData->m_colormap = c[i].colormap;
                              kdDebug( PMArea ) << "PMGLView: HP_RGB scmap used\n";
                           }
                           i++;
                        }
                        XFree( ( char* ) c );
                     }
                  }
               }
            }

#if !defined(Q_OS_SOLARIS)
            if( !s_pSharedData->m_colormap )
            {
               XStandardColormap* c;
               int n;
            
               if( XmuLookupStandardColormap( display, vi->screen, vi->visualid,
                                              vi->depth, XA_RGB_DEFAULT_MAP,
                                              FALSE, TRUE ) )
               {
                  if( XGetRGBColormaps( display, RootWindow( display, vi->screen ),
                                        &c, &n, XA_RGB_DEFAULT_MAP ) )
                  {
                     i = 0;
                     while( ( i < n ) && ( s_pSharedData->m_colormap == 0 ) )
                     {
                        if( c[i].visualid == vi->visualid )
                        {
                           s_pSharedData->m_colormap = c[i].colormap;
                           kdDebug( PMArea ) << "PMGLView: RGB_DEFAULT scmap used\n";
                        }
                        i++;
                     }
                     XFree( ( char* ) c );
                  }
               }
            }
#endif
         
            if( !s_pSharedData->m_colormap )
            {
               // create a new colormap otherwise
               kdDebug( PMArea ) << "PMGLView: New colormap created\n";
               s_pSharedData->m_colormap =
                  XCreateColormap( display,
                                   RootWindow( display, vi->screen ),
                                   vi->visual, AllocNone );
               s_pSharedData->m_colormapAllocated = true;
            }
         }

         // create the context
         // this context is shared between all gl views!
         s_pSharedData->m_context = glXCreateContext( display, vi, 0, GL_TRUE );
      
         if( s_pSharedData->m_context != NULL )
            kdDebug( PMArea ) << "PMGLView: glx context created\n";
      }
   }

   if( s_pSharedData->m_context != NULL )
   {
      XVisualInfo* vi = s_pSharedData->m_visualInfo;

      // create the window
      XSetWindowAttributes swa;
      swa.colormap = s_pSharedData->m_colormap;
      swa.border_pixel = 0;
      swa.background_pixel = 0;
         
      Window p;
      p = RootWindow( display, vi->screen );
      QWidget* pw = parentWidget( );
      if( pw )
         p = pw->winId( );
      
      Window w = XCreateWindow( display, p, x( ), y( ), width( ),
                                height( ), 0, vi->depth, InputOutput,
                                vi->visual,
                                CWColormap | CWBorderPixel | CWBackPixel,
                                &swa );
      
      // tell the windowmanager to use the colormap
      Window* colorMapWindows = 0;
      Window* newWindows = 0;
      int num;
      if( XGetWMColormapWindows( display, topLevelWidget( )->winId( ),
                                 &colorMapWindows, &num ) )
      {
         // create a new list and append the new window
         bool replaced = false;
         newWindows = new Window[num+1];
         
         for( i = 0; i < num; i++ )
         {
            newWindows[i] = colorMapWindows[i];
            if( newWindows[i] == winId( ) ) // old window
            {
               newWindows[i] = w;
               replaced = true;
            }
         }
         if( !replaced )
         {
            newWindows[num] = w;
            num++;
         }
      }
      else
      {
         num = 1;
         newWindows = new Window[1];
         newWindows[0] = w;
      }
      // tell Qt to use this window
      create( w );
      
      XSetWMColormapWindows( display, topLevelWidget( )->winId( ),
                             newWindows, num );
      delete[] newWindows;
      
      XFlush( x11Display( ) );
   }
   else
   {
      QVBoxLayout* topLayout = new QVBoxLayout( this );
      QLabel* label = new QLabel( i18n( "No OpenGL support" ), this );
      label->setAlignment( Qt::AlignCenter );
      topLayout->addWidget( label );
   }
   
   //setProjection( );
}

PMGLView::~PMGLView( )
{
   PMRenderManager* rm = PMRenderManager::theManager( );
   rm->removeView( this );
   rm->viewDeleted( );
   emit destroyed( this );
}

bool PMGLView::isValid( ) const
{
   if( s_pSharedData && ( s_pSharedData->m_context != NULL ) )
      return true;
   return false;
}

void PMGLView::makeCurrent( )
{
   if( isValid( ) )
      glXMakeCurrent( x11Display( ), winId( ), s_pSharedData->m_context );
}

void PMGLView::swapBuffers( )
{
   if( isValid( ) )
      glXSwapBuffers( x11Display( ), winId( ) );
}

void PMGLView::setScale( double scale )
{
   if( m_dScale > 0 )
   {
      m_dScale = scale;
      invalidateProjection( );
   }
   else
      kdError( PMArea ) << "Scale <= 0 in PMGLView::setScale\n";
}

void PMGLView::setTranslationX( double d )
{
   m_dTransX = d;
   invalidateProjection( );
}

void PMGLView::setTranslationY( double d )
{
   m_dTransY = d;
   invalidateProjection( );
}

void PMGLView::resizeEvent( QResizeEvent* )
{
   invalidateProjection( );
}

void PMGLView::paintEvent( QPaintEvent* )
{
   repaint( );
}

void PMGLView::invalidateProjection( bool graphicalChange /*= true*/ )
{
   m_viewTransformation = PMMatrix::identity( );

   
   if( m_type != PMViewCamera )
   {
      m_viewTransformation = m_viewTransformation * PMMatrix::scale( m_dScale, m_dScale, m_dScale );
      m_viewTransformation = m_viewTransformation * PMMatrix::translation( m_dTransX, m_dTransY, 0 );

      switch( m_type )
      {
         case PMViewPosZ:
            m_normal = PMVector( 0.0, 0.0, 1.0 );
            break;
         case PMViewNegZ:
            m_viewTransformation = m_viewTransformation * PMMatrix::rotation( 0.0, M_PI, 0.0 );
            m_normal = PMVector( 0.0, 0.0, -1.0 );
            break;
         case PMViewNegY:
            m_viewTransformation = m_viewTransformation * PMMatrix::rotation( M_PI_2, 0.0, 0.0 );
            m_normal = PMVector( 0.0, -1.0, 0.0 );
            break;
         case PMViewPosY:
            m_normal = PMVector( 0.0, 1.0, 0.0 );
            m_viewTransformation = m_viewTransformation * PMMatrix::rotation( -M_PI_2, 0.0, 0.0 );
            break;
         case PMViewPosX:
            m_viewTransformation = m_viewTransformation * PMMatrix::rotation( 0.0, M_PI_2, 0.0 );
            m_normal = PMVector( 1.0, 0.0, 0.0 );
            break;
         case PMViewNegX:
            m_viewTransformation = m_viewTransformation * PMMatrix::rotation( 0.0, -M_PI_2, 0.0 );
            m_normal = PMVector( -1.0, 0.0, 0.0 );
            break;
         default:
            break;
      }

      m_viewTransformation = m_viewTransformation * PMMatrix::scale( 1.0, 1.0, -1.0 );

      if( m_controlPoints.count( ) > 0 )
         recalculateTransformations( );
      recalculateControlPointPosition( );
   }
   m_projectionUpToDate = false;
   repaint( graphicalChange );
}

void PMGLView::enableTranslateMode( bool yes )
{
   if( m_type != PMViewCamera )
   {
      m_bScaleMode = false;
      m_bTranslateMode = yes;
      setCursor( yes ? crossCursor : arrowCursor );
   }
}

void PMGLView::enableScaleMode( bool yes )
{
   if( m_type != PMViewCamera )
   {
      m_bScaleMode = yes;
      m_bTranslateMode = false;
      setCursor( yes ? crossCursor : arrowCursor );
   }
}

void PMGLView::mousePressEvent( QMouseEvent* e )
{
   emit viewActivated( this );

   if( m_bScaleMode || m_bTranslateMode )
   {
      if( ( e->button( ) & LeftButton ) && ( e->state( ) == 0 ) )
      {
         m_bMousePressed = true;
         m_mousePos = e->pos( );
         m_scaleIntX = screenToInternalX( e->x( ) );
         m_scaleIntY = screenToInternalY( e->y( ) );
      }
   }
   else if( m_type != PMViewCamera )
   {
      if( ( e->button( ) & LeftButton ) && m_bInverseValid
          && m_pActiveObject )
      {
         if( m_pUnderMouse )
         {
            // check the control point selection
            if( m_pActiveObject->multipleSelectControlPoints( ) )
            {
               if( m_pUnderMouse->selected( ) )
               {
                  if( e->state( ) & ControlButton )
                     m_bDeselectUnderMouse = true;
                  else
                     m_bSelectUnderMouse = true;
               }
               else
               {
                  if( e->state( ) & ControlButton )
                     selectControlPoint( m_pUnderMouse,
                                         !m_pUnderMouse->selected( ), false );
                  else
                     selectControlPoint( m_pUnderMouse, true );
               }
            }
            else 
               selectControlPoint( m_pUnderMouse, true );

            // start the graphical change
            if( !m_bGraphicalChangeMode )
            {
               m_bGraphicalChangeMode = true;
               m_bMementoCreated = false;
               m_changeStartPos = e->pos( );
               m_changeStartTime = QTime::currentTime( );
               m_currentMousePos = m_changeStartPos;
               m_startTimer.start( c_mouseChangeDelayMs, true );
            }
         }
         else
         {
            if( m_pActiveObject->multipleSelectControlPoints( ) )
            {
               // multiple selection mode
               // start only when the view is rendered
               if( !PMRenderManager::theManager( )->containsTask( this ) )
               {
                  m_bMultipleSelectionMode = true;
                  m_bSelectionStarted = false;
                  m_selectionStart = e->pos( );
                  m_selectionEnd = m_selectionStart;
               }
            }
            else
               selectControlPoint( 0, false );
         }
      }
   }


   if( !( m_bGraphicalChangeMode || m_bMousePressed ) )
   {
      if( ( e->button( ) == RightButton ) && ( e->state( ) == 0 ) )
      {
         m_contextClickPosition = PMVector( screenToInternalX( e->x( ) ),
                                            screenToInternalY( e->y( ) ) );

         if( m_pUnderMouse )
         {
            // check the control point selection
            if( m_pActiveObject->multipleSelectControlPoints( ) )
            {
               if( !m_pUnderMouse->selected( ) )
                  selectControlPoint( m_pUnderMouse, true );
            }
            else 
               selectControlPoint( m_pUnderMouse, true );
         }

         QPopupMenu* m =
            ( QPopupMenu* ) m_pPart->factory( )->container( "viewPopup", m_pPart );
         if( m )
         {
            emit contextMenuAboutToShow( );
            m->exec( QCursor::pos( ) );
         }
      }
   }

   if( e->button( ) == MidButton )
   {
      m_bMidMousePressed = true;
      m_mousePos = e->pos( );
   }
}

void PMGLView::mouseReleaseEvent( QMouseEvent* e )
{
   m_bMousePressed = false;
   if( m_bGraphicalChangeMode )
   {
      m_startTimer.stop( );

      if( m_bMementoCreated )
      {
         PMDataChangeCommand* cmd;
         cmd = new PMDataChangeCommand( m_pActiveObject->takeMemento( ) );
         m_pPart->executeCommand( cmd );

         checkUnderMouse( ( int ) screenToInternalX( e->x( ) ),
                          ( int ) screenToInternalY( e->y( ) ) );
      }
      else
      {
         if( m_pUnderMouse )
         {
            if( m_bSelectUnderMouse )
               selectControlPoint( m_pUnderMouse, true );
            else if( m_bDeselectUnderMouse )
               selectControlPoint( m_pUnderMouse, false, false );
         }
      }
      m_bGraphicalChangeMode = false;
   }
   else if( m_bMultipleSelectionMode )
   {
      if( m_bSelectionStarted )
      {
         int sx, sy, ex, ey, w, h;
         double isx, isy, iex, iey;
         QPtrListIterator<PMVector> pit( m_controlPointsPosition );
         PMControlPointListIterator cit( m_controlPoints );
         PMVector p;

         calculateSelectionBox( sx, sy, ex, ey, w, h );
         isx = screenToInternalX( sx );
         iex = screenToInternalX( ex );
         isy = screenToInternalY( ey );
         iey = screenToInternalY( sy );
         restoreSelectionBox( );

         while( pit.current( ) && cit.current( ) )
         {
            p = *( pit.current( ) );

            if( ( isx <= p[0] ) && ( iex >= p[0] )
                && ( isy <= p[1] ) && ( iey >= p[1] ) )
               selectControlPoint( cit.current( ), true, false );
            else if( !( e->state( ) & ControlButton ) )
               selectControlPoint( cit.current( ), false, false );

            ++cit;
            ++pit;
         }
      }
      else
         selectControlPoint( 0, false );

      m_bMultipleSelectionMode = false;
   }

   if( m_bAutoScroll )
   {
      m_bAutoScroll = false;
      m_autoScrollTimer.stop( );
   }

   if( e->button( ) & QEvent::MidButton )
      m_bMidMousePressed = false;

   m_bSelectUnderMouse = false;
   m_bDeselectUnderMouse = false;
}

void PMGLView::mouseMoveEvent( QMouseEvent* e )
{
   if( m_bMousePressed )
   {
      if( m_bScaleMode )
      {
         int d = e->x( ) - m_mousePos.x( );
         if( d != 0 )
         {
            double s = exp( d * c_sizeFactor );
            double c = 1.0 / ( m_dScale * s ) - 1.0 / m_dScale;
            m_dTransX += m_scaleIntX * c;
            m_dTransY += m_scaleIntY * c;
            m_dScale *= s;
            invalidateProjection( );
         }
      }
      else if( m_bTranslateMode )
      {
         m_dTransX += ( e->x( ) - m_mousePos.x( ) ) / m_dScale;
         m_dTransY -= ( e->y( ) - m_mousePos.y( ) ) / m_dScale;
         invalidateProjection( );
      }
      m_mousePos = e->pos( );
   }
   else if( m_bMidMousePressed )
   {
      m_dTransX += ( e->x( ) - m_mousePos.x( ) ) / m_dScale;
      m_dTransY -= ( e->y( ) - m_mousePos.y( ) ) / m_dScale;
      invalidateProjection( );

      m_mousePos = e->pos( );
   }
   else if( m_bGraphicalChangeMode )
   {
      m_currentMousePos = e->pos( );
      if( !m_bMementoCreated )
      {
         QPoint movement = e->pos( ) - m_changeStartPos;
         if( ( m_changeStartTime.msecsTo( QTime::currentTime( ) ) > c_mouseChangeDelayMs )
            || ( movement.manhattanLength( ) > c_mouseChangeDelayPixel ) )
         {
            m_startTimer.stop( );
            startChange( m_changeStartPos );
         }
      }

      if( m_bMementoCreated )
         graphicalChange( e->pos( ) );
   }
   else if( m_bMultipleSelectionMode )
   {
      if( !m_bSelectionStarted )
      {
         m_selectionEnd = e->pos( );
         startSelection( );
      }
      else
      {
         restoreSelectionBox( );
         m_selectionEnd = e->pos( );
         saveSelectionBox( );
         paintSelectionBox( );
      }
   }
   else if( !( m_bScaleMode || m_bTranslateMode ) )
   {
      checkUnderMouse( ( int ) screenToInternalX( e->x( ) ),
                       ( int ) screenToInternalY( e->y( ) ) );
   }

   if( m_bMultipleSelectionMode || m_bGraphicalChangeMode )
   {
      bool as = m_bAutoScroll;

      if( e->x( ) < 0 )
         m_autoScrollDirectionX = 1;
      else if( e->x( ) >= width( ) )
         m_autoScrollDirectionX = -1;
      else
         m_autoScrollDirectionX = 0;

      if( e->y( ) < 0 )
         m_autoScrollDirectionY = 1;
      else if( e->y( ) >= height( ) )
         m_autoScrollDirectionY = -1;
      else
         m_autoScrollDirectionY = 0;

      if( ( m_autoScrollDirectionX != 0 ) || ( m_autoScrollDirectionY != 0 ) )
         m_bAutoScroll = true;
      else
         m_bAutoScroll = false;

      if( m_bAutoScroll && !as )
      {
         m_lastAutoScrollUpdate = QTime::currentTime( );
         m_autoScrollTimer.start( c_minAutoScrollUpdateTime, true );
      }
      if( !m_bAutoScroll && as )
         m_autoScrollTimer.stop( );
   }
}

void PMGLView::wheelEvent( QWheelEvent* e )
{
   if( m_type != PMViewCamera )
   {
      double s = exp( e->delta( ) / 4 * c_sizeFactor );
      double deltaX = screenToInternalX( e->x( ) );
      double deltaY = screenToInternalY( e->y( ) );
      double c = 1.0 / ( m_dScale * s ) - 1.0 / m_dScale;
      m_dTransX += deltaX * c;
      m_dTransY += deltaY * c;
      m_dScale *= s;
      invalidateProjection( );
   }
}

void PMGLView::keyPressEvent( QKeyEvent* e )
{
   bool accept = true;
   
   if( e->state( ) == 0 )
   {
      if( m_type != PMViewCamera )
      {
         if( m_dScale > 0 )
         {
            switch( e->key( ) )
            {
               case Key_Left:
                  m_dTransX -= keyMoveSpeed / m_dScale;
                  break;
               case Key_Right:
                  m_dTransX += keyMoveSpeed / m_dScale;
                  break;
               case Key_Up:
                  m_dTransY += keyMoveSpeed / m_dScale;
                  break;
               case Key_Down:
                  m_dTransY -= keyMoveSpeed / m_dScale;
                  break;
               default:
                  accept = false;
            }
         }
         else
            kdError( PMArea ) << "scale <= 0 in PMGLView::keyPressEvent\n";
      }
   }
   else if( e->state( ) == ControlButton )
   {
      if( m_type != PMViewCamera )
      {
         switch( e->key( ) )
         {
            case Key_Left:
            case Key_Down:
               m_dScale /= keyScaleFactor;
               break;
            case Key_Right:
            case Key_Up:
               m_dScale *= keyScaleFactor;
               break;
            default:
               accept = false;
         }
      }
   }
   else
      accept = false;
   
   if( accept )
      invalidateProjection( );
   else
      e->ignore( );
}

void PMGLView::slotAutoScroll( )
{
   if( m_bAutoScroll )
   {
      QTime now = QTime::currentTime( );
      int msecs = m_lastAutoScrollUpdate.msecsTo( now );
      int pixels = ( int ) ( m_autoScrollSpeed * msecs / 1000.0 );

      if( pixels < 1 )
         pixels = 1;
      if( pixels > ( width( ) * 3 / 4 ) )
         pixels = width( ) * 3 / 4;
      if( pixels > ( height( ) * 3 / 4 ) )
         pixels = height( ) * 3 / 4;

      if( m_bGraphicalChangeMode && !m_bMementoCreated )
         startChange( m_changeStartPos );
      if( m_bMultipleSelectionMode )
         restoreSelectionBox( );

      m_dTransX += pixels * m_autoScrollDirectionX / m_dScale;
      m_dTransY -= pixels * m_autoScrollDirectionY / m_dScale;
      invalidateProjection( );

   if( m_bGraphicalChangeMode )
      if( m_bMultipleSelectionMode )
      {
         m_selectionStart += QPoint( pixels * m_autoScrollDirectionX,
                                     pixels * m_autoScrollDirectionY );

         saveSelectionBox( );
         paintSelectionBox( );
      }

      if( m_bGraphicalChangeMode )
         graphicalChange( mapFromGlobal( QCursor::pos( ) ) );
      else
         repaint( );

      m_lastAutoScrollUpdate = now;
   }
}

void PMGLView::slotMouseChangeTimer( )
{
   if( !m_bMementoCreated )
   {
      if( m_currentMousePos != m_changeStartPos )
      {
         startChange( m_changeStartPos );
         graphicalChange( m_currentMousePos );
      }
   }
}

void PMGLView::startChange( const QPoint& mousePos )
{
   m_pActiveObject->createMemento( );
   m_bMementoCreated = true;

   PMVector p = mousePosition( m_pUnderMouse, mousePos.x( ), mousePos.y( ) );
   p.transform( m_inversePointsTransformation );

   if( m_pActiveObject->multipleSelectControlPoints( ) )
   {
      PMControlPointListIterator it( m_controlPoints );
      for( ; it.current( ); ++it )
         if( it.current( )->selected( ) )
            it.current( )->startChange( p, m_normal );
   }
   else
      m_pUnderMouse->startChange( p, m_normal );
}

void PMGLView::graphicalChange( const QPoint& mousePos )
{
   PMVector p = mousePosition( m_pUnderMouse, mousePos.x( ), mousePos.y( ) );
   p.transform( m_inversePointsTransformation );
   if( m_pActiveObject->multipleSelectControlPoints( ) )
   {
      PMControlPointListIterator it( m_controlPoints );
      for( ; it.current( ); ++it )
         if( it.current( )->selected( ) )
            it.current( )->change( p );
   }
   else
      m_pUnderMouse->change( p );

   m_pActiveObject->controlPointsChanged( m_controlPoints );

   emit objectChanged( m_pActiveObject, PMCGraphicalChange, this );
}

void PMGLView::checkUnderMouse( int x, int y )
{
   // is cursor over a control point?
   // double z;
   PMVector* v;
   PMControlPoint* p;
   PMControlPoint* old = m_pUnderMouse;

   if( m_bInverseValid && ( m_type != PMViewCamera ) )
   {
      m_pUnderMouse = 0;
//      z = -1e10;

      v = m_controlPointsPosition.first( );
      p = m_controlPoints.first( );

      while( p )
      {
         if( p->displayType( ) == PMControlPoint::CPCross )
         {
            if( !m_pUnderMouse )
               m_pUnderMouse = p;
         }
         else
         {
            if( ( fabs( x - (*v)[0] ) < ( controlPointSize / 2.0 + 0.1 ) )
                && ( fabs( y - (*v)[1] ) < ( controlPointSize / 2.0 + 0.1 ) ) )
            {
               if( m_pUnderMouse )
               {
                  if( p->selected( ) && !m_pUnderMouse->selected( ) )
                     m_pUnderMouse = p;
               }
               else
                  m_pUnderMouse = p;
            }
         }

         p = m_controlPoints.next( );
         v = m_controlPointsPosition.next( );
      }
   }
   else
      m_pUnderMouse = 0;

   setCursor( m_pUnderMouse ? crossCursor : arrowCursor );
   if( m_pUnderMouse != old )
   {
      if( m_pUnderMouse )
         emit controlPointMessage( m_pUnderMouse->description( ) );
      else
         emit controlPointMessage( "" );
   }
}

void PMGLView::slotNewControlPoints( const PMControlPointList& list, const PMMatrix& t )
{
   PMControlPointListIterator it( list );
   m_controlPoints.clear( );

   for( ; it.current( ); ++it )
      m_controlPoints.append( it.current( ) );

   if( m_controlPoints.count( ) > 0 )
   {
      m_objectsTransformation = t;
      recalculateTransformations( );
   }

   if( m_bGraphicalChangeMode )
      m_bGraphicalChangeMode = false;

   recalculateControlPointPosition( );
}

void PMGLView::recalculateControlPointPosition( )
{
   PMControlPointListIterator it( m_controlPoints );
   m_controlPointsPosition.clear( );
   PMVector* v;

   for( ; it.current( ); ++it )
   {
      v = new PMVector( m_controlPointsTransformation * it.current( )->position( ) );
      m_controlPointsPosition.append( v );
   }
   if( !m_bGraphicalChangeMode )
   {
      m_pUnderMouse = 0;
      emit controlPointMessage( "" );
   }
}

PMVector PMGLView::mousePosition( PMControlPoint* cp, int x, int y )
{
   PMVector result;
   int index;
   PMVector* p;

   result[0] = screenToInternalX( x );
   result[1] = screenToInternalY( y );
   if( cp )
   {
      index = m_controlPoints.findRef( cp );
      if( index >= 0 )
      {
         p = m_controlPointsPosition.at( ( uint ) index );
         if( p )
            result[2] = p->z( );
      }
   }
   return result;
}

void PMGLView::recalculateTransformations( )
{
   int r, c;

   m_controlPointsTransformation = m_viewTransformation * m_objectsTransformation;

   if( m_controlPointsTransformation.canBuildInverse( ) )
   {
      m_inversePointsTransformation = m_controlPointsTransformation.inverse( );

      for( c = 0; c < 4; c++ )
         for( r = 0; r < 4; r++ )
            if( fabs( m_inversePointsTransformation[c][r] ) < 1e-8 )
               m_inversePointsTransformation[c][r] = 0.0;

      m_bInverseValid = true;
   }
   else
      m_bInverseValid = false;
}

void PMGLView::setType( PMViewType t )
{
   if( m_type != t )
      m_viewTransformation = PMMatrix::identity( );
   m_type = t;
   invalidateProjection( );

   emit viewTypeChanged( viewTypeAsString( t ) );
}

void PMGLView::setCamera( PMCamera* c )
{
   m_pCamera = c;
   invalidateProjection( );
}

void PMGLView::slotRefresh( )
{
   if( m_type == PMViewCamera )
      if( !m_pCamera )
         setCamera( m_pPart->firstCamera( ) );

   repaint( );
}

void PMGLView::slotClear( )
{
   m_controlPointsPosition.clear( );
   m_controlPoints.clear( );
   m_pUnderMouse = 0;
   m_pCamera = 0;
   m_pActiveObject = 0;

   slotStopRendering( );
}

void PMGLView::slotActiveRenderModeChanged( )
{
   if( ( m_type == PMViewCamera ) && m_pCamera )
      invalidateProjection( );
}

void PMGLView::slotStopRendering( )
{
   PMRenderManager* rm = PMRenderManager::theManager( );
   rm->removeView( this );
}

void PMGLView::slotObjectChanged( PMObject* obj, const int mode,
                                  QObject* sender )
{
   bool redraw = false;

   if( mode & PMCNewSelection )
   {
      if( obj )
      {
         if( obj != m_pActiveObject )
         {
            m_pActiveObject = obj;
            redraw = true;
         }
      }
      else
      {
         m_pActiveObject = 0;
         redraw = true;
      }
   }
   if( mode & ( PMCSelected | PMCDeselected ) )
   {
      m_pActiveObject = 0;
      redraw = true;
   }
   if( mode & ( PMCViewStructure | PMCGraphicalChange ) )
   {
      if( m_type == PMGLView::PMViewCamera )
      {
         if( obj->type( ) == PMTCamera )
            if( m_pCamera == ( PMCamera* ) obj )
               invalidateProjection( );
         
         if( obj->parent( ) )
            if( obj->parent( )->type( ) == PMTCamera )
               if( m_pCamera == ( PMCamera* ) obj->parent( ) )
                  if( obj->hasTransformationMatrix( ) )
                     invalidateProjection( );
      }
      
      redraw = true;
   }
   if( mode & PMCDescription )
   {
      if( m_type == PMGLView::PMViewCamera && obj && obj == m_pCamera )
         redraw = true;
   }
   if( mode & PMCAdd )
   {
      if( m_type == PMGLView::PMViewCamera )
      {
         if( obj->type( ) == PMTCamera )
            if( !m_pCamera )
               setCamera( ( PMCamera* ) obj );
         if( obj->parent( ) )
            if( obj->parent( )->type( ) == PMTCamera )
               if( m_pCamera == ( PMCamera* ) obj->parent( ) )
                  if( obj->hasTransformationMatrix( ) )
                     invalidateProjection( );
      }
      redraw = true;
   }

   if( mode & PMCRemove )
   {
      if( obj->type( ) == PMTCamera )
         if( m_pCamera == ( PMCamera* ) obj )
            setCamera( 0 );
      
      if( m_type == PMGLView::PMViewCamera )
         if( obj->parent( ) )
            if( obj->parent( )->type( ) == PMTCamera )
               if( m_pCamera == ( PMCamera* ) obj->parent( ) )
                  if( obj->hasTransformationMatrix( ) )
                     invalidateProjection( );

      redraw = true;
   }

   if( mode & PMCChildren )
      redraw = true;

   if( redraw )
      repaint( sender == this );
}

void PMGLView::slotControlPointSelectionChanged( QObject* sender )
{
   repaint( sender == this );
}

void PMGLView::repaint( bool graphicalChange )
{
   if( isValid( ) )
   {
      PMObject* obj = m_pActiveObject;
      
      if( obj )
         obj = topLevelRenderingObject( obj );
      else
      {
         const PMObjectList& selected = m_pPart->selectedObjects( );
         PMObjectListIterator it( selected );
         if( it.current( ) )
            obj = topLevelRenderingObject( it.current( ) );

         if( obj && ( obj->type( ) != PMTScene ) )
            for( ++it; it.current( ) && obj; ++it )
               if( topLevelRenderingObject( it.current( ) ) != obj )
                  obj = 0;
      }

      if( !obj )
         obj = m_pPart->scene( );

      if( obj )
      {
         double aspectRatio = 1.0;
         PMRenderMode* mode = m_pPart->scene( )->renderModes( )->current( );
         if( mode )
            if( mode->height( ) != 0 )
               aspectRatio = ( double ) mode->width( )
                             / ( double ) mode->height( );
      
         PMRenderManager* rm = PMRenderManager::theManager( );
         rm->addView( this, m_pActiveObject, obj,
                      &m_controlPoints, aspectRatio,
                      m_pPart->scene( )->visibilityLevel( ), graphicalChange );
      }
   }
}

PMObject* PMGLView::topLevelRenderingObject( PMObject* o ) const
{
   PMObject* obj = o;
   bool stop = false;
   
   if( obj )
   {
      do
      {
         if( !obj )
            stop = true;
         else if( obj->isA( PMTScene ) || obj->isA( PMTDeclare ) )
            stop = true;
         else
            obj = obj->parent( );
      }
      while( !stop );
   }
   else
      obj = m_pPart->scene( );
   
   return obj;
}

void PMGLView::selectControlPoint( PMControlPoint* cp, bool select, bool deselectOthers )
{
   bool selectionChanged = false;

   if( cp )
   {
      if( deselectOthers )
      {
         PMControlPointListIterator it( m_controlPoints );
         for( ; it.current( ); ++it )
         {
            bool s;
            if( it.current( ) == cp )
               s = select;
            else
               s = false;

            if( s != it.current( )->selected( ) )
            {
               selectionChanged = true;
               it.current( )->setSelected( s );
            }
         }
      }
      else
      {
         if( select != cp->selected( ) )
         {
            selectionChanged = true;
            cp->setSelected( select );
         }
      }
   }
   else
   {
      PMControlPointListIterator it( m_controlPoints );
      for( ; it.current( ); ++it )
      {
         if( select != it.current( )->selected( ) )
         {
            selectionChanged = true;
            it.current( )->setSelected( select );
         }
      }
   }

   if( selectionChanged )
      emit controlPointSelectionChanged( this );
}

void PMGLView::startSelection( )
{
   if( !m_bSelectionStarted )
   {
      saveSelectionBox( );
      paintSelectionBox( );

      m_bSelectionStarted = true;
   }
}

void PMGLView::restoreSelectionBox( )
{
   if( !m_bAboutToUpdate )
   {
      int sx, sy, ex, ey, w, h;
      calculateSelectionBox( sx, sy, ex, ey, w, h );

      if( !m_selectionPixmap[0].isNull( ) )
         bitBlt( this, sx, sy, &( m_selectionPixmap[0] ), 0, 0, w, 1, CopyROP );
      if( !m_selectionPixmap[1].isNull( ) )
         bitBlt( this, sx, ey, &( m_selectionPixmap[1] ), 0, 0, w, 1, CopyROP );
      if( !m_selectionPixmap[2].isNull( ) )
         bitBlt( this, sx, sy+1, &( m_selectionPixmap[2] ), 0, 0, 1, h-2, CopyROP );
      if( !m_selectionPixmap[3].isNull( ) )
         bitBlt( this, ex, sy+1, &( m_selectionPixmap[3] ), 0, 0, 1, h-2, CopyROP );
   }
}

void PMGLView::saveSelectionBox( )
{
   if( !m_bAboutToUpdate )
   {
      int sx, sy, ex, ey, w, h;
      calculateSelectionBox( sx, sy, ex, ey, w, h );

      m_selectionPixmap[0].resize( w, 1 );
      if( !m_selectionPixmap[0].isNull( ) )
         bitBlt( &( m_selectionPixmap[0] ), 0, 0, this, sx, sy, w, 1, CopyROP );
      m_selectionPixmap[1].resize( w, 1 );
      if( !m_selectionPixmap[1].isNull( ) )
         bitBlt( &( m_selectionPixmap[1] ), 0, 0, this, sx, ey, w, 1, CopyROP );

      m_selectionPixmap[2].resize( 1, h-2 );
      if( !m_selectionPixmap[2].isNull( ) )
         bitBlt( &( m_selectionPixmap[2] ), 0, 0, this, sx, sy+1, 1, h-2, CopyROP );
      m_selectionPixmap[3].resize( 1, h-2 );
      if( !m_selectionPixmap[3].isNull( ) )
         bitBlt( &( m_selectionPixmap[3] ), 0, 0, this, ex, sy+1, 1, h-2, CopyROP );
   }
}

void PMGLView::paintSelectionBox( )
{
   if( !m_bAboutToUpdate )
   {
      int sx, sy, ex, ey, w, h;
      calculateSelectionBox( sx, sy, ex, ey, w, h );
      QPainter p;
      p.begin( this );
      p.setPen( PMRenderManager::theManager( )->controlPointColor( 1 ) );
      p.drawRect( sx, sy, w, h );
      p.end( );
   }
}

void PMGLView::calculateSelectionBox( int& sx, int& sy, int& ex, int& ey,
                                      int& w, int& h )
{
   if( m_selectionStart.x( ) < m_selectionEnd.x( ) )
   {
      sx = m_selectionStart.x( );
      ex = m_selectionEnd.x( );
   }
   else
   {
      ex = m_selectionStart.x( );
      sx = m_selectionEnd.x( );
   }

   if( m_selectionStart.y( ) < m_selectionEnd.y( ) )
   {
      sy = m_selectionStart.y( );
      ey = m_selectionEnd.y( );
   }
   else
   {
      ey = m_selectionStart.y( );
      sy = m_selectionEnd.y( );
   }

   w = ex - sx + 1;
   h = ey - sy + 1;
}

double PMGLView::screenToInternalX( int x ) const
{
   return rint( x - width( ) / 2.0 + 0.1 );
}

double PMGLView::screenToInternalY( int y ) const
{
   return rint( height( ) / 2.0 - y - 0.1 );
}

void PMGLView::slotRenderingStarted( PMGLView* )
{
}

void PMGLView::slotAboutToUpdate( PMGLView* view )
{
   if( view == this )
      m_bAboutToUpdate = true;
}

void PMGLView::slotRenderingFinished( PMGLView* view )
{
   if( view == this )
   {
      m_bAboutToUpdate = false;
      if( m_bMultipleSelectionMode )
      {
         saveSelectionBox( );
         paintSelectionBox( );
      }

      if( m_bAutoScroll )
      {
         QTime now = QTime::currentTime( );
         int msecs = m_lastAutoScrollUpdate.msecsTo( now );

         if( msecs < c_minAutoScrollUpdateTime )
            m_autoScrollTimer.start( c_minAutoScrollUpdateTime - msecs, true );
         else
            m_autoScrollTimer.start( 0, true );
      }
   }
}

QString PMGLView::viewTypeAsString( PMViewType t )
{
   QString str;
   
   switch( t )
   {
      case PMViewPosX:
         str = i18n( "Left" );
         break;
      case PMViewNegX:
         str = i18n( "Right" );
         break;
      case PMViewPosY:
         str = i18n( "Bottom" );
         break;
      case PMViewNegY:
         str = i18n( "Top" );
         break;
      case PMViewPosZ:
         str = i18n( "Front" );
         break;
      case PMViewNegZ:
         str = i18n( "Back" );
         break;
      case PMViewCamera:
         str = i18n( "Camera" );
         break;
   }
   return str;
}

void PMGLView::saveConfig( KConfig* /*cfg*/ )
{
}

void PMGLView::restoreConfig( KConfig* /*cfg*/ )
{
}

#include "pmglview.moc"
