#ifndef __tmplrEdit_h
#define __tmplrEdit_h

#include <atlctrls.h>
#include <winuser.h>

/////////////////////////////////////////////////////////////////////////////
// CWindowImplHotlinkRichEdit 
// Purpose - To display a hyperlink control (like Syslink in Whistler) using a rich edit ctrl
//
// Usage   - CWindowImplHotlinkRichEdit<> m_Hotlink;
//           CDialog::OnInitDialog(..) 
//           {
//              ...
//              m_Hotlink.SubClassWindow( GetDlgItem( IDC_RICHEDIT1 ));
//              ::SendMessage   (
//                                  GetDlgItem(IDC_RICHEDIT1), WM_SETTEXT, 0 ,
//                                  (LPARAM) _T("Click <A>here</A> to do something")
//                              );
//              ...
//           }


#define LINKSTARTTAG    _T("<A>")
#define LINKENDTAG      _T("</A>")

template <class T = CRichEditCtrl, class TBase = CWindow, class TWinTraits = CControlWinTraits>
class CWindowImplHotlinkRichEdit : public CWindowImpl< T, TBase, TWinTraits >
{

private:

    
    int m_iLinkIndex;
    RECT m_rect;
    BOOL m_bHasFocus;
    
  
public:


    CWindowImplHotlinkRichEdit() : m_iLinkIndex(-1), m_bHasFocus(FALSE)
    {
        ZeroMemory(&m_rect, sizeof(m_rect));
                
    }



    BEGIN_MSG_MAP(CWindowImplHotlinkRichEdit)
        MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
        MESSAGE_HANDLER(WM_SETTEXT, OnSetText)
        MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
        MESSAGE_HANDLER(WM_PAINT, OnPaint)
        MESSAGE_HANDLER(WM_CHAR, OnChar)
        MESSAGE_HANDLER(WM_NCHITTEST, OnHitTest) 
        MESSAGE_HANDLER(WM_KEYDOWN, OnKey) 
        MESSAGE_HANDLER(WM_KEYUP, OnKey) 
    END_MSG_MAP()


    HFONT CharFormatToHFont(CHARFORMAT * pcf)
    {
        // Create a font that matches the font specified by pcf

        HFONT hFont = NULL;
        HDC hDC = GetDC();

        if (pcf)
        {
            LOGFONT lf;

            ZeroMemory(&lf, sizeof(lf));

            lf.lfCharSet =  pcf->bCharSet;
            lf.lfPitchAndFamily = pcf->bPitchAndFamily;

            // yHeight is in twips. 
            
            lf.lfHeight =  -1 * pcf->yHeight / 20.0 * GetDeviceCaps (hDC, LOGPIXELSY) / 72;

            _tcsncpy(lf.lfFaceName, pcf->szFaceName, LF_FACESIZE);
            lf.lfFaceName[LF_FACESIZE-1] = NULL;

            hFont = CreateFontIndirect(&lf);
        }

        if (hDC)
        {
            ReleaseDC(hDC);
        }

        return hFont;
            
    }

    BOOL HFontToCharFormat(HFONT hFont, CHARFORMAT * pcf)
    {
        BOOL bRet = FALSE;

        HDC hDC = GetDC();

        if (hFont)
        {

            LOGFONT lf;

            ZeroMemory(&lf, sizeof(lf));

            if (GetObject(hFont, sizeof(lf), &lf))
            {
                pcf->bCharSet = lf.lfCharSet;
                pcf->bPitchAndFamily =  lf.lfPitchAndFamily;

                // yHeight is in twips

                pcf->yHeight = 20 * lf.lfHeight * 72.0 / GetDeviceCaps (hDC, LOGPIXELSY);


                pcf->yHeight = pcf->yHeight < 0 ? -pcf->yHeight : pcf->yHeight;
                    
                _tcsncpy(pcf->szFaceName, lf.lfFaceName, LF_FACESIZE);
                pcf->szFaceName[LF_FACESIZE-1] = NULL;

                pcf->dwMask |= CFM_CHARSET | CFM_FACE | CFM_SIZE;

                bRet = TRUE;
            }
        }

        if (hDC)
        {
            ReleaseDC(hDC);
        }

        return bRet;
    }

   

