/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ #include "stdafx.h" #include "MyHyperlink.h" #include "MyHyperlinkResource.h" #include "MyMenu.h" #include "Generic.h" #include "MyApp.h" #include "MyLog.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Macros. #define TOOLTIP_ID 1 #define SETBITS(dw, bits) (dw |= bits) #define CLEARBITS(dw, bits) (dw &= ~(bits)) #define ISBITSET(dw, bit) (((dw) & (bit)) != 0L) ///////////////////////////////////////////////////////////////////////////// // Statics. const DWORD MyHyperlink::StyleUnderline = 0x00000001; const DWORD MyHyperlink::StyleUseHover = 0x00000002; const DWORD MyHyperlink::StyleAutoSize = 0x00000004; const DWORD MyHyperlink::StyleDownClick = 0x00000008; const DWORD MyHyperlink::StyleGetFocusOnClick = 0x00000010; const DWORD MyHyperlink::StyleNoHandCursor = 0x00000020; COLORREF MyHyperlink::crLinkColor = RGB_BLUE; COLORREF MyHyperlink::crVisitedColor = RGB_CYAN_DARK; COLORREF MyHyperlink::crHoverColor = RGB_RED; HCURSOR MyHyperlink::hLinkCursor = NULL; ///////////////////////////////////////////////////////////////////////////// // MyHyperlink // Constructor. MyHyperlink::MyHyperlink() : m_bOverControl(false) , m_bVisited(false) , m_dwStyle(MyHyperlink::StyleUnderline | MyHyperlink::StyleAutoSize | MyHyperlink::StyleUseHover | MyHyperlink::StyleGetFocusOnClick) { } // Destructor. /* virtual */ MyHyperlink::~MyHyperlink() { HRESULT hr(S_OK); // Delete the font, if it was ever created. if (m_Font.GetSafeHandle()) { EC_B(m_Font.DeleteObject()); } // Destroy the cursor. if (MyHyperlink::hLinkCursor) { EC_B_(::DestroyCursor(MyHyperlink::hLinkCursor)); MyHyperlink::hLinkCursor = NULL; } } IMPLEMENT_DYNAMIC(MyHyperlink, CStatic) BEGIN_MESSAGE_MAP(MyHyperlink, CStatic) //{{AFX_MSG_MAP(MyHyperlink) ON_WM_SETCURSOR() ON_WM_MOUSEMOVE() ON_WM_LBUTTONUP() ON_WM_SETFOCUS() ON_WM_KILLFOCUS() ON_WM_KEYDOWN() ON_WM_NCHITTEST() ON_WM_LBUTTONDOWN() ON_WM_CTLCOLOR_REFLECT() ON_WM_RBUTTONUP() ON_COMMAND(ID_EDIT_COPY, OnEditCopy) ON_COMMAND(ID_OPEN_URL, OnOpenUrl) ON_COMMAND(ID_TOGGLE_VISITED, OnToggleVisited) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MyHyperlink message handlers // BOOL MyHyperlink::PreTranslateMessage(MSG* pMsg) { VALIDATE; m_ToolTip.Activate(true); m_ToolTip.RelayEvent(pMsg); return CStatic::PreTranslateMessage(pMsg); } // void MyHyperlink::PreSubclassWindow() { VALIDATE; // If the URL string is empty try to set it to the window text. if (m_sURL.IsEmpty()) { GetWindowText(m_sURL); } // Check that the window text isn't empty. If it is, set it as URL string. CString sWndText; GetWindowText(sWndText); if (sWndText.IsEmpty()) { _ASSERTE(! m_sURL.IsEmpty() && "Window text and URL both NULL!"); CStatic::SetWindowText(m_sURL); } // Get the current window font. CFont* pFont = GetFont(); ASSERT_VALID(pFont); if (pFont) { LOGFONT lf; pFont->GetLogFont(&lf); lf.lfUnderline = ISBITSET(m_dwStyle, StyleUnderline); if (m_Font.CreateFontIndirect(&lf)) { CStatic::SetFont(&m_Font); } // Adjust window size to fit URL if necessary. AdjustWindow(); } else { // If GetFont() returns NULL then probably the static control is not // of a text type: it's better to set auto-resizing off. CLEARBITS(m_dwStyle, StyleAutoSize); } // Try to load a "hand" cursor. if (! ISBITSET(m_dwStyle, StyleNoHandCursor)) { SetDefaultCursor(); } // Create the tooltip. We set the max width to allow multiline tips. CRect rc; GetClientRect(rc); m_ToolTip.Create(this, TTS_ALWAYSTIP | 0x40 /* TTS_BALLOON */ ); m_ToolTip.SetMaxTipWidth(SHRT_MAX); m_ToolTip.SetDelayTime(333); m_ToolTip.AddTool(this, m_sURL, rc, TOOLTIP_ID); CStatic::PreSubclassWindow(); } // WM_SETCURSOR BOOL MyHyperlink::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { VALIDATE; UNUSED_ALWAYS(pWnd); UNUSED_ALWAYS(message); UNUSED_ALWAYS(nHitTest); bool bReturn(false); if (MyHyperlink::hLinkCursor) { ::SetCursor(MyHyperlink::hLinkCursor); bReturn = true; } // "If an application processes this message, it should return TRUE to // halt further processing or FALSE to continue." return bReturn; } // WM_MOUSEMOVE void MyHyperlink::OnMouseMove(UINT nFlags, CPoint point) { VALIDATE; UNUSED_ALWAYS(nFlags); // Cursor currently over control. if (m_bOverControl) { CRect rc; GetClientRect(rc); if (! rc.PtInRect(point)) { m_bOverControl = false; ReleaseCapture(); Invalidate(); } } else { // Cursor has left control area. m_bOverControl = true; Invalidate(); SetCapture(); } } // void MyHyperlink::OnLButtonUp(UINT nFlags, CPoint point) { VALIDATE; UNUSED_ALWAYS(nFlags); UNUSED_ALWAYS(point); FollowLink(); } // void MyHyperlink::OnSetFocus(CWnd* pOldWnd) { VALIDATE; UNUSED_ALWAYS(pOldWnd); // Repaint to set the focus. Invalidate(); } // void MyHyperlink::OnKillFocus(CWnd* pNewWnd) { VALIDATE; UNUSED_ALWAYS(pNewWnd); // Assume that control lost focus = mouse out this avoid troubles with the // Hover color. m_bOverControl = false; // Repaint to unset the focus Invalidate(); } // A non-system key has been pressed. void MyHyperlink::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { VALIDATE; // Spacebar means, do it! if (VK_SPACE == nChar) { FollowLink(); } else { // Not handled by me, so let base class have a whack. CStatic::OnKeyDown(nChar, nRepCnt, nFlags); } } // "Normally, a static control does not get mouse events unless it has // SS_NOTIFY. This achieves the same effect as SS_NOTIFY, but it's fewer // lines of code and more reliable than turning on SS_NOTIFY in OnCtlColor // because Windows doesn't send WM_CTLCOLOR to bitmap static controls." // (Paul DiLascia) UINT MyHyperlink::OnNcHitTest(CPoint point) { VALIDATE; UNUSED_ALWAYS(point); //return CStatic::OnNcHitTest(point); return HTCLIENT; } // void MyHyperlink::OnLButtonDown(UINT nFlags, CPoint point) { VALIDATE; UNUSED_ALWAYS(point); UNUSED_ALWAYS(nFlags); // Set the focus and make the link active. if (ISBITSET(m_dwStyle, StyleGetFocusOnClick)) { SetFocus(); } if (ISBITSET(m_dwStyle, StyleDownClick)) { FollowLink(); } } // HBRUSH MyHyperlink::CtlColor(CDC* pDC, UINT nCtlColor) { VALIDATE; _ASSERTE(CTLCOLOR_STATIC == nCtlColor); UNUSED_ALWAYS(nCtlColor); if (m_bOverControl && ISBITSET(m_dwStyle, StyleUseHover)) { pDC->SetTextColor(MyHyperlink::crHoverColor); } else if (m_bVisited) { pDC->SetTextColor(MyHyperlink::crVisitedColor); } else { pDC->SetTextColor(MyHyperlink::crLinkColor); } pDC->SetBkMode(TRANSPARENT); return (HBRUSH) ::GetStockObject(NULL_BRUSH); } // /* static */ void MyHyperlink::SetColors(COLORREF crLinkColor, COLORREF crVisitedColor, COLORREF crHoverColor /* = -1 */ ) { MyHyperlink::crLinkColor = crLinkColor; MyHyperlink::crVisitedColor = crVisitedColor; if (-1 == crHoverColor) { MyHyperlink::crHoverColor = ::GetSysColor(COLOR_HIGHLIGHT); } else { MyHyperlink::crHoverColor = crHoverColor; } } // /* static */ void MyHyperlink::SetColors(HYPERLINKCOLORS& linkColors) { MyHyperlink::crLinkColor = linkColors.crLink; MyHyperlink::crVisitedColor = linkColors.crVisited; MyHyperlink::crHoverColor = linkColors.crHover; } // /* static */ void MyHyperlink::GetColors(HYPERLINKCOLORS& linkColors) { linkColors.crLink = MyHyperlink::crLinkColor; linkColors.crVisited = MyHyperlink::crVisitedColor; linkColors.crHover = MyHyperlink::crHoverColor; } // /* static */ void MyHyperlink::SetLinkCursor(HCURSOR hCursor) { _ASSERTE(hCursor); MyHyperlink::hLinkCursor = hCursor; SetDefaultCursor(); } // /* static */ HCURSOR MyHyperlink::GetLinkCursor() { return MyHyperlink::hLinkCursor; } // bool MyHyperlink::ModifyLinkStyle(DWORD dwRemove, DWORD dwAdd, bool bApply /* = true */ ) { VALIDATE; // Check if we are adding and removing the same style. if ((dwRemove & dwAdd) != 0L) { return false; } // Remove old styles and set the new ones. CLEARBITS(m_dwStyle, dwRemove); SETBITS(m_dwStyle, dwAdd); if (bApply && ::IsWindow(GetSafeHwnd())) { // If possible, APPLY the new styles on the fly. if (ISBITSET(dwAdd, StyleUnderline) || ISBITSET(dwRemove, StyleUnderline)) { SwitchUnderline(); } if (ISBITSET(dwAdd, StyleAutoSize)) { AdjustWindow(); } if (ISBITSET(dwRemove, StyleUseHover)) { Invalidate(); } } return true; } // DWORD MyHyperlink::GetLinkStyle() const { VALIDATE; return m_dwStyle; } // void MyHyperlink::SetURL(const CString& sURL) { VALIDATE; m_sURL = sURL; if (::IsWindow(GetSafeHwnd())) { ShowWindow(SW_HIDE); AdjustWindow(); m_ToolTip.UpdateTipText(sURL, this, TOOLTIP_ID); ShowWindow(SW_SHOW); } } // void MyHyperlink::SetWindowText(LPCTSTR lpszText) { VALIDATE; _ASSERTE(lpszText); if (::IsWindow(GetSafeHwnd())) { // Set the window text and adjust its size while the window is kept // hidden in order to allow dynamic modification. ShowWindow(SW_HIDE); CStatic::SetWindowText(lpszText); // Resize the control if necessary. AdjustWindow(); ShowWindow(SW_SHOW); } } // void MyHyperlink::SetFont(CFont* pFont) { VALIDATE; _ASSERTE(::IsWindow(GetSafeHwnd())); _ASSERTE(pFont); // Set the window font and adjust its size while the window is kept hidden // in order to allow dynamic modification. ShowWindow(SW_HIDE); // Create the new font. LOGFONT lf; pFont->GetLogFont(&lf); m_Font.DeleteObject(); m_Font.CreateFontIndirect(&lf); // Call the base class SetFont(). CStatic::SetFont(&m_Font); // Resize the control if necessary. AdjustWindow(); ShowWindow(SW_SHOW); } // Function to set underline on/off void MyHyperlink::SwitchUnderline() { VALIDATE; LOGFONT lf; CFont* pFont = GetFont(); ASSERT_VALID(pFont); if (pFont) { pFont->GetLogFont(&lf); lf.lfUnderline = ISBITSET(m_dwStyle,StyleUnderline); m_Font.DeleteObject(); m_Font.CreateFontIndirect(&lf); SetFont(&m_Font); } } // Move and resize the window so that its client area has the same size as the // hyperlink text. This prevents the hyperlink cursor being active when it is // not over the text. void MyHyperlink::AdjustWindow() { VALIDATE; _ASSERTE(::IsWindow(GetSafeHwnd())); if (ISBITSET(m_dwStyle, StyleAutoSize)) { // Get the current window rect. CRect rcWnd; GetWindowRect(rcWnd); // For a child CWnd object, window rect is relative to the upper-left // corner of the parent window’s client area. CWnd* pParent = GetParent(); ASSERT_VALID(pParent); if (pParent) { pParent->ScreenToClient(rcWnd); } // Get the current client rect. CRect rcClient; GetClientRect(rcClient); // Calc border size based on window and client rects. int nBorderWidth(rcWnd.Width() - rcClient.Width()); int nBorderHeight(rcWnd.Height() - rcClient.Height()); // Get the extent of window text. CString sWndText; GetWindowText(sWndText); CDC* pDC = GetDC(); ASSERT_VALID(pDC); CSize sizExtent; if (pDC) { CFont* pOldFont = pDC->SelectObject(&m_Font); ASSERT_VALID(pOldFont); sizExtent = pDC->GetTextExtent(sWndText); pDC->SelectObject(pOldFont); ReleaseDC(pDC); pDC = NULL; } // Get the text justification style. DWORD dwStyle(GetStyle()); // Recalc window size and position based on text justification. if (ISBITSET(dwStyle, SS_CENTERIMAGE)) { rcWnd.DeflateRect(0, ((rcWnd.Height() - sizExtent.cy) / 2) - 1); } else { rcWnd.bottom = rcWnd.top + sizExtent.cy; } if (ISBITSET(dwStyle, SS_CENTER)) { rcWnd.DeflateRect(((rcWnd.Width() - sizExtent.cx) / 2) - 1, 0); } else if (ISBITSET(dwStyle,SS_RIGHT)) { rcWnd.left = rcWnd.right - sizExtent.cx; } else { // SS_LEFT rcWnd.right = rcWnd.left + sizExtent.cx; } // Move and resize the window. MoveWindow(rcWnd.left, rcWnd.top, rcWnd.Width() + nBorderWidth, rcWnd.Height() + nBorderHeight); } } // void MyHyperlink::SetVisited(bool bVisited /* = true */ ) { VALIDATE; m_bVisited = bVisited; } // bool MyHyperlink::IsVisited() const { VALIDATE; return m_bVisited; } ///////////////////////////////////////////////////////////////////////////// // MyHyperlink implementation // Load the "hand" cursor. /* static */ void MyHyperlink::SetDefaultCursor() { if (! MyHyperlink::hLinkCursor) { MyHyperlink::hLinkCursor = AfxGetApp()->LoadCursor(IDC_MYHYPERLINK_CUR_HAND); _ASSERTE(MyHyperlink::hLinkCursor); } } // /* static */ long MyHyperlink::GetRegKey(HKEY key, LPCTSTR lpszSubkey, LPTSTR lpszRetdata) { HKEY hkey(NULL); long lnRetval(::RegOpenKeyEx(key, lpszSubkey, 0, KEY_QUERY_VALUE, &hkey)); if (ERROR_SUCCESS == lnRetval) { long lnDataSize(MAX_PATH); TCHAR cData[MAX_PATH + 1] = { 0 }; RegQueryValue(hkey, NULL, cData, &lnDataSize); lstrcpy(lpszRetdata, cData); ::RegCloseKey(hkey); } return lnRetval; } // "GotoURL" function by Stuart Patterson. As seen in the August, 1997 Windows // Developer's Journal. /* static */ HINSTANCE MyHyperlink::GotoURL(LPCTSTR lpszUrl, int nShowCmd) { TCHAR tcKey[MAX_PATH + MAX_PATH]; // First try ShellExecute(). HINSTANCE hInst(::ShellExecute(NULL, _T("open"), lpszUrl, NULL, NULL, nShowCmd)); // If it failed, get the .htm regkey and lookup the program. if (HINSTANCE_ERROR >= (UINT) hInst) { if (ERROR_SUCCESS == GetRegKey(HKEY_CLASSES_ROOT, _T(".htm"), tcKey)) { lstrcat(tcKey, _T("\\shell\\open\\command")); if (ERROR_SUCCESS == GetRegKey(HKEY_CLASSES_ROOT, tcKey, tcKey)) { TCHAR* pos = _tcsstr(tcKey, _T("\"%1\"")); // No quotes found. if (pos == NULL) { // Check for %1, without quotes. pos = strstr(tcKey, _T("%1")); // No parameter at all... if (pos == NULL) { pos = tcKey + lstrlen(tcKey) - 1; } else { // Remove the parameter. *pos = '\0'; } } else { // Remove the parameter. *pos = '\0'; } lstrcat(pos, _T(" ")); lstrcat(pos, lpszUrl); hInst = (HINSTANCE) WinExec(tcKey, nShowCmd); } } } return hInst; } // Activate the link. void MyHyperlink::FollowLink() { VALIDATE; HRESULT hr(S_OK); CWaitCursor wc; int nResult((int) GotoURL(m_sURL, SW_SHOW)); // If successful, mark link as visited and repaint window. EC_B(HINSTANCE_ERROR < nResult); if (SUCCEEDED(hr)) { m_bVisited = true; Invalidate(); } } // Handle the context menu's Edit, Copy. void MyHyperlink::OnEditCopy() { VALIDATE; HRESULT hr(S_OK); EC_B(OpenClipboard()); EC_B(::EmptyClipboard()); HGLOBAL hgBuffer(NULL); EC_B(hgBuffer = ::GlobalAlloc(GHND, m_sURL.GetLength() + 1)); char* pszBuffer = NULL; EC_B(pszBuffer = reinterpret_cast (::GlobalLock(hgBuffer))); EC_V(lstrcpy(pszBuffer, m_sURL)); EC_V(::GlobalUnlock(hgBuffer)); EC_B(NO_ERROR == ::GetLastError()); EC_B(NULL != ::SetClipboardData(CF_TEXT, hgBuffer)); EC_B_(::CloseClipboard()); } // User clicked on context menu item. void MyHyperlink::OnOpenUrl() { VALIDATE; FollowLink(); } // Allows the user to change the colors (fairly useless, but what the heck). void MyHyperlink::OnToggleVisited() { VALIDATE; m_bVisited = ! m_bVisited; Invalidate(); } // Create a context menu. void MyHyperlink::OnRButtonUp(UINT nFlags, CPoint point) { VALIDATE; HRESULT hr(S_OK); // Create the context menu. MyMenu menuContext; VERIFY(menuContext.LoadMenu(IDR_MYHYPERLINK)); VERIFY(menuContext.LoadToolbar(IDR_MYHYPERLINK)); // Set the Jump text for this hyperlink. VERIFY(menuContext.ModifyMyMenu(m_sURL, ID_OPEN_URL, IDR_MYHYPERLINK)); // Set the Toggle colors text. CString sVisited(Generic::LoadString(m_bVisited ? IDS_MARK_AS_UNVISITED : IDS_MARK_AS_VISITED)); VERIFY(menuContext.ModifyMyMenu(sVisited, ID_TOGGLE_VISITED, IDR_MYHYPERLINK)); // Pop the menu. ClientToScreen(&point); CMenu* pDummy = NULL; EC_B(pDummy = menuContext.GetSubMenu(0)); ASSERT_VALID(pDummy); EC_B(pDummy->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, point.x, point.y, this)); // Repaint the hyperlink to make its color go back to "not hovering". m_bOverControl = false; Invalidate(); CStatic::OnRButtonUp(nFlags, point); }