/********************************************************************
*   DrvDC.c
*
*   Macintosh device specific drawing context sub-system implementation.
*
*   Copyright (c) 1995-1997, Willows Software Inc.  All rights reserved.  
********************************************************************/

#include <stdlib.h>
#include <string.h>

#include "DrvHook.h"
#include "DrvDC.h"
#include "DrvWindows.h"
#include "DrvSystem.h"
#include "DrvText.h"
#include "GdiDC.h"
#include "Log.h"

//#define SIMPLECLIPSWAP

/* Local prototypes */
static DWORD DrvSetUpdateClip(LPDRIVERDC lpddc, RgnHandle rgn, RgnHandle visRgn);
static DWORD DrvSetVisRgn(LPDRIVERDC lpddc, RgnHandle visRgn);
static DWORD CombineClipRgn(LPDRIVERDC lpddc);
static DWORD DrvSetClipOrg(LPDRIVERDC lpddc, LPPOINT lppt);
static DWORD DrvGetDCClip(LPDRIVERDC lpddc);
static DWORD DrvSetDCClip(LPDRIVERDC lpddc, RgnHandle clipRgn);
static DWORD DrvSaveDC(LPDRIVERDC lpddc);
static DWORD DrvRestoreDC(LPDRIVERDC lpddc1, LPDRIVERDC lpddc2);
DWORD DrvCreateDC(DWORD dwDCXFlags, WindowRef parentWin);
static DWORD DrvDeleteDC(LPDRIVERDC lppdc);

/* Globals for the DrvSelectDC call, used in save/restore operations */
static GrafPtr gSavePort;
static RgnHandle gSaveClip = NULL;
static int gSelectDepth = 0;
static PixMapHandle gSavedPortPixMap=NULL;

#define PMROWBYTESMSB 	0x8000	/* The MSB of the rowBytes field needs to be one to signify it is a pixmap */


/********************************************************************
*	PrivateDCHook
*
********************************************************************/
DWORD PrivateDCHook(WORD wFunc, LPARAM dwParam1, LPARAM dwParam2, LPVOID lpStruct)
{

    switch (wFunc) {
	/* (dwParam)?INITIALIZE:DEINITIALIZE */
	case DSUBSYSTEM_INIT:
		return (DWORD)0;

	case DSUBSYSTEM_GETCAPS:
	    return (DWORD)0;

	case DSUBSYSTEM_EVENTS:
	    return (DWORD)0;

	/* dwParam1 - dwDCXFlags for the DC being created */
	/* lpStruct - WindowRef */
	case PDCH_CREATE:
		return DrvCreateDC(dwParam1, (WindowRef)lpStruct);

	/* lpStruct - LPDRIVERDC for the DC being deleted */
	case PDCH_DELETE:
	    return DrvDeleteDC((LPDRIVERDC)lpStruct);

	/* lpStruct - LPDRIVERDC for the DC being saved */
	case PDCH_SAVE:
	    return DrvSaveDC((LPDRIVERDC)lpStruct);

	/* lpStruct - LPDRIVERDC for the DC copy being released */
	/* dwParam1, if set - LPDRIVERDC for the DC being restored */
	case PDCH_RESTORE:
	    return DrvRestoreDC((LPDRIVERDC)lpStruct, (LPDRIVERDC)dwParam1);

	/* lpStruct - LPDRIVERDC for the DC to inquire */
	case PDCH_GETCLIP:
	    return DrvGetDCClip((LPDRIVERDC)lpStruct);
	    
   	/* lpStruct - LPDRIVERDC for the DC to set clip for */
	/* dwParam1 - Region to use as clip or NULL for no clipping */
	case PDCH_SETCLIP:
	    return DrvSetDCClip((LPDRIVERDC)lpStruct, (RgnHandle)dwParam1);

   	/* lpStruct - LPDRIVERDC for the DC to set clip for */
	/* dwParam1 - LPPOINT to set origin to */
	case PDCH_SETCLIPORG:
	    return DrvSetClipOrg((LPDRIVERDC)lpStruct, (LPPOINT)dwParam1);

	/* lpStruct - LPDRIVERDC for the DC to set update clip for */
	/* dwParam1 - Update region to use as clip or NULL for no clipping */
	/* dwParam2 - pointer to client clip rectangle for DCX_CLIENTCLIP */
	case PDCH_SETUPDATECLIP:
	    return DrvSetUpdateClip((LPDRIVERDC)lpStruct,
			(RgnHandle)dwParam1,
			(RgnHandle)dwParam2);

	/* lpStruct - LPDRIVERDC for the DC to set visrgn for */
	/* dwParam2 - vis region to set */
	case PDCH_SETVISRGN:
	    return DrvSetVisRgn((LPDRIVERDC)lpStruct,(RgnHandle)dwParam2);
		
	default:
	    return (DWORD)0;

	}

}


