/*
 * USER DCE functions
 *
 * Copyright 1993 Alexandre Julliard
 */

static char Copyright[] = "Copyright  Alexandre Julliard, 1993";

#include "dce.h"
#include "class.h"
#include "win.h"
#include "gdi.h"
#include "user.h"
#include "sysmetrics.h"
#include "stddebug.h"
/* #define DEBUG_DC */
/* #undef  DEBUG_DC */
#include "debug.h"


#define NB_DCE    5  /* Number of DCEs created at startup */

extern Display * display;

static HANDLE firstDCE = 0;
static HDC defaultDCstate = 0;


/***********************************************************************
 *           DCE_AllocDCE
 *
 * Allocate a new DCE.
 */
HANDLE DCE_AllocDCE( DCE_TYPE type )
{
    DCE * dce;
    HANDLE handle = USER_HEAP_ALLOC( GMEM_MOVEABLE, sizeof(DCE) );
    if (!handle) return 0;
    dce = (DCE *) USER_HEAP_ADDR( handle );
    if (!(dce->hdc = CreateDC( "DISPLAY", NULL, NULL, NULL )))
    {
	USER_HEAP_FREE( handle );
	return 0;
    }
    dce->hwndCurrent = 0;
    dce->type  = type;
    dce->inUse = (type != DCE_CACHE_DC);
    dce->xOrigin = 0;
    dce->yOrigin = 0;
    dce->hNext = firstDCE;
    firstDCE = handle;
    return handle;
}


/***********************************************************************
 *           DCE_FreeDCE
 */
void DCE_FreeDCE( HANDLE hdce )
{
    DCE * dce;
    HANDLE *handle = &firstDCE;

    if (!(dce = (DCE *) USER_HEAP_ADDR( hdce ))) return;
    while (*handle && (*handle != hdce))
    {
	DCE * prev = (DCE *) USER_HEAP_ADDR( *handle );	
	handle = &prev->hNext;
    }
    if (*handle == hdce) *handle = dce->hNext;
    DeleteDC( dce->hdc );
    USER_HEAP_FREE( hdce );
}


/***********************************************************************
 *           DCE_Init
 */
void DCE_Init()
{
    int i;
    HANDLE handle;
    DCE * dce;
        
    for (i = 0; i < NB_DCE; i++)
    {
	if (!(handle = DCE_AllocDCE( DCE_CACHE_DC ))) return;
	dce = (DCE *) USER_HEAP_ADDR( handle );	
	if (!defaultDCstate) defaultDCstate = GetDCState( dce->hdc );
    }
}


/***********************************************************************
 *           DCE_GetVisRect
 *
 * Calc the visible rectangle of a window, i.e. the client or
 * window area clipped by the client area of all ancestors.
 * Return FALSE if the visible region is empty.
 */
static BOOL DCE_GetVisRect( WND *wndPtr, BOOL clientArea, RECT *lprect )
{
    int xoffset, yoffset;

    *lprect = clientArea ? wndPtr->rectClient : wndPtr->rectWindow;
    xoffset = lprect->left;
    yoffset = lprect->top;

    if (!(wndPtr->dwStyle & WS_VISIBLE) || (wndPtr->flags & WIN_NO_REDRAW))
    {
        SetRectEmpty( lprect );  /* Clip everything */
        return FALSE;
    }

    while (wndPtr->hwndParent)
    {
	WND *parentPtr = WIN_FindWndPtr( wndPtr->hwndParent );
        if (!(parentPtr->dwStyle & WS_VISIBLE) ||
            (parentPtr->flags & WIN_NO_REDRAW) ||
            (parentPtr->dwStyle & WS_ICONIC))
        {
            SetRectEmpty( lprect );  /* Clip everything */
            return FALSE;
        }
	xoffset += parentPtr->rectClient.left;
	yoffset += parentPtr->rectClient.top;
	OffsetRect( lprect, parentPtr->rectClient.left,
		    parentPtr->rectClient.top );

	  /* Warning!! we assume that IntersectRect() handles the case */
	  /* where the destination is the same as one of the sources.  */
	if (!IntersectRect( lprect, lprect, &parentPtr->rectClient ))
            return FALSE;  /* Visible rectangle is empty */
	wndPtr = parentPtr;
    }
    OffsetRect( lprect, -xoffset, -yoffset );
    return TRUE;
}


