//=--------------------------------------------------------------------------=
// CardCtl.Cpp
//=--------------------------------------------------------------------------=
// Copyright  1995  Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
//
//
#include "IPServer.H"

#include "Guids.H"
#include "LocalObj.H"
#include "Util.H"
#include "Globals.H"
#include "Resource.H"
#include "cardctl.H"

#include "carddraw.h"

extern HBITMAP cardcacheMem[];
extern CRITICAL_SECTION drawCritSec;

// for ASSERT and FAIL
//
SZTHISFILE

WCHAR wszImage [] = L"Card";

//=--------------------------------------------------------------------------=
// all the events in this control
//
// TODO: add events here ...
//

static VARTYPE rgMouse[] = { VT_I2, VT_I2, VT_I4, VT_I4 };
enum { MouseMove=0, MouseDown, MouseUp, Click, DblClick };

static EVENTINFO rgEvents [] = {
    { DISPID_MOUSEMOVE, 4, rgMouse },
    { DISPID_MOUSEDOWN, 4, rgMouse },
    { DISPID_MOUSEUP, 4, rgMouse },
    { DISPID_CLICK, 0, NULL },
    { DISPID_DBLCLICK, 0, NULL },
};

//=--------------------------------------------------------------------------=
// array describing all of our property pages.  these clsids are typically
// in guids.h
//
// TODO: add any additional property page guids here ...
//
const GUID *rgCardPropPages [] = {
    &CLSID_CardGeneralPage
};

//=--------------------------------------------------------------------------=
// Custum Verb information
//
// TODO: add any custom verbs here in an array, using the VERBINFO structure.
//       then mark the controld def'n in CardCtl.H with
//       this verb array
//


//=--------------------------------------------------------------------------=
// CCardControl::CCardControl
//=--------------------------------------------------------------------------=
// "Being born is like being kidnapped.  And then sold into slavery."
//    - andy warhol (1928 - 87)
//
// Parameters:
//    IUnknown *        - [in]
//
// Notes:
//
#pragma warning(disable:4355)  // using 'this' in constructor
CCardControl::CCardControl
(
    IUnknown *pUnkOuter
)
: COleControl(pUnkOuter, OBJECT_TYPE_CTLCARD, (IDispatch *)this)
{
	static SIZEL size = { CCardDraw::CardSizeX, CCardDraw::CardSizeY };

	m_number = DefaultNumber;
	m_suite = DefaultSuite;
	m_invert = DefaultInvert;
	m_cardalignment = DefaultCardAlignment;
	BackBrush = NULL;

	SetControlSize( &size );

#if 0
// for better performance when the cards are first displayed (such as
// when a full deck is displayed to the user), un-ifdef this code.  
// for better initial control creation time, keep this code ifdef'd.  

	// initialize the standard 52 cards (no jokers, no backs)
	if( !cardcacheMem[0] )
	{
	        EnterCriticalSection( &drawCritSec );
		if( !cardcacheMem[0] )
		{
			CCardDraw cd( CCardDraw::DrawAll );
			HDC drawDc;
			HBITMAP drawDcMem;
			int t;

			drawDc = CreateCompatibleDC( NULL );

			for( t = 0; t < 52; t++ )
			{
				cardcacheMem[t] = cd.CreateDIBCard( drawDc );
				drawDcMem = SelectObject( drawDc, cardcacheMem[t] );	
				cd.DrawCard( drawDc, (t/14) + 1, (t%14) + 1 );
				SelectObject( drawDc, drawDcMem );
			}
			DeleteDC( drawDc );
		}
		LeaveCriticalSection( &drawCritSec );
	}
#endif
}
#pragma warning(default:4355)  // using 'this' in constructor

//=--------------------------------------------------------------------------=
// CCardControl::~CCardControl
//=--------------------------------------------------------------------------=
// "We all labour against our own cure, for death is the cure of all diseases"
//    - Sir Thomas Browne (1605 - 82)
//
// Notes:
//
CCardControl::~CCardControl ()
{
	if( BackBrush )
		DeleteObject( BackBrush );
}