/********************************************************************
*	DrvSetDCClip
*
*	Set the clip region of the specified DC.
********************************************************************/
static DWORD DrvSetDCClip(LPDRIVERDC lpddc, RgnHandle clipRgn)
{

	/* If there is an empty update region, dispose of it */
	if ((lpddc->updateRegion) && EmptyRgn(lpddc->updateRegion)) {
		DisposeRgn(lpddc->updateRegion);
		lpddc->updateRegion = NULL;
	}
	
	/* Get rid of the old region */
	if (lpddc->clipRegion) {
		DisposeRgn(lpddc->clipRegion);
		lpddc->clipRegion = NULL;
	}
	
	/* If there is a non empty region passed in, set the clip region to it */
	if (clipRgn && !EmptyRgn(clipRgn)) {
		lpddc->clipRegion = NewRgn();
		CopyRgn(clipRgn, lpddc->clipRegion);
	}
	
	/* Combine the regions */	
    return CombineClipRgn(lpddc);	
}


/********************************************************************
*	DrvGetDCClip
*
*	Set the clip region of the specified DC.
********************************************************************/
static DWORD DrvGetDCClip(LPDRIVERDC lpddc)
{
	return((DWORD)lpddc->clipRegion);
}


/********************************************************************
*	DrvSetUpdateClip
*
* Sets the update and clip regions the passed region.
* If rgn is empty or NULL, update and clip regions are disposed.
********************************************************************/
static DWORD DrvSetUpdateClip(LPDRIVERDC lpddc, RgnHandle rgn, RgnHandle visRgn)
{

	/* If there is a non-empty update region, remember it */
	if (rgn && !EmptyRgn(rgn)) {
		if (!lpddc->updateRegion)
			lpddc->updateRegion = NewRgn();		/* Create if needed */
		CopyRgn(rgn, lpddc->updateRegion);		/* Clear old clip */
	}
	else
		if (lpddc->updateRegion) {
			DisposeRgn(lpddc->updateRegion);
			lpddc->updateRegion = NULL;
		}

	/* No user-defined clipping at this point */
	if (lpddc->clipRegion) {
		DisposeRgn(lpddc->clipRegion);
		lpddc->clipRegion = NULL;
	}
	
	/* No combine all three regions */
	DrvSetVisRgn(lpddc, visRgn);

	return(TRUE);
}


/********************************************************************
*	DrvSetVisRgn
*
* Sets the visible region.
********************************************************************/
static DWORD DrvSetVisRgn(LPDRIVERDC lpddc, RgnHandle visRgn)
{
int nRet = NULLREGION;

	if (lpddc->visibleRegion)
		DisposeRgn(lpddc->visibleRegion);
		
	/* If there is a new visible region, set it */	
	if (visRgn) {
		lpddc->visibleRegion = NewRgn();
		CopyRgn(visRgn, lpddc->visibleRegion);
		nRet = COMPLEXREGION;
	}	
	else
		lpddc->visibleRegion = NULL;			/* No region exists */
		
	CombineClipRgn(lpddc);
		
	return(nRet);
}