	BOOL SubclassWindow(HWND hWnd)
	{
        BOOL bRC = FALSE;

        if (::IsWindow(hWnd))
        {
            bRC = CWindowImpl< T, TBase, TWinTraits >::SubclassWindow( hWnd );

            ::SendMessage(hWnd, EM_SETSEL, -1, 0);
            
        }

		
        return bRC;
	}

    LRESULT OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
    {

        DefWindowProc(uMsg, wParam, lParam);

        ::SendMessage(m_hWnd, EM_SETSEL,  -1, 0);

        HideCaret();

        if (m_bHasFocus)
        {
            DrawHotlinkFocusRect();
            
        }

        bHandled = TRUE;
        return 0;
    }

    LRESULT OnKey( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
    {
        if (VK_TAB == wParam || VK_SHIFT == wParam || VK_ESCAPE == wParam)
        {
            return DefWindowProc(uMsg, wParam, lParam);
        }

        bHandled = TRUE;
        
        return 0;
    }

    LRESULT OnChar( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
    {
        if (VK_RETURN == wParam || VK_SPACE == wParam)   // Enter and space when we have the focus...
        {
            HWND hWndParent;

            hWndParent = ::GetParent(m_hWnd);

            if (hWndParent)
            {
                NMHDR nmhdr;
                ENLINK enlink;

                nmhdr.hwndFrom = m_hWnd;
                nmhdr.idFrom = ::GetDlgCtrlID(m_hWnd);
                nmhdr.code = EN_LINK;

                enlink.msg = uMsg;
                enlink.lParam = lParam;
                enlink.wParam = wParam;
                enlink.nmhdr = nmhdr;

                // DO NOT USE PostMessage for the notification, can cause AV
                
                ::SendMessage(hWndParent, WM_NOTIFY, ::GetDlgCtrlID(m_hWnd), (LPARAM) &enlink);
            }
            
        }

        bHandled = TRUE;

        return 0;
    }


    LRESULT OnHitTest( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
    {
        POINTS pts;
        POINT pt;

        pts = MAKEPOINTS(lParam);

        POINTSTOPOINT(pt, pts);

        ::MapWindowPoints(NULL, m_hWnd, &pt, 1);

        if (PtInRect(&m_rect, pt))
        {
           return DefWindowProc(uMsg, wParam, lParam);
        }

        return HTTRANSPARENT;
    }

    LRESULT OnKillFocus( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
    {
        DefWindowProc(uMsg, wParam, lParam);

        if (TRUE == m_bHasFocus)
        {

            DrawHotlinkFocusRect();

            m_bHasFocus = FALSE;
        }


        bHandled = TRUE;

        return 0;
        
    }

    LRESULT OnFocus( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
    {
        // Each time we get WM_SETFOCUS we need to draw focus rect
        // around the link

        DefWindowProc(uMsg, wParam, lParam);

        ::SendMessage(m_hWnd, EM_SETSEL,  -1, 0);

        HideCaret();

        if (FALSE == m_bHasFocus)
        {

            DrawHotlinkFocusRect();

            m_bHasFocus = TRUE;
        }

        bHandled = TRUE;

        return 0;
    }


    LRESULT OnSetText( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
    {
        TCHAR szLink[128];
        TCHAR * szText = reinterpret_cast<LPTSTR> (lParam);
        TCHAR * szActualText = NULL;
        TCHAR * pLinkStart, * pLinkEnd, * pTemp;
        int i,j;

        // When we get WM_SETTEXT message, we will search the text for 
        // the link identified by <A> </A>
        // After identifying the link we will strip off the tags and send the message 
        // to DefWindowProc
        // Ex: "Click <A>here</A> to do something interesting"
        
        if (szText)
        {
            pLinkStart = _tcsstr(szText, LINKSTARTTAG);
            pLinkEnd = _tcsstr(szText, LINKENDTAG);

            // Make sure that we have a link in the text

            if (pLinkStart && pLinkEnd && pLinkStart < pLinkEnd)
            {
                // szActualText will hold the final text without the tags

                szActualText = new TCHAR[_tcslen(szText) + 1];

                if (szActualText)
                {
                    i = j = 0;
                    pTemp = pLinkStart + _tcslen(LINKSTARTTAG); // pTemp = "here</A> to do something interesting"
                
                    while (pTemp < pLinkEnd)
                    {
                        szLink[i++] = *pTemp++; 
                    }

                    szLink[i] = NULL;       // szLink = "here"

                    while(szText < pLinkStart)
                    {
                        szActualText[j++] = *szText++;
                    }
                    szActualText[j] = NULL; // szActualText = "Click"

                    m_iLinkIndex = j;

                    _tcscat(szActualText, szLink); // szActualText = "Click here"

                    pTemp = pLinkEnd + _tcslen(LINKENDTAG);   // pTemp = " to do something interesting"

                    _tcscat(szActualText, pTemp); // szActualText = "Click here to do something interesting"
                }
            }
        }

        bHandled = TRUE;

        if (szActualText)
        {
            HWND hWndParent;
            HFONT hFont;

            CHARFORMAT cf;

            ZeroMemory(&cf, sizeof(cf));

            cf.cbSize = sizeof(cf);

            hWndParent = ::GetParent(m_hWnd);

            if (hWndParent)
            {
                // Stick to parent window's font...

                hFont = reinterpret_cast<HFONT> (::SendMessage(hWndParent, WM_GETFONT, 0, 0));

                if (hFont)
                {
                    if (HFontToCharFormat(hFont, &cf))
                    {
                        ::SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM) &cf); 
                    }
                    
                }
            }

            // Let the control display the text without the links

            DefWindowProc(uMsg, wParam, (LPARAM) szActualText);

            // Get current char format

            ::SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_DEFAULT, (LPARAM) &cf);

            cf.dwEffects |= CFE_LINK;   // For link style

            // Select the link text

            ::SendMessage(m_hWnd, EM_SETSEL, m_iLinkIndex, m_iLinkIndex + _tcslen(szLink));

            // Change the format of the link text 

            ::SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf); 

            // Get the rect that covers the link in logical units
            // We will use this rect to draw focus rect around the link

            GetLinkRect(szActualText, szLink);

            return 0;

        
        }
        else
        {
            return DefWindowProc(uMsg, wParam, lParam);
        }

    }

    BOOL GetLinkRect(LPTSTR szText, LPCTSTR szLink)
    {
        if (!szText || !szLink)
        {
            return FALSE;
        }

        BOOL bSuccess = FALSE;

        if (-1 != m_iLinkIndex)
        {
            DWORD dwStart;
            DWORD dwEnd;

            dwStart = ::SendMessage(m_hWnd, EM_POSFROMCHAR, m_iLinkIndex, 0);
            dwEnd = ::SendMessage(m_hWnd, EM_POSFROMCHAR, m_iLinkIndex + _tcslen(szLink), 0);

            CHARFORMAT cf;

            ZeroMemory(&cf, sizeof(cf));

            cf.cbSize = sizeof(cf);

            cf.dwMask |= CFM_CHARSET | CFM_FACE | CFM_SIZE;

            ::SendMessage(m_hWnd, EM_SETSEL, 0, -1);

            ::SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);

            HFONT hFont = CharFormatToHFont(&cf);

            if (hFont)
            {
                HDC hDC = GetDC();

                if (hDC)
                {
                
                    SelectObject(hDC, hFont);
                      
                    TEXTMETRIC tm;

                    ZeroMemory(&tm, sizeof(tm));

                    if (GetTextMetrics(hDC, &tm))
                    {
                       m_rect.left = LOWORD(dwStart);
                       m_rect.top = HIWORD(dwStart);
                       m_rect.right = LOWORD(dwEnd);
                       m_rect.bottom = m_rect.top + tm.tmHeight + tm.tmDescent;

                       bSuccess = TRUE;
                    }

                    ReleaseDC(hDC);

                }

                DeleteObject(hFont);
            }
        }

        return bSuccess;

    }

    BOOL DrawHotlinkFocusRect()
    {
        BOOL bRet = FALSE;

        if (-1 != m_iLinkIndex)
        {
          
            HDC hdc = GetDC();
                            
            bRet = DrawFocusRect(hdc, &m_rect);

            ReleaseDC(hdc);
            
            
        }

        return bRet;

    }

    
};



#endif // #ifndef __tmplEdit.h