//=--------------------------------------------------------------------------=
// CCardControl:RegisterClassData
//=--------------------------------------------------------------------------=
// register the window class information for your control here.
// this information will automatically get cleaned up for you on DLL shutdown.
//
// Output:
//    BOOL            - FALSE means fatal error.
//
// Notes:
//
BOOL CCardControl::RegisterClassData()
{
    WNDCLASS wndclass;

    // TODO: register any additional information you find interesting here.
    //       this method is only called once for each type of control
    //
    memset(&wndclass, 0, sizeof(WNDCLASS));
    wndclass.style          = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_OWNDC;
    wndclass.lpfnWndProc    = COleControl::ControlWindowProc;
    wndclass.hInstance      = g_hInstance;
    wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wndclass.lpszClassName  = WNDCLASSNAMEOFCONTROL(OBJECT_TYPE_CTLCARD);

    return RegisterClass(&wndclass);
}

//=--------------------------------------------------------------------------=
// CCardControl::InternalQueryInterface
//=--------------------------------------------------------------------------=
// qi for things only we support.
//
// Parameters:
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CCardControl::InternalQueryInterface
(
    REFIID  riid,
    void  **ppvObjOut
)
{
    IUnknown *pUnk;

    *ppvObjOut = NULL;

    // TODO: if you want to support any additional interrfaces, then you should
    // indicate that here.  never forget to call COleControl's version in the
    // case where you don't support the given interface.
    //
    if (DO_GUIDS_MATCH(riid, IID_ICard)) {
        pUnk = (IUnknown *)(ICard *)this;
    } else{
        return COleControl::InternalQueryInterface(riid, ppvObjOut);
    }

    pUnk->AddRef();
    *ppvObjOut = (void *)pUnk;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::LoadTextState
//=--------------------------------------------------------------------------=
// load in our text state for this control.
//
// Parameters:
//    IPropertyBag *        - [in] property bag to read from
//    IErrorLog *           - [in] errorlog object to use with proeprty bag
//
// Output:
//    HRESULT
//
// Notes:
//    - NOTE: if you have a binary object, then you should pass an unknown
//      pointer to the property bag, and it will QI it for IPersistStream, and
//      get said object to do a Load()
//
STDMETHODIMP CCardControl::LoadTextState
(
    IPropertyBag *pPropertyBag,
    IErrorLog    *pErrorLog
)
{
	VARIANT v;
	HRESULT hr;

	VariantInit(&v);    

	v.vt = VT_I2;
	hr = pPropertyBag->Read(L"Number",&v,pErrorLog);
	if (SUCCEEDED(hr))
		m_number = (enumCardNumber) v.iVal;
	ValidateNumber();
	VariantClear(&v);

	v.vt = VT_I2;
	hr = pPropertyBag->Read(L"Suite",&v,pErrorLog);
	if (SUCCEEDED(hr))
		m_suite = (enumCardSuite) v.iVal;
	ValidateSuite();
	VariantClear(&v);

	v.vt = VT_BOOL;
	hr = pPropertyBag->Read(L"Invert",&v,pErrorLog);
	if (SUCCEEDED(hr))
		m_invert = v.boolVal;
	VariantClear(&v);

	v.vt = VT_I2;
	hr = pPropertyBag->Read(L"CardAlignment",&v,pErrorLog);
	if (SUCCEEDED(hr))
		m_cardalignment = (enumCardAlignment) v.iVal;
	ValidateCardAlignment();
	VariantClear(&v);

    return S_OK;
}

#define		STREAMHDR_MAGIC		1296924267

//=--------------------------------------------------------------------------=
// CCardControl::LoadBinaryState
//=--------------------------------------------------------------------------=
// loads in our binary state using streams.
//
// Parameters:
//    IStream *            - [in] stream to write to.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CCardControl::LoadBinaryState
(
    IStream *pStream
)
{
    HRESULT hr;
	DWORD sh;

	hr = pStream->Read(&sh, sizeof(sh), NULL);
	RETURN_ON_FAILURE(hr);

	if (sh != STREAMHDR_MAGIC )
		return E_UNEXPECTED;

	hr = pStream->Read(&(m_number),sizeof(m_number),NULL);
	RETURN_ON_FAILURE(hr);
	ValidateNumber();

	hr = pStream->Read(&(m_suite),sizeof(m_suite),NULL);
	RETURN_ON_FAILURE(hr);
	ValidateSuite();

	hr = pStream->Read(&(m_invert),sizeof(m_invert),NULL);
	RETURN_ON_FAILURE(hr);

	hr = pStream->Read(&(m_cardalignment),sizeof(m_cardalignment),NULL);
	RETURN_ON_FAILURE(hr);
	ValidateCardAlignment();

	return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::SaveTextState
//=--------------------------------------------------------------------------=
// saves out the text state for this control using a property bag.
//
// Parameters:
//    IPropertyBag *        - [in] the property bag with which to work.
//    BOOL                  - [in] if TRUE, then write out ALL properties, even
//                            if they're their the default value ...
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CCardControl::SaveTextState
(
    IPropertyBag *pPropertyBag,
    BOOL          fWriteDefaults
)
{
	VARIANT v;
	HRESULT hr;

	if( m_number != DefaultNumber )
	{
		VariantInit(&v);
		v.vt = VT_I2;
		v.iVal = m_number;
		hr = pPropertyBag->Write( L"Number", &v);
	}

	if( m_suite != DefaultSuite )
	{
		VariantInit(&v);
		v.vt = VT_I2;
		v.iVal = m_suite;
		hr = pPropertyBag->Write( L"Suite", &v);
	}

	if( m_invert != DefaultInvert )
	{
		VariantInit(&v);
		v.vt = VT_BOOL;
		v.boolVal = m_invert;
		hr = pPropertyBag->Write( L"Invert", &v);
	}

	if( m_cardalignment != DefaultCardAlignment )
	{
		VariantInit(&v);
		v.vt = VT_I2;
		v.iVal = m_cardalignment;
		hr = pPropertyBag->Write( L"CardAlignment", &v);
	}

	return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::SaveBinaryState
//=--------------------------------------------------------------------------=
// save out the binary state for this control, using the given IStream object.
//
// Parameters:
//    IStream  *             - [in] save to which you should save.
//
// Output:
//    HRESULT
//
// Notes:
//    - it is important that you seek to the end of where you saved your
//      properties when you're done with the IStream.
//
STDMETHODIMP CCardControl::SaveBinaryState
(
    IStream *pStream
)
{
    HRESULT hr;
    DWORD sh = STREAMHDR_MAGIC;    

    // write out the stream hdr.
    //
    hr = pStream->Write(&sh, sizeof(sh), NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_number),sizeof(m_number),NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_suite),sizeof(m_suite),NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_invert),sizeof(m_invert),NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_cardalignment),sizeof(m_cardalignment),NULL);
    RETURN_ON_FAILURE(hr);

    return S_OK;
}


