#ifndef __tmplrEdit_h #define __tmplrEdit_h #include #include ///////////////////////////////////////////////////////////////////////////// // 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 here to do something") // ); // ... // } #define LINKSTARTTAG _T("") #define LINKENDTAG _T("") template 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 (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 // After identifying the link we will strip off the tags and send the message // to DefWindowProc // Ex: "Click here 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 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 (::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