/***********************************************************************
 *           DCE_ClipWindows
 *
 * Go through the linked list of windows from hwndStart to hwndEnd,
 * removing from the given region the rectangle of each window offset
 * by a given amount.  The new region is returned, and the original one
 * is destroyed.  Used to implement DCX_CLIPSIBLINGS and
 * DCX_CLIPCHILDREN styles.
 */
static HRGN DCE_ClipWindows( HWND hwndStart, HWND hwndEnd,
                             HRGN hrgn, int xoffset, int yoffset )
{
    HRGN hrgnTmp, hrgnNew;
    WND *wndPtr;

    if (!hwndStart) return hrgn;
    for (; hwndStart != hwndEnd; hwndStart = wndPtr->hwndNext)
    {
        hrgnTmp = hrgnNew = 0;
        wndPtr = WIN_FindWndPtr( hwndStart );
        if (!(wndPtr->dwStyle & WS_VISIBLE)) continue;
        if (!(hrgnTmp = CreateRectRgn( 0, 0, 0, 0 ))) break;
        if (!(hrgnNew = CreateRectRgn( wndPtr->rectWindow.left + xoffset,
                                       wndPtr->rectWindow.top + yoffset,
                                       wndPtr->rectWindow.right + xoffset,
                                       wndPtr->rectWindow.bottom + yoffset )))
            break;
        if (!CombineRgn( hrgnTmp, hrgn, hrgnNew, RGN_DIFF )) break;
        DeleteObject( hrgn );
        DeleteObject( hrgnNew );
        hrgn = hrgnTmp;
    }
    if (hwndStart != hwndEnd)  /* something went wrong */
    {
        if (hrgnTmp) DeleteObject( hrgnTmp );
        if (hrgnNew) DeleteObject( hrgnNew );
        if (hrgn) DeleteObject( hrgn );
        return 0;
    }
    return hrgn;
}


/***********************************************************************
 *           DCE_GetVisRgn
 *
 * Return the visible region of a window, i.e. the client or window area
 * clipped by the client area of all ancestors, and then optionally
 * by siblings and children.
 */
static HRGN DCE_GetVisRgn( HWND hwnd, WORD flags )
{
    RECT rect;
    HRGN hrgn;
    int xoffset, yoffset;
    WND *wndPtr = WIN_FindWndPtr( hwnd );

      /* Get visible rectangle and create a region with it */

    if (!DCE_GetVisRect( wndPtr, !(flags & DCX_WINDOW), &rect ))
    {
        return CreateRectRgn( 0, 0, 0, 0 );  /* Visible region is empty */
    }
    if (!(hrgn = CreateRectRgnIndirect( &rect ))) return 0;

      /* Clip all children from the visible region */

    if (flags & DCX_CLIPCHILDREN)
    {
        if (flags & DCX_WINDOW)
        {
            xoffset = wndPtr->rectClient.left - wndPtr->rectWindow.left;
            yoffset = wndPtr->rectClient.top - wndPtr->rectWindow.top;
        }
        else xoffset = yoffset = 0;
        hrgn = DCE_ClipWindows( wndPtr->hwndChild, 0, hrgn, xoffset, yoffset );
        if (!hrgn) return 0;
    }

      /* Clip siblings placed above this window */

    if (flags & DCX_WINDOW)
    {
        xoffset = -wndPtr->rectWindow.left;
        yoffset = -wndPtr->rectWindow.top;
    }
    else
    {
        xoffset = -wndPtr->rectClient.left;
        yoffset = -wndPtr->rectClient.top;
    }
    if (flags & DCX_CLIPSIBLINGS)
    {
        hrgn = DCE_ClipWindows( GetWindow( wndPtr->hwndParent, GW_CHILD ),
                                hwnd, hrgn, xoffset, yoffset );
        if (!hrgn) return 0;
    }

      /* Clip siblings of all ancestors that have the WS_CLIPSIBLINGS style */

    while (wndPtr->dwStyle & WS_CHILD)
    {
        hwnd = wndPtr->hwndParent;
        wndPtr = WIN_FindWndPtr( hwnd );
        xoffset -= wndPtr->rectClient.left;
        yoffset -= wndPtr->rectClient.top;
        hrgn = DCE_ClipWindows( GetWindow( wndPtr->hwndParent, GW_CHILD ),
                                hwnd, hrgn, xoffset, yoffset );
        if (!hrgn) return 0;
    }
    return hrgn;
}