//=--------------------------------------------------------------------------=
// CCardControl::OnDraw
//=--------------------------------------------------------------------------=
// "I don't very much enjoy looking at paintings in general.  i know too
//  much about them.  i take them apart."
//    - georgia o'keeffe (1887-1986)
//
// Parameters:
//    HDC                - [in]  HDC to draw to
//    LPCRECTL           - [in]  rect we're drawing to
//    LPCRECTL           - [in]  window extent and origin for meta-files
//    HDC                - [in]  HIC for target device
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT CCardControl::OnDraw
(
    DWORD    dvaspect,        
    HDC      hdcDraw,
    LPCRECTL prcBounds,
    LPCRECTL prcWBounds,
    HDC      hicTargetDevice,
    BOOL     fOptimize
)
{
	int OffsetX, OffsetY;
	RECTL rcBoundsDP;
	int width, height;
	int savedDC;
	HDC drawDc;
	HBITMAP drawDcMem;
	int c;

	if( !Windowless() && !BackBrush )
	{
		OLE_COLOR BackColor;
		COLORREF BackColorCR;
		if( !GetAmbientProperty( DISPID_AMBIENT_BACKCOLOR, VT_I4, &BackColor ) )
			BackColor = 0;
		OleTranslateColor( BackColor, NULL, &BackColorCR );
		BackBrush = CreateSolidBrush( BackColorCR );
	}
		
	rcBoundsDP = *prcBounds;
	LPtoDP( hdcDraw, (POINT *) &rcBoundsDP, 2 );

	width = rcBoundsDP.right - rcBoundsDP.left;
	height = rcBoundsDP.bottom - rcBoundsDP.top;

	switch( m_cardalignment )
	{
		case CardTopCenter: 
		case CardCenter:
		case CardBottomCenter:
			OffsetX = (width - CCardDraw::CardSizeX) / 2;
			break;
		case CardTopRight: 
		case CardCenterRight:
		case CardBottomRight:
			OffsetX = (width - CCardDraw::CardSizeX);
			break;
		default:
			OffsetX = 0;
			break;
	}
	switch( m_cardalignment )
	{
		case CardCenterRight:
		case CardCenter:
		case CardCenterLeft:
			OffsetY = (height - CCardDraw::CardSizeY) / 2;
			break;
		case CardBottomRight: 
		case CardBottomCenter: 
		case CardBottomLeft:
			OffsetY = (height - CCardDraw::CardSizeY);
			break;
		default:
			OffsetY = 0;
			break;
	}

	if( BackBrush )
		FillRect( hdcDraw, (RECT *) prcBounds, BackBrush );

	if( m_suite == CardBlank )
		return S_OK;

	savedDC = SaveDC( hdcDraw );

	SetMapMode( hdcDraw, MM_TEXT );
	SetWindowOrgEx( hdcDraw, -(OffsetX + rcBoundsDP.left), 
				 -(OffsetY + rcBoundsDP.top), NULL );
	SetViewportOrgEx( hdcDraw, 0, 0, NULL );

	if( width < CCardDraw::CardSizeX || height < CCardDraw::CardSizeY )
	{
		HRGN clipRgn;
		clipRgn = CreateRectRgn( rcBoundsDP.left, 
					 rcBoundsDP.top, 
					 rcBoundsDP.left + width, 
					 rcBoundsDP.top + height );
		SelectClipRgn( hdcDraw, clipRgn );
		DeleteObject( clipRgn );
	}		

	SelectObject( hdcDraw, GetStockObject( m_invert ? WHITE_PEN : BLACK_PEN ) );

	drawDc = CreateCompatibleDC( NULL );

	if( m_suite > 0 )
		c = (m_suite-1)*14 + (m_number-1);
	else
		c = (m_suite < -12) ? 56 : -m_suite + 56;

	if( !cardcacheMem[c] )
	{
		CCardDraw cd( (c > 55 ? CCardDraw::DrawBack : (int) m_number) );

	        EnterCriticalSection( &drawCritSec );
		if( !cardcacheMem[c] )
		{
			cardcacheMem[c] = cd.CreateDIBCard( hdcDraw );
			drawDcMem = SelectObject( drawDc, cardcacheMem[c] );	
			cd.DrawCard( drawDc, m_suite, m_number );
		}
		else
			drawDcMem = SelectObject( drawDc, cardcacheMem[c] );	
		LeaveCriticalSection( &drawCritSec );
	}
	else
		drawDcMem = SelectObject( drawDc, cardcacheMem[c] );	

	if( m_cardalignment != CardStretch )
	{
	  	BitBlt( hdcDraw, 1, 1, CCardDraw::CardSizeX-2, CCardDraw::CardSizeY-2,
		 	   drawDc, 1, 1, m_invert ? NOTSRCCOPY : SRCCOPY );
		Polyline( hdcDraw, CCardDraw::CardBorder, 
				sizeof(CCardDraw::CardBorder)/sizeof(POINT) );
	}
	else
	{
		POINT border[9];
		memcpy( border, CCardDraw::CardBorder, sizeof(border) );
		border[2].x = border[5].x = width-3;
		border[3].x = border[4].x = width-1;
		border[4].y = border[7].y = height-3;
		border[5].y = border[6].y = height-1;
		StretchBlt( hdcDraw, 1, 1, width-2, height-2,
				drawDc, 1, 1, 
				CCardDraw::CardSizeX-2,
				CCardDraw::CardSizeY-2,
				m_invert ? NOTSRCCOPY : SRCCOPY );
		Polyline( hdcDraw, border, sizeof(border)/sizeof(POINT) );
	}

	RestoreDC( hdcDraw, savedDC );

	SelectObject( drawDc, drawDcMem );
	DeleteDC( drawDc );
	
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::WindowProc
//=--------------------------------------------------------------------------=
// window procedure for this control.  nothing terribly exciting.
//
// Parameters:
//     see win32sdk on window procs.
//
// Notes:
//
LRESULT CCardControl::WindowProc
(
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    POINTS pt;
    RECT rect;
    int b, s;
    int ev = -1;

    switch( msg )
    {
	case WM_MOUSEMOVE:
		ev = MouseMove;
		// FALL THROUGH!
	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		if( ev == -1 )	ev = MouseDown;
		// FALL THROUGH!
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		if( ev == -1 )  ev = MouseUp;
		pt = MAKEPOINTS( lParam );
		OcxGetWindowRect( &rect );
		b = (wParam & 3) == 3 ? 4 : wParam & 3;
		s = (wParam >> 2) & 7;
		FireEvent( &rgEvents[ev], b, s, pt.x-rect.left, pt.y-rect.top );
		if( msg == WM_LBUTTONUP )
			FireEvent( &rgEvents[Click] );
		break;
	case WM_LBUTTONDBLCLK:
		FireEvent( &rgEvents[DblClick] );
		break;
    }

    return OcxDefWindowProc(msg, wParam, lParam);
}

//=--------------------------------------------------------------------------=
// CCardControl::AboutBox
//=--------------------------------------------------------------------------=
// prints up an about box.  fweeeee.
//
// Notes:
//
void CCardControl::AboutBox
(
    void
)
{
    // TODO: Ideally, one would use DialogBox, and some sort of Dialog Box here if
    // they wanted a slightly more interesting About Box ...  you should
    // still call ModalDialog first, however.
    //
    ModalDialog(TRUE);
    MessageBox(NULL, "Microsoft ActiveX Card Control\n\n"
		     "Copyright © 1996 Microsoft Corp.",
		     "About Card", 
		     MB_OK | MB_TASKMODAL);
    ModalDialog(FALSE);
}

STDMETHODIMP  CCardControl::get_Number(enumCardNumber * number)
{
	CHECK_POINTER(number);

	*number = m_number;

	return( S_OK );
}

STDMETHODIMP  CCardControl::put_Number(enumCardNumber number)
{
	if( m_number == number ) 
		return S_OK;

	m_number = number;
	ValidateNumber();
	if( m_suite >= 1 )
	{
		if( Windowless() )
		{
			RECT rect;
			OcxGetWindowRect( &rect );
			OcxInvalidateRect( &rect, TRUE );
		}
		else
			InvalidateControl( NULL );
	}

	// update anybody who cares about property changes and mark ourselves
	// as dirty
	//
	PropertyChanged(DISPID_NUMBER);
	m_fDirty = TRUE;

    return S_OK;
}

STDMETHODIMP  CCardControl::get_Invert( VARIANT_BOOL *invert )
{
	CHECK_POINTER(invert);

	*invert = m_invert;

	return( S_OK );
}

STDMETHODIMP  CCardControl::put_Invert( VARIANT_BOOL invert )
{
	if( m_invert == invert )
		return S_OK;

	m_invert = invert;

	{
		if( Windowless() )
		{
			RECT rect;
			OcxGetWindowRect( &rect );
			OcxInvalidateRect( &rect, TRUE );
		}
		else
			InvalidateControl( NULL );
	}

	// update anybody who cares about property changes and mark ourselves
	// as dirty
	//
	PropertyChanged(DISPID_INVERT);
	m_fDirty = TRUE;

    return S_OK;
}

STDMETHODIMP  CCardControl::get_CardAlignment( enumCardAlignment *cardalignment )
{
	CHECK_POINTER(cardalignment);

	*cardalignment = m_cardalignment;

	return( S_OK );
}

STDMETHODIMP  CCardControl::put_CardAlignment( enumCardAlignment cardalignment )
{
	if( m_cardalignment == cardalignment )
		return S_OK;

	m_cardalignment = cardalignment;	
	ValidateCardAlignment();

	{
		if( Windowless() )
		{
			RECT rect;
			OcxGetWindowRect( &rect );
			OcxInvalidateRect( &rect, TRUE );
		}
		else
			InvalidateControl( NULL );
	}

	// update anybody who cares about property changes and mark ourselves
	// as dirty
	//
	PropertyChanged(DISPID_CARDALIGNMENT);
	m_fDirty = TRUE;

    return S_OK;
}

STDMETHODIMP  CCardControl::get_Suite(enumCardSuite * suite)
{
	CHECK_POINTER(suite);

	*suite = m_suite;

	return( S_OK );
}

STDMETHODIMP  CCardControl::put_Suite(enumCardSuite suite)
{
	if( m_suite == suite ) 
		return S_OK;

	m_suite = suite;
	ValidateSuite();

	{
		if( Windowless() )
		{
			RECT rect;
			OcxGetWindowRect( &rect );
			OcxInvalidateRect( &rect, TRUE );
		}
		else
			InvalidateControl( NULL );
	}

	// update anybody who cares about property changes and mark ourselves
	// as dirty
	//
	PropertyChanged(DISPID_SUITE);
	m_fDirty = TRUE;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::Create
//=--------------------------------------------------------------------------=
// global static function that creates an instance of the control an returns
// an IUnknown pointer for it.
//
// Parameters:
//    IUnknown *        - [in] controlling unknown for aggregation
//
// Output:
//    IUnknown *        - new object.
//
// Notes:
//
IUnknown *CCardControl::Create
(
    IUnknown *pUnkOuter
)
{
    // make sure we return the private unknown so that we support aggegation
    // correctly!
    //
    CCardControl *pNew = new CCardControl(pUnkOuter);
    return pNew->PrivateUnknown();
}

  				  	   	 	
  			 		   	  	