/********************************************************************
*	CombineClipRgn
*
*
********************************************************************/
static DWORD CombineClipRgn(LPDRIVERDC lpddc)
{

	/* The combined region holds the final clipping to be performed on a window */
	if (!lpddc->combinedRegion)
		lpddc->combinedRegion = NewRgn();

	/* Calculate the combined region of all three if it is visible */
	if (lpddc->visibleRegion) {
		CopyRgn(lpddc->visibleRegion, lpddc->combinedRegion);
		if (lpddc->clipRegion)			/* Intersect the clip region if it exists */
			SectRgn(lpddc->clipRegion, lpddc->combinedRegion, lpddc->combinedRegion);
		if (lpddc->updateRegion)		/* Intersect the update region if it exists */
			SectRgn(lpddc->updateRegion, lpddc->combinedRegion, lpddc->combinedRegion);
	}
	else {
		if (lpddc->clipRegion) {
			/* Make the clip region the starting "visible" region */
			CopyRgn(lpddc->clipRegion, lpddc->combinedRegion);
			if (lpddc->updateRegion)	/* Intersect the update region if it exists */
				SectRgn(lpddc->updateRegion, lpddc->combinedRegion, lpddc->combinedRegion);
		}
		else {
			if (lpddc->updateRegion)
				CopyRgn(lpddc->updateRegion, lpddc->combinedRegion);
			else {
				DisposeRgn(lpddc->combinedRegion);
				lpddc->combinedRegion = NULL;
				return(NULLREGION);
			}
		}
	}

	/* Set clip origin here */
	/* This clip will be applied to the GrafPort's clip region */
	if (lpddc->combinedRegion) {
		OffsetRgn(lpddc->combinedRegion, lpddc->clipOriginX, lpddc->clipOriginY);
		/* Now intersect with any special region the real window has (like a grow box) */
		SectRgn(lpddc->combinedRegion, ((CGrafPtr)lpddc->grafPort)->clipRgn, lpddc->combinedRegion);
	}
	
	return(COMPLEXREGION);
}

/********************************************************************
*	DrvSetClipOrg
*
*	Set the clip region's logical point of origin.
********************************************************************/
static DWORD DrvSetClipOrg(LPDRIVERDC lpddc, LPPOINT lppt)
{

	/* Save the clip origin */
	lpddc->clipOriginX = lppt->x;
	lpddc->clipOriginY = lppt->y;
	
	return 1L;
}


/********************************************************************
*	DrvSaveDC
*
*	Save the DC.
********************************************************************/
static DWORD DrvSaveDC(LPDRIVERDC lpddc)
{
LPDRIVERDC lpNewddc;

	/* Allocate storage for a new dc */
    lpNewddc = (LPDRIVERDC)DrvMalloc(sizeof(DRIVERDC));
    memcpy((LPSTR)lpNewddc,(LPSTR)lpddc,sizeof(DRIVERDC));

	/* Save the clip region if it existed */
    if (lpNewddc->clipRegion) {
		lpNewddc->clipRegion = NewRgn();
		CopyRgn(lpddc->clipRegion, lpNewddc->clipRegion);
	}

	/* Save the update region if it existed */
    if (lpNewddc->updateRegion) {
		lpNewddc->updateRegion = NewRgn();
		CopyRgn(lpddc->updateRegion, lpNewddc->updateRegion);
	}

	/* Save the visibleRegion region if it existed */
    if (lpNewddc->visibleRegion) {
		lpNewddc->visibleRegion = NewRgn();
		CopyRgn(lpddc->visibleRegion, lpNewddc->visibleRegion);
	}

	/* Save the combinedRegion region if it existed */
    if (lpNewddc->combinedRegion) {
		lpNewddc->combinedRegion = NewRgn();
		CopyRgn(lpddc->combinedRegion, lpNewddc->combinedRegion);
	}
	
	return((DWORD)lpNewddc);
}



/********************************************************************
*	DrvRestoreDC
*
* If called with lpddc2==NULL, DrvRestoreDC() destroys clipping region
* in lpddc1 and deallocates the structure; called with lpddc2, it saves
* the drawable from lpddc1 to lpddc2, invalidates the fields in lpddc2 that
* don't make sense anymore, and also frees the memory taken by lpddc1
********************************************************************/
static DWORD DrvRestoreDC(LPDRIVERDC lpddc1, LPDRIVERDC lpddc2)
{
    if (lpddc1->clipRegion)
		DisposeRgn(lpddc1->clipRegion);

    if (lpddc1->updateRegion)
		DisposeRgn(lpddc1->updateRegion);

    if (lpddc1->visibleRegion)
		DisposeRgn(lpddc1->visibleRegion);

    if (lpddc1->combinedRegion)
		DisposeRgn(lpddc1->combinedRegion);

    if (lpddc2) {
    	/* Keep the selected port */
    	lpddc2->grafPort = lpddc1->grafPort;
    	lpddc2->bLocalGrafPort = lpddc1->bLocalGrafPort;
    	
    	if ((lpddc2->clipRegion != NULL) || (lpddc2->updateRegion !=NULL)) {
    	}
    	
	}

    DrvFree(lpddc1);

    return ((DWORD)PGH_SUCCESS);
}



