Click here to Skip to main content
15,881,687 members
Articles / Desktop Programming / MFC

XTimer - Timer and Stopwatch Utility with Source Code

Rate me:
Please Sign up or sign in to vote.
4.81/5 (33 votes)
20 Aug 2007CPOL3 min read 165.5K   6.7K   82  
XTimer provides countdown timer and stopwatch features in a compact MFC dialog app.
// XHyperLink.cpp  Version 1.0
//
// XHyperLink static control. Will open the default browser with the given URL
// when the user clicks on the link.
//
// Copyright (C) 1997 - 1999 Chris Maunder
// All rights reserved. May not be sold for profit.
//
// Thanks to P�l K. T�nder for auto-size and window caption changes.
//
// "GotoURL" function by Stuart Patterson
// As seen in the August, 1997 Windows Developer's Journal.
// Copyright 1997 by Miller Freeman, Inc. All rights reserved.
// Modified by Chris Maunder to use TCHARs instead of chars.
//
// "Default hand cursor" from Paul DiLascia's Jan 1998 MSJ article.
//
// 2/29/00 -- P. Shaffer standard font mod.
//
///////////////////////////////////////////////////////////////////////////////
//
// Modified by:  Hans Dietrich
//               hdietrich2@hotmail.com
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "XHyperLink.h"
#include "atlconv.h"		// for Unicode conversion

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#pragma warning(disable : 4996)		// This function or variable may be unsafe

#define TOOLTIP_ID 1

// Uncomment following line to enable error message box for URL navigation
//#define XHYPERLINK_REPORT_ERROR


#ifndef IDC_HAND
#define IDC_HAND MAKEINTRESOURCE(32649)   // From WINUSER.H
#endif

// sends message to parent when hyperlink is clicked (see SetNotifyParent())
UINT WM_XHYPERLINK_CLICKED = ::RegisterWindowMessage(_T("WM_XHYPERLINK_CLICKED"));

///////////////////////////////////////////////////////////////////////////////
// CXHyperLink

BEGIN_MESSAGE_MAP(CXHyperLink, CStatic)
	//{{AFX_MSG_MAP(CXHyperLink)
	ON_WM_CTLCOLOR_REFLECT()
	ON_WM_SETCURSOR()
	ON_WM_MOUSEMOVE()
	ON_WM_TIMER()
	ON_CONTROL_REFLECT(STN_CLICKED, OnClicked)
	ON_WM_ERASEBKGND()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////////
// ctor
CXHyperLink::CXHyperLink()
{
	m_hLinkCursor     = NULL;			// No cursor as yet
	m_crLinkColour    = RGB(0,0,238);	// Blue
	m_crVisitedColour = RGB(85,26,139);	// Purple
	m_crHoverColour	  = RGB(255,0,0);	// Red
	m_bOverControl    = FALSE;			// Cursor not yet over control
	m_bVisited        = FALSE;			// Hasn't been visited yet.
	m_nUnderline      = ulHover;		// Underline the link?
	m_bAdjustToFit    = TRUE;			// Resize the window to fit the text?
	m_strURL          = _T("");
	m_nTimerID        = 100;
	m_bNotifyParent   = FALSE;			// TRUE = notify parent
	m_bIsURLEnabled   = TRUE;			// TRUE = navigate to url
	m_bToolTip        = TRUE;			// TRUE = display tooltip
	m_crBackground    = (UINT) -1;		// set to default (no bg color)
	m_bAlwaysOpenNew  = FALSE;			// TRUE = always open new browser window
}

///////////////////////////////////////////////////////////////////////////////
// dtor
CXHyperLink::~CXHyperLink()
{
	TRACE(_T("in CXHyperLink::~CXHyperLink\n"));

	if (m_hLinkCursor)
		DestroyCursor(m_hLinkCursor);
	m_hLinkCursor = NULL;
	m_UnderlineFont.DeleteObject();
	if (m_Brush.GetSafeHandle())
		m_Brush.DeleteObject();
}

/////////////////////////////////////////////////////////////////////////////
// CXHyperLink overrides

///////////////////////////////////////////////////////////////////////////////
// DestroyWindow
BOOL CXHyperLink::DestroyWindow() 
{
	KillTimer(m_nTimerID);
	return CStatic::DestroyWindow();
}

