/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

#include <tools/prex.h>
#include <X11/Xlib.h>
#include <X11/extensions/shape.h>
#include <tools/postx.h>

#include <salunx.h>
#include <salstd.hxx>
#include <saldata.hxx>
#include <salinst.h>
#include <saldisp.hxx>
#include <salframe.h>
#include <salobj.h>
#include <vcl/salwtype.hxx>
#include <vcl/keycodes.hxx>

#include <tools/debug.hxx>
#if OSL_DEBUG_LEVEL > 1
#include <stdio.h>
#endif

// =======================================================================
// SalInstance member to create and destroy a SalObject

SalObject* X11SalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, sal_Bool bShow )
{
    return X11SalObject::CreateObject( pParent, pWindowData, bShow );
}

X11SalObject* X11SalObject::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, sal_Bool bShow )
{
    int error_base, event_base;
    X11SalObject*		pObject  = new X11SalObject();
    SystemChildData*	pObjData = const_cast<SystemChildData*>(pObject->GetSystemData());

    if ( ! XShapeQueryExtension( (Display*)pObjData->pDisplay,
                                  &event_base, &error_base ) )
    {
        delete pObject;
        return NULL;
    }

    pObject->mpParent = pParent;
    
    SalDisplay* pSalDisp		= GetX11SalData()->GetDisplay();
    const SystemEnvData* pEnv	= pParent->GetSystemData();
    Display* pDisp				= pSalDisp->GetDisplay();
    XLIB_Window aObjectParent	= (XLIB_Window)pEnv->aWindow;
    
    // find out on which screen that window is
    XWindowAttributes aParentAttr;
    XGetWindowAttributes( pDisp, aObjectParent, &aParentAttr );
    int nScreen = XScreenNumberOfScreen( aParentAttr.screen );
    Visual* pVisual = (pWindowData && pWindowData->pVisual) ?
                      (Visual*)pWindowData->pVisual :
                      pSalDisp->GetVisual( nScreen ).GetVisual();
    // get visual info
    VisualID aVisID = XVisualIDFromVisual( pVisual );
    XVisualInfo aTemplate;
    aTemplate.visualid = aVisID;
    int nVisuals = 0;
    XVisualInfo* pInfos = XGetVisualInfo( pDisp, VisualIDMask, &aTemplate, &nVisuals );
    // only one VisualInfo structure can match the visual id
    DBG_ASSERT( nVisuals == 1, "match count for visual id is not 1" );
    unsigned int nDepth     = pInfos->depth;
    XFree( pInfos );
    XSetWindowAttributes aAttribs;
    aAttribs.event_mask =   StructureNotifyMask
                          | ButtonPressMask
                          | ButtonReleaseMask
                          | PointerMotionMask
                          | EnterWindowMask
                          | LeaveWindowMask
                          | FocusChangeMask
                          | ExposureMask
                          ;

    pObject->maPrimary =
        XCreateSimpleWindow( pDisp,
                             aObjectParent,
                             0, 0,
                             1, 1, 0,
                             pSalDisp->GetColormap( nScreen ).GetBlackPixel(),
                             pSalDisp->GetColormap( nScreen ).GetWhitePixel()
                             );
    if( aVisID == pSalDisp->GetVisual( nScreen ).GetVisualId() )
    {
        pObject->maSecondary =
            XCreateSimpleWindow( pDisp,
                                 pObject->maPrimary,
                                 0, 0,
                                 1, 1, 0,
                                 pSalDisp->GetColormap( nScreen ).GetBlackPixel(),
                                 pSalDisp->GetColormap( nScreen ).GetWhitePixel()
                                 );
    }
    else
    {
        #if OSL_DEBUG_LEVEL > 1
        fprintf( stderr, "visual id of vcl %x, of visual %x\n",
                 static_cast<unsigned int> (pSalDisp->GetVisual( nScreen ).GetVisualId()),
                 static_cast<unsigned int> (aVisID) );
        #endif
        pSalDisp->GetXLib()->PushXErrorLevel( true );

        // create colormap for visual - there might not be one
        pObject->maColormap = aAttribs.colormap = XCreateColormap( 
            pDisp,
            pSalDisp->GetRootWindow( nScreen ),
            pVisual,
            AllocNone );

        pObject->maSecondary =
            XCreateWindow( pDisp,
                           pSalDisp->GetRootWindow( nScreen ),
                           0, 0,
                           1, 1, 0,
                           nDepth, InputOutput,
                           pVisual,
                           CWEventMask|CWColormap, &aAttribs );
        XSync( pDisp, False );
        sal_Bool bWasXError = pSalDisp->GetXLib()->HasXErrorOccurred();
        pSalDisp->GetXLib()->PopXErrorLevel();
        if( bWasXError )
        {
            pObject->maSecondary = None;
            delete pObject;
            return NULL;
        }
        XReparentWindow( pDisp, pObject->maSecondary, pObject->maPrimary, 0, 0 );
    }

    pSalDisp->GetXLib()->PushXErrorLevel( true );
    if( bShow ) {
        XMapWindow( pDisp, pObject->maSecondary );
        XMapWindow( pDisp, pObject->maPrimary );
    }

    pObjData->pDisplay		= pDisp;
    pObjData->aWindow		= pObject->maSecondary;
    pObjData->pWidget		= NULL;
    pObjData->pVisual		= pVisual;
    pObjData->nDepth		= nDepth;
    pObjData->aColormap		= aVisID == pSalDisp->GetVisual( nScreen ).GetVisualId() ?
                              pSalDisp->GetColormap( nScreen ).GetXColormap() : None;
    pObjData->pAppContext	= NULL;

    XSync(pDisp, False);
    sal_Bool bWasXError = pSalDisp->GetXLib()->HasXErrorOccurred();
    pSalDisp->GetXLib()->PopXErrorLevel();
    if( bWasXError )
    {
        delete pObject;
        return NULL;
    }

    return pObject;
}