/********************************************************************
*	DrvCreateDC
*
*	Create the driver portion of the DC and initialize the variables.
********************************************************************/
DWORD DrvCreateDC(DWORD dwDCXFlags, WindowRef parentWin)
{
LPDRIVERDC lpddc;
PixMapHandle newPixMap;
GrafPtr savePort;
Rect r;

	GetPort(&savePort);									/* Get current port to restore */

	/* Allocate memory for the driver dc structure */
	lpddc = (LPDRIVERDC) DrvMalloc(sizeof(DRIVERDC));
	
	memset(lpddc, '\0', sizeof(DRIVERDC));			/* Slick way to clear memory */
	lpddc->dwDCFlags = dwDCXFlags;						/* Store the flags */

	/* Check to see if this dc is a memory dc */
    if (lpddc->dwDCFlags & DCX_COMPATIBLE_DC) {
		/* Allocate and open the graphics port */
		lpddc->grafPort = (CGrafPtr) DrvMalloc(sizeof(CGrafPort));

		/* Open the color port, this will initialize everything to the root windows parameters */
		OpenCPort(lpddc->grafPort);							/* Has side effect of making this the active port */
		lpddc->bLocalGrafPort = TRUE;						/* Driver allocated grafport */

		/* Set clip rect to pixmap's bounds rect */
		newPixMap = ((CGrafPtr) lpddc->grafPort)->portPixMap;
		r = (*newPixMap)->bounds;
		
		/* Set the port pix map to be the memory one we just created */
		/* This is applied to the current grafport, which is dc->grafPort */
		SetPortPix(newPixMap);

	}
    else {
		if (parentWin == NULL) {
			/* Rootless dc, create a copy of the win mgr port */
			lpddc->grafPort = (CGrafPtr) DrvMalloc(sizeof(CGrafPort));
			/* Open the color port, this will initialize everything to the root windows parameters */
			OpenCPort(lpddc->grafPort);							/* Has side effect of making this the active port */
			lpddc->bLocalGrafPort = TRUE;						/* Driver allocated grafport */
		}
		else {
			/* Screen dc will use the top-level window's graf port */
			lpddc->grafPort = parentWin;		
		}				
    		
		/* Create the image STRUCTURE and place the screen contents into it */
		newPixMap = ((CGrafPtr) lpddc->grafPort)->portPixMap;
		r = (*newPixMap)->bounds;

		/* Allocate the image structure */
		/* A screen DC is our domain, and we must manage the image contents */
		lpddc->lpDrvImage=DrvMalloc(sizeof(DRVIMAGEDATA));	
   		lpddc->lpDrvImage->nWidth 		= r.right - r.left;
    	lpddc->lpDrvImage->nHeight 		= r.bottom - r.top;
    	lpddc->lpDrvImage->nLinePad	= 0;
    	lpddc->lpDrvImage->nBitsPixel 	= (*newPixMap)->pixelSize;
    	lpddc->lpDrvImage->nWidthBytes = (*newPixMap)->rowBytes | !(PMROWBYTESMSB);	/* new */
    	lpddc->lpDrvImage->fMono 		= FALSE;
    	lpddc->lpDrvImage->fInvalid 		= FALSE;
	    lpddc->lpDrvImage->image 		= newPixMap;
	}
	
	lpddc->updateRegion = NULL;
	lpddc->brushFlag = BFP_PIXEL;				/* Default to a solid color */
	lpddc->brushFillPat = NULL;

	LOGSTR((LF_OBJECTS,"PDCH: Create DC returns lpddc = %x grafPort = %x (%s)\n", lpddc, lpddc->grafPort, (lpddc->dwDCFlags & DCX_COMPATIBLE_DC)? "MEMORY":"SCREEN"));
	
	return((DWORD)lpddc);
}