///////////////////////////////////////////////////////////////////////////////
// PreTranslateMessage
BOOL CXHyperLink::PreTranslateMessage(MSG* pMsg) 
{
	m_ToolTip.RelayEvent(pMsg);
	return CStatic::PreTranslateMessage(pMsg);
}

///////////////////////////////////////////////////////////////////////////////
// PreSubclassWindow
void CXHyperLink::PreSubclassWindow() 
{
	// We want to get mouse clicks via STN_CLICKED
	DWORD dwStyle = GetStyle();
	::SetWindowLong(GetSafeHwnd(), GWL_STYLE, dwStyle | SS_NOTIFY);
	
	// Set the URL as the window text
	if (m_strURL.IsEmpty())
		GetWindowText(m_strURL);

	// Check that the window text isn't empty. If it is, set it as the URL.
	CString strWndText;
	GetWindowText(strWndText);
	if (strWndText.IsEmpty()) 
	{
		ASSERT(!m_strURL.IsEmpty());	// Window and URL both NULL. DUH!
		SetWindowText(m_strURL);
	}

	CFont* pFont = GetFont();
	if (!pFont)
	{
		HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
		if (hFont == NULL)
			hFont = (HFONT) GetStockObject(ANSI_VAR_FONT);
		if (hFont)
			pFont = CFont::FromHandle(hFont);
	}
	ASSERT(pFont->GetSafeHandle());

	// Create the underline font
	LOGFONT lf;
	pFont->GetLogFont(&lf);
	m_StdFont.CreateFontIndirect(&lf);
	lf.lfUnderline = (BYTE) TRUE;
	m_UnderlineFont.CreateFontIndirect(&lf);

	PositionWindow();		// Adjust size of window to fit URL if necessary
	SetDefaultCursor();		// Try and load up a "hand" cursor
	SetUnderline();

	// Create the tooltip
	if (m_bToolTip)
	{
		CRect rect; 
		GetClientRect(rect);
		m_ToolTip.Create(this);
		m_ToolTip.AddTool(this, m_strURL, rect, TOOLTIP_ID);
	}

	CStatic::PreSubclassWindow();
}

/////////////////////////////////////////////////////////////////////////////
// CXHyperLink message handlers

///////////////////////////////////////////////////////////////////////////////
// OnClicked
void CXHyperLink::OnClicked()
{
	m_bOverControl = FALSE;
	int result = HINSTANCE_ERROR + 1;
	if (m_bIsURLEnabled)
		result = (int)(INT_PTR)GotoURL(m_strURL, SW_SHOW, m_bAlwaysOpenNew);
	m_bVisited = (result > HINSTANCE_ERROR);
	if (!m_bVisited)
	{
		MessageBeep(MB_ICONEXCLAMATION);	 // Unable to follow link
		ReportError(result);
	}
	else 
		SetVisited();						// Repaint to show visited colour

	NotifyParent();
}

///////////////////////////////////////////////////////////////////////////////
// CtlColor
#ifdef _DEBUG
HBRUSH CXHyperLink::CtlColor(CDC* pDC, UINT nCtlColor) 
#else
HBRUSH CXHyperLink::CtlColor(CDC* pDC, UINT /*nCtlColor*/) 
#endif
{
	ASSERT(nCtlColor == CTLCOLOR_STATIC);

	if (m_bOverControl)
		pDC->SetTextColor(m_crHoverColour);
	else if (m_bVisited)
		pDC->SetTextColor(m_crVisitedColour);
	else
		pDC->SetTextColor(m_crLinkColour);

	// transparent text.
	pDC->SetBkMode(TRANSPARENT);

	if (m_Brush.GetSafeHandle())
	{
		pDC->SetBkColor(m_crBackground);
		return (HBRUSH) m_Brush;
	}
	else
	{
		return (HBRUSH)GetStockObject(NULL_BRUSH);
	}
}

///////////////////////////////////////////////////////////////////////////////
// OnMouseMove
void CXHyperLink::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (!m_bOverControl)		// Cursor has just moved over control
	{
		m_bOverControl = TRUE;

		if (m_nUnderline == ulHover)
			SetFont(&m_UnderlineFont);
		Invalidate();

		SetTimer(m_nTimerID, 100, NULL);
	}
	CStatic::OnMouseMove(nFlags, point);
}