void X11SalInstance::DestroyObject( SalObject* pObject )
{
    delete pObject;
}


// ======================================================================
// SalClipRegion is a member of SalObject
// definition of SalClipRegion my be found in unx/inc/salobj.h


SalClipRegion::SalClipRegion()
{
    ClipRectangleList = NULL;
    numClipRectangles = 0;
    maxClipRectangles = 0;
    nClipRegionType   = SAL_OBJECT_CLIP_INCLUDERECTS;
}


SalClipRegion::~SalClipRegion()
{
    if ( ClipRectangleList )
        delete [] ClipRectangleList;
}


void
SalClipRegion::BeginSetClipRegion( sal_uLong nRects )
{
    if (ClipRectangleList)
        delete [] ClipRectangleList;

    ClipRectangleList = new XRectangle[nRects];
    numClipRectangles = 0;
    maxClipRectangles = nRects;
}


void
SalClipRegion::UnionClipRegion( long nX, long nY, long nWidth, long nHeight )
{
    if ( nWidth && nHeight && (numClipRectangles < maxClipRectangles) )
    {
        XRectangle *aRect = ClipRectangleList + numClipRectangles;

        aRect->x	 = (short) nX;
        aRect->y	 = (short) nY;
        aRect->width = (unsigned short) nWidth;
        aRect->height= (unsigned short) nHeight;

        numClipRectangles++;
    }
}


// =======================================================================
// SalObject Implementation


X11SalObject::X11SalObject()
{
    maSystemChildData.nSize		= sizeof( SystemChildData );
    maSystemChildData.pDisplay 	= GetX11SalData()->GetDisplay()->GetDisplay();
    maSystemChildData.aWindow		= None;
    maSystemChildData.pSalFrame	= 0;
    maSystemChildData.pWidget		= 0;
    maSystemChildData.pVisual		= 0;
    maSystemChildData.nDepth		= 0;
    maSystemChildData.aColormap	= 0;
    maSystemChildData.pAppContext	= NULL;
    maSystemChildData.aShellWindow	= 0;
    maSystemChildData.pShellWidget	= NULL;
    maPrimary						= 0;
    maSecondary						= 0;
    maColormap                      = 0;

    std::list< SalObject* >& rObjects = GetX11SalData()->GetDisplay()->getSalObjects();
    rObjects.push_back( this );
}


X11SalObject::~X11SalObject()
{
    std::list< SalObject* >& rObjects = GetX11SalData()->GetDisplay()->getSalObjects();
    rObjects.remove( this );
    SalDisplay* pSalDisp = GetX11SalData()->GetDisplay();
    pSalDisp->GetXLib()->PushXErrorLevel( true );
    if ( maSecondary ) 
        XDestroyWindow( (Display*)maSystemChildData.pDisplay, maSecondary );
    if ( maPrimary )
        XDestroyWindow( (Display*)maSystemChildData.pDisplay, maPrimary );
    if ( maColormap )
        XFreeColormap((Display*)maSystemChildData.pDisplay, maColormap);
    XSync( (Display*)maSystemChildData.pDisplay, False );
    pSalDisp->GetXLib()->PopXErrorLevel();
}