/********************************************************************
*	DrvDeleteDC
*
*	Dispose the driver portion of the DC.
********************************************************************/
static DWORD DrvDeleteDC(LPDRIVERDC lpddc)
{
GrafPtr savePort;

	LOGSTR((LF_OBJECTS,"PDCH: Delete DC lpddc = %x grafPort = %x (%s)\n", lpddc, lpddc->grafPort, (lpddc->dwDCFlags & DCX_COMPATIBLE_DC)? "MEMORY":"SCREEN"));

	GetPort(&savePort);						/* Get the currently set port */
	if (savePort == lpddc->grafPort)		/* See if it is the one we are disposing */
		savePort = NULL;							/* We are disposing it, nothing to save */

	/* Dispose of any regions if they exist */
	if (lpddc->updateRegion)
		DisposeRgn(lpddc->updateRegion);		
	if (lpddc->clipRegion)
		DisposeRgn(lpddc->clipRegion);			
	if (lpddc->visibleRegion)
		DisposeRgn(lpddc->visibleRegion);		
	if (lpddc->combinedRegion)
		DisposeRgn(lpddc->combinedRegion);		

	/* Dispose of the grafport if we (the driver) allocated it */
	if (lpddc->bLocalGrafPort) {
		CloseCPort(lpddc->grafPort);		/* Disposes all the memory in the port */
		DrvFree(lpddc->grafPort);			/* Disposes the port itself */
	}
	
	/* With a screen dc, we allocated an image structure, dispose of it */
	if (!(lpddc->dwDCFlags & DCX_COMPATIBLE_DC))
		DrvFree(lpddc->lpDrvImage);		/* Dispose of the screen image structure */

	DrvFree(lpddc);								/* Free the old structure */

	if (savePort)
		SetPort(savePort);						/* Restore the port if it existed */

	return(1L);
}



/********************************************************************
*   DrvSelectDC
*
*  A utility to save/restore temporary ports in a DC.  
*	It also sets origins, saves window clip region, etc.
*  Use the SAVE and RESTORE definitions for readability.
*	lpddc does not have to be passed for a RESTORE operation.
********************************************************************/
int DrvSelectDC(int save, LPDRIVERDC lpddc)
{

	if (save) {
		GetPort(&gSavePort);				/* Get current port */
		if (lpddc && lpddc->grafPort) {
			SetPort(lpddc->grafPort);		/* Set the new one, if it exists */
			gSelectDepth++;
		} else {
			Debugger();			//Why is there no grafport??  Is this valid?
			gSelectDepth++;	//Temporary for now, to handle the sync problem
		}
		
		/* Now setup the dc's (graf port) clipping */
#ifndef SIMPLECLIPSWAP

		if (!gSaveClip)			
			gSaveClip = NewRgn();
		GetClip(gSaveClip);					/* Save the current port clip region (for restoring) */

		/* Set the combined clipping region (clip & vis & update)*/
		if(lpddc && lpddc->combinedRegion)
			SetClip(lpddc->combinedRegion);	
#else
		/* Save the current port clip region (for restoring) */
		gSaveClip = ((CGrafPtr)lpddc->grafPort)->clipRgn;
		/* Set the combined clipping region (clip & vis & update)*/
		if(lpddc && lpddc->combinedRegion)
			((CGrafPtr)lpddc->grafPort)->clipRgn = lpddc->combinedRegion;
#endif		
			
		/* If it is a memory DC, make the image's pixmap the ports pixmap */
	    if (lpddc && (lpddc->dwDCFlags & DCX_COMPATIBLE_DC)) {
			gSavedPortPixMap = ((CGrafPtr) lpddc->grafPort)->portPixMap;
			SetPortPix(lpddc->lpDrvImage->image);
			LOGSTR((LF_OBJECTS,"PDCH: Select Memory DC %x port = %x  oldport = %x  image = %x\n", lpddc, lpddc->grafPort, gSavePort, lpddc->lpDrvImage->image));
		}
		else {
			LOGSTR((LF_OBJECTS,"PDCH: Select DC %x  port = %x  oldport = %x\n", lpddc, lpddc->grafPort, gSavePort));
			gSavedPortPixMap = NULL;	/* Did not replace port pix map */
		}
		
//PaintRect(&((CGrafPtr)lpddc->grafPort)->portRect);

	}
	else {
		/* restore clip before setting to old port */
#ifndef SIMPLECLIPSWAP
		SetClip(gSaveClip);
#else
		((CGrafPtr)lpddc->grafPort)->clipRgn = gSaveClip;
#endif

		/* Restore the original port pix map if it was replaced */
	    if (gSavedPortPixMap)
			SetPortPix(gSavedPortPixMap);

		gSelectDepth--;	
		
		SetPort(gSavePort);					/* Restore the old port */
	}

	/* For debugging, make sure we don't get out of sync */
	if ((gSelectDepth > 1) || (gSelectDepth < 0))
		DebugStr("\pDrvSelectDC:  Depth is out of sync!");
		
	return(true);
}