/***********************************************************************
 *           DCE_SetDrawable
 *
 * Set the drawable, origin and dimensions for the DC associated to
 * a given window.
 */
static void DCE_SetDrawable( WND *wndPtr, DC *dc, WORD flags )
{
    if (!wndPtr)  /* Get a DC for the whole screen */
    {
        dc->w.DCOrgX = 0;
        dc->w.DCOrgY = 0;
        dc->u.x.drawable = rootWindow;
        XSetSubwindowMode( display, dc->u.x.gc, IncludeInferiors );
    }
    else
    {
        if (flags & DCX_WINDOW)
        {
            dc->w.DCOrgX  = wndPtr->rectWindow.left;
            dc->w.DCOrgY  = wndPtr->rectWindow.top;
        }
        else
        {
            dc->w.DCOrgX  = wndPtr->rectClient.left;
            dc->w.DCOrgY  = wndPtr->rectClient.top;
        }
        while (!wndPtr->window)
        {
            wndPtr = WIN_FindWndPtr( wndPtr->hwndParent );
            dc->w.DCOrgX += wndPtr->rectClient.left;
            dc->w.DCOrgY += wndPtr->rectClient.top;
        }
        dc->w.DCOrgX -= wndPtr->rectWindow.left;
        dc->w.DCOrgY -= wndPtr->rectWindow.top;
        dc->u.x.drawable = wndPtr->window;
    }
}


/***********************************************************************
 *           GetDCEx    (USER.359)
 */
/* Unimplemented flags: DCX_LOCKWINDOWUPDATE
 */