void
X11SalObject::ResetClipRegion()
{
    maClipRegion.ResetClipRegion();

    const int	dest_kind	= ShapeBounding;
    const int	op			= ShapeSet;
    const int	ordering	= YSorted;

    XWindowAttributes win_attrib;
    XRectangle		  win_size;

    XLIB_Window aShapeWindow = maPrimary;

    XGetWindowAttributes ( (Display*)maSystemChildData.pDisplay,
                           aShapeWindow,
                           &win_attrib );

    win_size.x		= 0;
    win_size.y		= 0;
    win_size.width	= win_attrib.width;
    win_size.height = win_attrib.height;

    XShapeCombineRectangles ( (Display*)maSystemChildData.pDisplay,
                              aShapeWindow,
                              dest_kind,
                              0, 0, 			// x_off, y_off
                              &win_size,		// list of rectangles
                              1,				// number of rectangles
                              op, ordering );
}


void
X11SalObject::BeginSetClipRegion( sal_uLong nRectCount )
{
    maClipRegion.BeginSetClipRegion ( nRectCount );
}


void
X11SalObject::UnionClipRegion( long nX, long nY, long nWidth, long nHeight )
{
    maClipRegion.UnionClipRegion ( nX, nY, nWidth, nHeight );
}


void
X11SalObject::EndSetClipRegion()
{
    XRectangle *pRectangles = maClipRegion.EndSetClipRegion ();
    const int	nType		= maClipRegion.GetClipRegionType();
    const int	nRectangles = maClipRegion.GetRectangleCount();

    const int	dest_kind	= ShapeBounding;
    const int	ordering	= YSorted;
    int 		op;

    switch ( nType )
    {
        case SAL_OBJECT_CLIP_INCLUDERECTS :
            op = ShapeSet;
            break;
        case SAL_OBJECT_CLIP_EXCLUDERECTS :
            op = ShapeSubtract;
            break;
        case SAL_OBJECT_CLIP_ABSOLUTE :
            op = ShapeSet;
            break;
        default :
            op = ShapeUnion;
    }

    XLIB_Window aShapeWindow = maPrimary;
    
    XShapeCombineRectangles ( (Display*)maSystemChildData.pDisplay,
                              aShapeWindow,
                              dest_kind,
                              0, 0, // x_off, y_off
                              pRectangles,
                              nRectangles,
                              op, ordering );
}


sal_uInt16
X11SalObject::GetClipRegionType()
{
    return maClipRegion.GetClipRegionType();
}

// -----------------------------------------------------------------------

void
X11SalObject::SetPosSize( long nX, long nY, long nWidth, long nHeight )
{
    if ( maPrimary && maSecondary && nWidth && nHeight )
    {
        XMoveResizeWindow( (Display*)maSystemChildData.pDisplay,
                           maPrimary,
                            nX, nY, nWidth, nHeight );
        XMoveResizeWindow( (Display*)maSystemChildData.pDisplay,
                           maSecondary,
                            0, 0, nWidth, nHeight );
    }
}


void
X11SalObject::Show( sal_Bool bVisible )
{
    if	( ! maSystemChildData.aWindow )
        return;

    if ( bVisible ) {
        XMapWindow( (Display*)maSystemChildData.pDisplay,
                    maSecondary );
        XMapWindow( (Display*)maSystemChildData.pDisplay,
                    maPrimary );
    } else {
        XUnmapWindow( (Display*)maSystemChildData.pDisplay,
                      maPrimary );
        XUnmapWindow( (Display*)maSystemChildData.pDisplay,
                      maSecondary );
    }
    mbVisible = bVisible;
}

// -----------------------------------------------------------------------

void X11SalObject::Enable( sal_Bool )
{
}

// -----------------------------------------------------------------------

void X11SalObject::GrabFocus()
{
    if( mbVisible )
         XSetInputFocus( (Display*)maSystemChildData.pDisplay,
                         maSystemChildData.aWindow,
                         RevertToNone,
                         CurrentTime );
}

// -----------------------------------------------------------------------

void X11SalObject::SetBackground()
{
}

// -----------------------------------------------------------------------

void X11SalObject::SetBackground( SalColor )
{
}

// -----------------------------------------------------------------------

const SystemChildData* X11SalObject::GetSystemData() const
{
    return &maSystemChildData;
}

static sal_uInt16 sal_GetCode( int state )
{
    sal_uInt16 nCode = 0;

    if( state & Button1Mask )
        nCode |= MOUSE_LEFT;
    if( state & Button2Mask )
        nCode |= MOUSE_MIDDLE;
    if( state & Button3Mask )
        nCode |= MOUSE_RIGHT;

    if( state & ShiftMask )
        nCode |= KEY_SHIFT;
    if( state & ControlMask )
        nCode |= KEY_MOD1;
    if( state & Mod1Mask )
        nCode |= KEY_MOD2;
        if( state & Mod3Mask )
                nCode |= KEY_MOD3;

    return nCode;
}

long X11SalObject::Dispatch( XEvent* pEvent )
{
    std::list< SalObject* >& rObjects = GetX11SalData()->GetDisplay()->getSalObjects();

    for( std::list< SalObject* >::iterator it = rObjects.begin(); it != rObjects.end(); ++it )
    {
        X11SalObject* pObject = static_cast<X11SalObject*>(*it);
        if( pEvent->xany.window == pObject->maPrimary ||
            pEvent->xany.window == pObject->maSecondary )
        {
            if( pObject->IsMouseTransparent() && (
                    pEvent->type == ButtonPress     ||
                    pEvent->type == ButtonRelease   ||
                    pEvent->type == EnterNotify     ||
                    pEvent->type == LeaveNotify     ||
                    pEvent->type == MotionNotify
                    )
               )
            {
                SalMouseEvent aEvt;
                const SystemEnvData* pParentData = pObject->mpParent->GetSystemData();
                int dest_x, dest_y;
                XLIB_Window aChild = None;
                XTranslateCoordinates( pEvent->xbutton.display,
                                       pEvent->xbutton.root,
                                       pParentData->aWindow,
                                       pEvent->xbutton.x_root,
                                       pEvent->xbutton.y_root,
                                       &dest_x, &dest_y,
                                       &aChild );
                aEvt.mnX        = dest_x;
                aEvt.mnY        = dest_y;
                aEvt.mnTime     = pEvent->xbutton.time;
                aEvt.mnCode     = sal_GetCode( pEvent->xbutton.state );
                aEvt.mnButton   = 0;
                sal_uInt16 nEvent = 0;
                if( pEvent->type == ButtonPress ||
                    pEvent->type == ButtonRelease )
                {
                    switch( pEvent->xbutton.button )
                    {
                        case Button1: aEvt.mnButton = MOUSE_LEFT;break;
                        case Button2: aEvt.mnButton = MOUSE_MIDDLE;break;
                        case Button3: aEvt.mnButton = MOUSE_RIGHT;break;
                    }
                    nEvent = (pEvent->type == ButtonPress) ?
                             SALEVENT_MOUSEBUTTONDOWN :
                             SALEVENT_MOUSEBUTTONUP;
                }
                else if( pEvent->type == EnterNotify )
                    nEvent = SALEVENT_MOUSELEAVE;
                else
                    nEvent = SALEVENT_MOUSEMOVE;
                pObject->mpParent->CallCallback( nEvent, &aEvt );
            }
            else
            {
                switch( pEvent->type )
                {
                    case UnmapNotify:
                    pObject->mbVisible = sal_False;
                    return 1;
                    case MapNotify:
                    pObject->mbVisible = sal_True;
                    return 1;
                    case ButtonPress:
                    pObject->CallCallback( SALOBJ_EVENT_TOTOP, NULL );
                    return 1;
                    case FocusIn:
                    pObject->CallCallback( SALOBJ_EVENT_GETFOCUS, NULL );
                    return 1;
                    case FocusOut:
                    pObject->CallCallback( SALOBJ_EVENT_LOSEFOCUS, NULL );
                    return 1;
                    default: break;
                }
            }
            return 0;
        }
    }
    return 0;
}

// -----------------------------------------------------------------------

void X11SalObject::InterceptChildWindowKeyDown( sal_Bool /*bIntercept*/ )
{
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */