///////////////////////////////////////////////////////////////////////
// Header file: "Ddx.h"
//
// (C) Copyright 2001, RightMark Gathering.  All Rights Reserved.
//
// @Author: Konstantin Mironovich (km) develop@rightmark3d.org
//
// Purpose: 
//   This file is an extension of Windows Template Library.
//   Provides support for DDX/DDV
//
// History: 
//   2001/05/21 created. (km)
//
///////////////////////////////////////////////////////////////////////

#pragma once

#include <atlbase.h>
#include <atlapp.h>
#include <commctrl.h>       // includes the common control header
#pragma comment(lib, "comctl32.lib")

#include <AtlDdx.h>



// @TRICKY: get back to ATL namespace to avoid conflict with WTL classes
namespace ATL
{

// Customized DDX map macros

// Create spin buddy and set integer value within range
//
#define DDX_INT_SPIN(nID, var, min, max) \
        if (nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            if (!DDX_Int_Spin(nID, var, true, bSaveAndValidate, min, max)) \
                return false; \
        }

// Select string in combo box on var position
//
#define DDX_COMBO_INDEX(nID, var) \
        if (nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            if (!DDX_Combo_Index(nID, var, true, bSaveAndValidate)) \
                return false; \
        }

// Check box with var value and 
// enable/disable dependant ID2(3) control(s)
//
#define DDX_CHECK2(nID, var, nID2) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            DDX_Check(nID, var, bSaveAndValidate); \
            ::EnableWindow(GetDlgItem(nID2), var); \
        }

#define DDX_CHECK3(nID, var, nID2, nID3) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            DDX_Check(nID, var, bSaveAndValidate); \
            ::EnableWindow(GetDlgItem(nID2), var); \
            ::EnableWindow(GetDlgItem(nID3), var); \
        }

// Check box with var value and 
// disable/enable dependant ID2(3) control(s)
//
#define DDX_CHECK2N(nID, var, nID2) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            DDX_Check(nID, var, bSaveAndValidate); \
            ::EnableWindow(GetDlgItem(nID2), !var); \
        }

#define DDX_CHECK3N(nID, var, nID2, nID3) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            DDX_Check(nID, var, bSaveAndValidate); \
            ::EnableWindow(GetDlgItem(nID2), !var); \
            ::EnableWindow(GetDlgItem(nID3), !var); \
        }

#define DDX_BITMAP(nID, var) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            DDX_Bitmap(nID, var); \
        }

    
/////////////////////////////////////////////////////////////////////////////
// CWinDataExchangeEx - provides extended support for DDX/DDV

template <class T>
class CWinDataExchangeEx : public WTL::CWinDataExchange<T>
{
public:

// Numeric exchange with spin-button
    template <class Type>
    BOOL DDX_Int_Spin(UINT nID, Type& nVal, BOOL bSigned, BOOL bSave
                    , Type nMin = 0, Type nMax = 0)
    {
        T* pT = static_cast<T*>(this);
        BOOL IsOk = true;

        if (bSave)
        {
            nVal = (Type)pT->GetDlgItemInt(nID, &IsOk, bSigned);
        }
        else
        {
            ATLASSERT(nVal >= nMin && nVal <= nMax);

            RECT rc;
            ::GetWindowRect(pT->GetDlgItem(nID), &rc);
            rc.right -= rc.left; rc.bottom -= rc.top;

            // It's important to use UDS_NOTHOUSANDS flag to avoid
            // wrong text parsing for values greater than 999.
            HWND hCtl = ::CreateUpDownControl(WS_CHILD | WS_BORDER | WS_VISIBLE
                                | UDS_ARROWKEYS | UDS_ALIGNRIGHT 
                                | UDS_SETBUDDYINT | UDS_NOTHOUSANDS
                                , 0, 0, rc.bottom, rc.bottom
                                , pT->m_hWnd, 0, _Module.GetResourceInstance()
                                , pT->GetDlgItem(nID), nMax, nMin, nVal);
            // Comctl32.dll version 4.71 or later is required
            // to support full 32-bit range for up-down control.
            ::SendMessage(hCtl, UDM_SETRANGE32, (WPARAM)nMin, (LPARAM)nMax);
            // Comctl32.dll version 5.80 or later is required to support 
            // UDM_SETPOS32 parameter. So, we do not use it at the moment.
            IsOk = pT->SetDlgItemInt(nID, (UINT)nVal, nVal < 0);
            //IsOk = (hCtl != NULL);
        }

        if (!IsOk)
        {
            ATLTRACE(_T("DDX: error in conrol, ID=%d\n"), nID);
            pT->OnDataExchangeError(nID, bSave);
        }
        else if (bSave)
        {
            ATLASSERT(nMin != nMax);
            if (nVal < nMin || nVal > nMax)
            {
                _XData data;
                data.nDataType = ddxDataInt;
                data.intData.nVal = (long)nVal;
                data.intData.nMin = (long)nMin;
                data.intData.nMax = (long)nMax;
                pT->OnDataValidateError(nID, bSave, data);
                IsOk = false;
            }
        }
        return IsOk;
    }

// Combobox control
    template <class Type>
    BOOL DDX_Combo_Index(UINT nID, Type& nVal, BOOL bSigned, BOOL bSave
                       , BOOL bValidate = false, Type nMin = 0, Type nMax = 0)
    {
        T* pT = static_cast<T*>(this);
        BOOL IsOk = true;
    
        if (bSave)
        {
            nVal = (Type)::SendMessage((HWND)pT->GetDlgItem(nID), CB_GETCURSEL, 0, 0L);
            IsOk = (CB_ERR != nVal); 
        }
        else
        {
            ATLASSERT(!bValidate || nVal >= nMin && nVal <= nMax);
            IsOk = (CB_ERR != ::SendMessage((HWND)(Type)pT->GetDlgItem(nID), CB_SETCURSEL, nVal, 0L));
        }
    
        if (!IsOk)
        {
            pT->OnDataExchangeError(nID, bSave);
        }
        else if (bSave && bValidate)        // validation
        {
            ATLASSERT(nMin != nMax);
            if (nVal < nMin || nVal > nMax)
            {
                _XData data;
                data.nDataType = ddxDataInt;
                data.intData.nVal = (long)nVal;
                data.intData.nMin = (long)nMin;
                data.intData.nMax = (long)nMax;
                pT->OnDataValidateError(nID, bSave, data);
                IsOk = false;
            }
        }
        return IsOk;
    }

// Show custom bitmap
    BOOL DDX_Bitmap(UINT nID, LPTSTR lpsz)
    {
        T* pT = static_cast<T*>(this);

        CBitmapHandle bmp;
        CSize size;

        HBITMAP hbmp;

        if (NULL != lpsz && NULL == HIWORD(lpsz))
        {
            // Load image from resource
            hbmp = (HBITMAP)::LoadImage(_Module.GetResourceInstance(), lpsz, IMAGE_BITMAP, 0, 0, 0);
        }
        else
        {
            hbmp = (HBITMAP)::LoadImage(NULL, lpsz, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        }
        if (NULL == hbmp)
            return FALSE;

        bmp.Attach(hbmp);
        bmp.GetSize(size);
        // set the size of the picture.
        ::SetWindowPos(pT->GetDlgItem(nID), NULL, 0, 0, size.cx, size.cy, SWP_NOMOVE|SWP_NOZORDER);

        pT->SendDlgItemMessage(nID, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bmp.m_hBitmap);
        
        return TRUE;
    }
#ifdef __ATLSTR_H__
    BOOL DDX_Bitmap(UINT nID, CString& str)
    {
        return DDX_Bitmap(nID, (LPTSTR)(LPCTSTR)str);
    }
#endif //__ATLSTR_H__

// Dialog initialization support
    BOOL ExecuteDlgInit(int IDD)
    {
        PVOID   pResource = NULL;
        HGLOBAL hResource = NULL;

        if (IDD)
        {
            HRSRC hDlg = ::FindResource(_Module.GetResourceInstance()
                                      , MAKEINTRESOURCE(IDD), RT_DLGINIT);
            if (hDlg)
            {
                hResource = ::LoadResource(_Module.GetResourceInstance(), hDlg);
                if (NULL == hResource)
                    return false;
                // lock it
                pResource = ::LockResource(hResource);
                ATLASSERT(NULL != pResource);
            }
        }

        return ExecuteDlgInit(pResource);
}

    BOOL ExecuteDlgInit(PVOID pResource)
    {
        T* pT = static_cast<T*>(this);
        BOOL IsOk = true;
        if (NULL != pResource)
        {
            UNALIGNED WORD* pnRes = (WORD*)pResource;
            while (IsOk && 0 != *pnRes)
            {
                WORD nIDC = *pnRes++;
                WORD nMsg = *pnRes++;
                DWORD len = *((UNALIGNED DWORD*&)pnRes)++;

                // In Win32 the WM_ messages have changed. They have
                // to be translated from the 32-bit values to 16-bit values here.
                #define WIN16_LB_ADDSTRING  0x0401
                #define WIN16_CB_ADDSTRING  0x0403
                #define AFX_CB_ADDSTRING    0x1234

                // unfortunately, WIN16_CB_ADDSTRING == CBEM_INSERTITEM
                if (nMsg == AFX_CB_ADDSTRING)
                    nMsg = CBEM_INSERTITEM;
                else if (nMsg == WIN16_LB_ADDSTRING)
                    nMsg = LB_ADDSTRING;
                else if (nMsg == WIN16_CB_ADDSTRING)
                    nMsg = CB_ADDSTRING;

            // check for invalid/unknown message types
            ATLASSERT(nMsg == LB_ADDSTRING || nMsg == CB_ADDSTRING || nMsg == CBEM_INSERTITEM);

#ifdef _DEBUG
            // For AddStrings, the count must exactly delimit the string, 
            // including the NULL termination. This check will not catch 
            // all mal-formed ADDSTRINGs, but will catch some.
            if (nMsg == LB_ADDSTRING || nMsg == CB_ADDSTRING || nMsg == CBEM_INSERTITEM)
                ATLASSERT(0 == *((LPBYTE)pnRes + (UINT)len - 1));
#endif

            if (nMsg == CBEM_INSERTITEM)
            {
                USES_CONVERSION;
                COMBOBOXEXITEM item;
                item.mask = CBEIF_TEXT;
                item.iItem = -1;
                item.pszText = A2T(LPSTR(pnRes));

                if (CB_ERR == ::SendDlgItemMessage(pT->m_hWnd, nIDC, nMsg, 0, (LPARAM) &item))
                    IsOk = false;
            }
            else if (nMsg == LB_ADDSTRING || nMsg == CB_ADDSTRING)
            {
                // List/Combobox returns -1 for error
                if (CB_ERR == ::SendDlgItemMessageA(pT->m_hWnd, nIDC, nMsg, 0, (LPARAM) pnRes))
                    IsOk = false;
            }

            // skip past data
            pnRes = (WORD*)((LPBYTE)pnRes + (UINT)len);
        }
    }

    return IsOk;
}

};

}; //namespace ATL