HDC GetDCEx( HWND hwnd, HRGN hrgnClip, DWORD flags )
{
    HANDLE hdce;
    HRGN hrgnVisible;
    HDC hdc = 0;
    DCE * dce;
    DC * dc;
    WND * wndPtr;
    
    if (hwnd)
    {
	if (!(wndPtr = WIN_FindWndPtr( hwnd ))) return 0;
    }
    else wndPtr = NULL;

    if (flags & DCX_USESTYLE)
    {
        /* Set the flags according to the window style. */
	/* Not sure if this is the real meaning of the DCX_USESTYLE flag... */
	flags &= ~(DCX_CACHE | DCX_CLIPCHILDREN |
                   DCX_CLIPSIBLINGS | DCX_PARENTCLIP);
	if (wndPtr)
	{
            if (!(WIN_CLASS_STYLE(wndPtr) & (CS_OWNDC | CS_CLASSDC)))
		flags |= DCX_CACHE;
            if (WIN_CLASS_STYLE(wndPtr) & CS_PARENTDC) flags |= DCX_PARENTCLIP;
	    if (wndPtr->dwStyle & WS_CLIPCHILDREN) flags |= DCX_CLIPCHILDREN;
	    if (wndPtr->dwStyle & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS;
	}
	else flags |= DCX_CACHE;
    }

      /* Can only use PARENTCLIP on child windows */
    if (!wndPtr || !(wndPtr->dwStyle & WS_CHILD)) flags &= ~DCX_PARENTCLIP;

      /* Whole window DC implies using cache DC and not clipping children */
    if (flags & DCX_WINDOW) flags = (flags & ~DCX_CLIPCHILDREN) | DCX_CACHE;

    if (flags & DCX_CACHE)
    {
	for (hdce = firstDCE; (hdce); hdce = dce->hNext)
	{
	    if (!(dce = (DCE *) USER_HEAP_ADDR( hdce ))) return 0;
	    if ((dce->type == DCE_CACHE_DC) && (!dce->inUse)) break;
	}
    }
    else hdce = wndPtr->hdce;

    if (!hdce) return 0;
    dce = (DCE *) USER_HEAP_ADDR( hdce );
    dce->hwndCurrent = hwnd;
    dce->inUse       = TRUE;
    hdc = dce->hdc;
    
      /* Initialize DC */
    
    if (!(dc = (DC *) GDI_GetObjPtr( hdc, DC_MAGIC ))) return 0;

    DCE_SetDrawable( wndPtr, dc, flags );
    if (hwnd)
    {
        if (flags & DCX_PARENTCLIP)  /* Get a VisRgn for the parent */
        {
            WND *parentPtr = WIN_FindWndPtr( wndPtr->hwndParent );
            DWORD newflags = flags & ~(DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN |
                                       DCX_WINDOW);
            if (parentPtr->dwStyle & WS_CLIPSIBLINGS)
                newflags |= DCX_CLIPSIBLINGS;
            hrgnVisible = DCE_GetVisRgn( wndPtr->hwndParent, newflags );
            if (flags & DCX_WINDOW)
                OffsetRgn( hrgnVisible, -wndPtr->rectWindow.left,
                                        -wndPtr->rectWindow.top );
            else OffsetRgn( hrgnVisible, -wndPtr->rectClient.left,
                                         -wndPtr->rectClient.top );
        }
        else hrgnVisible = DCE_GetVisRgn( hwnd, flags );
    }
    else  /* Get a VisRgn for the whole screen */
    {
        hrgnVisible = CreateRectRgn( 0, 0, SYSMETRICS_CXSCREEN,
                                     SYSMETRICS_CYSCREEN);
    }

      /* Intersect VisRgn with the given region */

    if ((flags & DCX_INTERSECTRGN) || (flags & DCX_EXCLUDERGN))
    {
	HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
	if (hrgn)
	{
            CombineRgn( hrgn, hrgnVisible, hrgnClip,
                       (flags & DCX_INTERSECTRGN) ? RGN_AND : RGN_DIFF );
	    DeleteObject( hrgnVisible );
            hrgnVisible = hrgn;
	}
    }
    SelectVisRgn( hdc, hrgnVisible );
    DeleteObject( hrgnVisible );

    dprintf_dc(stddeb, "GetDCEx(%d,%d,0x%lx): returning %d\n", 
	       hwnd, hrgnClip, flags, hdc);
    return hdc;
}


/***********************************************************************
 *           GetDC    (USER.66)
 */
HDC GetDC( HWND hwnd )
{
    return GetDCEx( hwnd, 0, DCX_USESTYLE );
}


/***********************************************************************
 *           GetWindowDC    (USER.67)
 */
HDC GetWindowDC( HWND hwnd )
{
    int flags = DCX_CACHE | DCX_WINDOW;
    if (hwnd)
    {
	WND * wndPtr;
	if (!(wndPtr = WIN_FindWndPtr( hwnd ))) return 0;
/*	if (wndPtr->dwStyle & WS_CLIPCHILDREN) flags |= DCX_CLIPCHILDREN; */
	if (wndPtr->dwStyle & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS;
    }
    return GetDCEx( hwnd, 0, flags );
}


/***********************************************************************
 *           ReleaseDC    (USER.68)
 */
int ReleaseDC( HWND hwnd, HDC hdc )
{
    HANDLE hdce;
    DCE * dce = NULL;
    
    dprintf_dc(stddeb, "ReleaseDC: %d %d\n", hwnd, hdc );
        
    for (hdce = firstDCE; (hdce); hdce = dce->hNext)
    {
	if (!(dce = (DCE *) USER_HEAP_ADDR( hdce ))) return 0;
	if (dce->inUse && (dce->hdc == hdc)) break;
    }
    if (!hdce) return 0;

    if (dce->type == DCE_CACHE_DC)
    {
	SetDCState( dce->hdc, defaultDCstate );
	dce->inUse = FALSE;
    }
    return 1;
}