///////////////////////////////////////////////////////////////////////////////
// OnTimer
void CXHyperLink::OnTimer(UINT nIDEvent) 
{
	CPoint p(GetMessagePos());
	ScreenToClient(&p);

	CRect rect;
	GetClientRect(rect);
	if (!rect.PtInRect(p))
	{
		m_bOverControl = FALSE;
		KillTimer(m_nTimerID);

		if (m_nUnderline != ulAlways)
			SetFont(&m_StdFont);
		rect.bottom+=10;
		InvalidateRect(rect);
	}
	
	CStatic::OnTimer(nIDEvent);
}

///////////////////////////////////////////////////////////////////////////////
// OnSetCursor
BOOL CXHyperLink::OnSetCursor(CWnd* /*pWnd*/, UINT /*nHitTest*/, UINT /*message*/) 
{
	if (m_hLinkCursor)
	{
		::SetCursor(m_hLinkCursor);
		return TRUE;
	}
	return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// OnEraseBkgnd
BOOL CXHyperLink::OnEraseBkgnd(CDC* pDC) 
{
	CRect rect;
	GetClientRect(rect);
	if (m_crBackground != (UINT)-1)
		pDC->FillSolidRect(rect, m_crBackground);
	else
		pDC->FillSolidRect(rect, ::GetSysColor(COLOR_3DFACE));

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CXHyperLink operations

///////////////////////////////////////////////////////////////////////////////
// SetURL
void CXHyperLink::SetURL(CString strURL)
{
	m_strURL = strURL;

	if (::IsWindow(GetSafeHwnd())) 
	{
		PositionWindow();
		m_ToolTip.UpdateTipText(strURL, this, TOOLTIP_ID);
	}
}

///////////////////////////////////////////////////////////////////////////////
// SetColours
void CXHyperLink::SetColours(COLORREF crLinkColour, 
							 COLORREF crVisitedColour,	
							 COLORREF crHoverColour /* = -1 */) 
{ 
	m_crLinkColour	= crLinkColour; 
	m_crVisitedColour = crVisitedColour;

	if (crHoverColour == -1)
		m_crHoverColour = ::GetSysColor(COLOR_HIGHLIGHT);
	else
		m_crHoverColour = crHoverColour;

	if (::IsWindow(m_hWnd))
		Invalidate(); 
}

///////////////////////////////////////////////////////////////////////////////
// SetBackgroundColour
void CXHyperLink::SetBackgroundColour(COLORREF crBackground)
{
	m_crBackground = crBackground;
	if (m_Brush.GetSafeHandle())
		m_Brush.DeleteObject();
	m_Brush.CreateSolidBrush(m_crBackground);
}

///////////////////////////////////////////////////////////////////////////////
// SetVisited
void CXHyperLink::SetVisited(BOOL bVisited /* = TRUE */) 
{ 
	m_bVisited = bVisited; 

	if (::IsWindow(GetSafeHwnd()))
		Invalidate(); 
}

///////////////////////////////////////////////////////////////////////////////
// SetLinkCursor
void CXHyperLink::SetLinkCursor(HCURSOR hCursor)
{ 
	m_hLinkCursor = hCursor;
	if (m_hLinkCursor == NULL)
		SetDefaultCursor();
}

///////////////////////////////////////////////////////////////////////////////
// SetUnderline
void CXHyperLink::SetUnderline(int nUnderline /*=ulHover*/)
{
	if (m_nUnderline == nUnderline)
		return;

	if (::IsWindow(GetSafeHwnd()))
	{
		if (nUnderline == ulAlways)
			SetFont(&m_UnderlineFont);
		else
			SetFont(&m_StdFont);

		Invalidate(); 
	}

	m_nUnderline = nUnderline;
}

///////////////////////////////////////////////////////////////////////////////
// SetAutoSize
void CXHyperLink::SetAutoSize(BOOL bAutoSize /* = TRUE */)
{
	m_bAdjustToFit = bAutoSize;

	if (::IsWindow(GetSafeHwnd()))
		PositionWindow();
}

///////////////////////////////////////////////////////////////////////////////
// SetWindowText
void CXHyperLink::SetWindowText(LPCTSTR lpszString)
{
	ASSERT(lpszString);
	if (!lpszString)
		return;
	CStatic::SetWindowText(_T(""));
	RedrawWindow();
	CStatic::SetWindowText(lpszString);
	PositionWindow();
}

///////////////////////////////////////////////////////////////////////////////
// PositionWindow

// Move and resize the window so that the window is the same size
// as the hyperlink text. This stops the hyperlink cursor being active
// when it is not directly over the text. If the text is left justified
// then the window is merely shrunk, but if it is centred or right
// justified then the window will have to be moved as well.
//
// Suggested by P�l K. T�nder 
//
void CXHyperLink::PositionWindow()
{
	if (!::IsWindow(GetSafeHwnd()) || !m_bAdjustToFit) 
		return;

	// Get the current window position
	CRect WndRect, ClientRect;
	GetWindowRect(WndRect);
	GetClientRect(ClientRect);

	ClientToScreen(ClientRect);

	CWnd* pParent = GetParent();
	if (pParent)
	{
		pParent->ScreenToClient(WndRect);
		pParent->ScreenToClient(ClientRect);
	}

	// Get the size of the window text
	CString strWndText;
	GetWindowText(strWndText);

	CDC* pDC = GetDC();
	CFont* pOldFont = pDC->SelectObject(&m_UnderlineFont);
	CSize Extent = pDC->GetTextExtent(strWndText);
	pDC->SelectObject(pOldFont);
	ReleaseDC(pDC);

	// Adjust for window borders
	Extent.cx += WndRect.Width() - ClientRect.Width(); 
	Extent.cy += WndRect.Height() - ClientRect.Height(); 

	// Get the text justification via the window style
	DWORD dwStyle = GetStyle();

	// Recalc the window size and position based on the text justification
	if (dwStyle & SS_CENTERIMAGE)
		WndRect.DeflateRect(0, (WndRect.Height() - Extent.cy)/2);
	else
		WndRect.bottom = WndRect.top + Extent.cy;

	if (dwStyle & SS_CENTER) 
		WndRect.DeflateRect((WndRect.Width() - Extent.cx)/2, 0);
	else if (dwStyle & SS_RIGHT) 
		WndRect.left = WndRect.right - Extent.cx;
	else // SS_LEFT = 0, so we can't test for it explicitly 
		WndRect.right = WndRect.left + Extent.cx;

	// Move the window
	SetWindowPos(NULL, 
				 WndRect.left, WndRect.top, 
				 WndRect.Width(), WndRect.Height(), 
				 SWP_NOZORDER);
}

/////////////////////////////////////////////////////////////////////////////
// CXHyperLink implementation

///////////////////////////////////////////////////////////////////////////////
// SetDefaultCursor
void CXHyperLink::SetDefaultCursor()
{
	if (m_hLinkCursor == NULL)				// No cursor handle - try to load one
	{
		// First try to load the Win98 / Windows 2000 hand cursor

		TRACE(_T("loading from IDC_HAND\n"));
		m_hLinkCursor = AfxGetApp()->LoadStandardCursor(IDC_HAND);

		if (m_hLinkCursor == NULL)			// Still no cursor handle - 
											// load the WinHelp hand cursor
		{
			// The following appeared in Paul DiLascia's Jan 1998 MSJ articles.
			// It loads a "hand" cursor from the winhlp32.exe module.

			TRACE(_T("loading from winhlp32\n"));

			// Get the windows directory
			CString strWndDir;
			GetWindowsDirectory(strWndDir.GetBuffer(MAX_PATH), MAX_PATH);
			strWndDir.ReleaseBuffer();

			strWndDir += _T("\\winhlp32.exe");

			// This retrieves cursor #106 from winhlp32.exe, which is a hand pointer
			HMODULE hModule = LoadLibrary(strWndDir);
			if (hModule) 
			{
				HCURSOR hHandCursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106));
				if (hHandCursor)
					m_hLinkCursor = CopyCursor(hHandCursor);
				FreeLibrary(hModule);
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// GetRegKey
LONG CXHyperLink::GetRegKey(HKEY key, LPCTSTR subkey, LPTSTR retdata)
{
	HKEY hkey;
	LONG retval = RegOpenKeyEx(key, subkey, 0, KEY_QUERY_VALUE, &hkey);

	if (retval == ERROR_SUCCESS) 
	{
		long datasize = MAX_PATH;
		TCHAR data[MAX_PATH];
		RegQueryValue(hkey, NULL, data, &datasize);
		_tcscpy(retdata, data);
		RegCloseKey(hkey);
	}

	return retval;
}

///////////////////////////////////////////////////////////////////////////////
// ReportError
void CXHyperLink::ReportError(int nError)
{
#ifdef XHYPERLINK_REPORT_ERROR

	CString str;
	switch (nError) 
	{
		case 0:							str = "The operating system is out\nof memory or resources."; break;
		case SE_ERR_PNF:				str = "The specified path was not found."; break;
		case SE_ERR_FNF:				str = "The specified file was not found."; break;
		case ERROR_BAD_FORMAT:			str = "The .EXE file is invalid\n(non-Win32 .EXE or error in .EXE image)."; break;
		case SE_ERR_ACCESSDENIED:		str = "The operating system denied\naccess to the specified file."; break;
		case SE_ERR_ASSOCINCOMPLETE:	str = "The filename association is\nincomplete or invalid."; break;
		case SE_ERR_DDEBUSY:			str = "The DDE transaction could not\nbe completed because other DDE transactions\nwere being processed."; break;
		case SE_ERR_DDEFAIL:			str = "The DDE transaction failed."; break;
		case SE_ERR_DDETIMEOUT:			str = "The DDE transaction could not\nbe completed because the request timed out."; break;
		case SE_ERR_DLLNOTFOUND:		str = "The specified dynamic-link library was not found."; break;
		case SE_ERR_NOASSOC:			str = "There is no application associated\nwith the given filename extension."; break;
		case SE_ERR_OOM:				str = "There was not enough memory to complete the operation."; break;
		case SE_ERR_SHARE:				str = "A sharing violation occurred. ";
		default:						str.Format(_T("Unknown Error (%d) occurred."), nError); break;
	}
	str = "Unable to open hyperlink:\n\n" + str;
	AfxMessageBox(str, MB_ICONEXCLAMATION | MB_OK);

#else

	UNUSED_ALWAYS(nError);

#endif	// XHYPERLINK_REPORT_ERROR
}

///////////////////////////////////////////////////////////////////////////////
// NotifyParent
void CXHyperLink::NotifyParent()
{
	if (m_bNotifyParent)
	{
		CWnd *pParent = GetParent();
		if (pParent && ::IsWindow(pParent->m_hWnd))
		{
			// wParam will contain control id
			pParent->SendMessage(WM_XHYPERLINK_CLICKED, GetDlgCtrlID());
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// GotoURL
HINSTANCE CXHyperLink::GotoURL(LPCTSTR url, int showcmd, BOOL bAlwaysOpenNew /*= FALSE*/)
{
	// if no url then this is not an internet link
	if (!url || url[0] == _T('\0'))
		return (HINSTANCE) HINSTANCE_ERROR + 1;

	TCHAR key[MAX_PATH*2];

	// First try ShellExecute()
	TCHAR *verb = _T("open");
	if (bAlwaysOpenNew)
		verb = _T("new");
	HINSTANCE result = ShellExecute(NULL, verb, url, NULL,NULL, showcmd);

	// If it failed, get the .htm regkey and lookup the program
	if (result <= (HINSTANCE)HINSTANCE_ERROR) 
	{
		if (GetRegKey(HKEY_CLASSES_ROOT, _T(".htm"), key) == ERROR_SUCCESS) 
		{
			_tcscat(key, _T("\\shell\\open\\command"));

			if (GetRegKey(HKEY_CLASSES_ROOT,key,key) == ERROR_SUCCESS) 
			{
				TCHAR *pos;
				pos = _tcsstr(key, _T("\"%1\""));
				if (pos == NULL) 
				{					// No quotes found
					pos = _tcsstr(key, _T("%1"));	// Check for %1, without quotes 
					if (pos == NULL)				// No parameter at all...
						pos = key + _tcslen(key)-1;
					else
						*pos = _T('\0');				// Remove the parameter
				}
				else
				{
					*pos = _T('\0');					// Remove the parameter
				}

				_tcscat(pos, _T(" "));
				_tcscat(pos, url);

				USES_CONVERSION;
				result = (HINSTANCE) (UINT_PTR) WinExec(T2A(key),showcmd);
			}
		}
	}

	return result;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions