Click here to Skip to main content
15,891,184 members
Articles / Desktop Programming / WTL

Hyperlink Text View Control

Rate me:
Please Sign up or sign in to vote.
4.50/5 (4 votes)
20 Jun 20021 min read 124.1K   2.2K   37  
Control that shows text and automatically finds and highlights hyperlinks (like http://, www. etc.) so that user can click on them.
/********************************************************************


		HyperTextCtrl.h - Controls that shows hyperlinks 
		in text

		Copyright (C) 2001-2002 Magomed G. Abdurakhmanov			
		
		History:

		11.06.2002
			+ CHyperTextCtrlFSB - uses flat scroll bars
			+ SetTextColor, SetBkColor
			- bugs fixed

		08.06.2002
			+ Now CHyperTextCtrl inherits from CHyperTextCtrlT<>

		07.06.2002	
			+ .cpp file removed. Now all code is in HyperTextCtrl.h
			- bugs fixed 
			* Tested on WinXP, VC++7, WTL7

		01.06.2001	
			+ Control was created. 
			* Tested on Win2K, VC++6 SP3, WTL3.1


********************************************************************/

#ifndef MAQ_HYPERTEXTCONTROL_H
#define MAQ_HYPERTEXTCONTROL_H

#if _MSC_VER > 1000
#pragma once
#endif

#include <list>
#include <vector>

namespace HyperTextControl 
{

#define HTC_WORDWRAP			1	// word wrap text
#define HTC_AUTO_SCROLL_BARS	2	// auto hide scroll bars
#define HTC_UNDERLINE_LINKS		4	// underline links
#define HTC_UNDERLINE_HOVER		8	// underline hover links
#define HTC_ENABLE_TOOLTIPS		16	// enable hyperlink tool tips

// --------------------------------------------------------------
// CHyperLink
 
class CHyperLink
{
	int m_iBegin;
	int m_iEnd;
	CString m_sTitle;

	enum LinkType
	{
		lt_Shell = 0, /* http:// mailto:*/
		lt_Message = 1 /* WM_COMMAND */
	} m_Type;

	// used for lt_Shell
	CString m_sCommand;
	CString m_sDirectory;

	// used for lt_Message
	HWND m_hWnd; 
	UINT m_uMsg;
	WPARAM m_wParam;
	LPARAM m_lParam;

public:

	CHyperLink(int iBegin, int iEnd, const CString& sTitle, const CString& sCommand, const CString& sDirectory)
	{
		m_Type = lt_Shell;
		m_iBegin = iBegin;
		m_iEnd = iEnd;
		m_sTitle = sTitle;
		m_sCommand = sCommand;
		m_sDirectory = sDirectory;
	}

	CHyperLink(int iBegin, int iEnd, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		m_Type = lt_Message;
		m_iBegin = iBegin;
		m_iEnd = iEnd;
		m_sTitle = sTitle;
		m_hWnd = hWnd;
		m_uMsg = uMsg;
		m_wParam = wParam;
		m_lParam = lParam;
	}

	CHyperLink(const CHyperLink& Src)
	{
		m_Type = Src.m_Type;
		m_iBegin = Src.m_iBegin;
		m_iEnd = Src.m_iEnd;
		m_sTitle = Src.m_sTitle;
		m_sCommand = Src.m_sCommand;
		m_sDirectory = Src.m_sDirectory;
		m_hWnd = Src.m_hWnd;
		m_uMsg = Src.m_uMsg;
		m_wParam = Src.m_wParam;
		m_lParam = Src.m_lParam;
	}

	void Execute()
	{
		switch(m_Type)
		{
		case lt_Shell:
			ShellExecute(NULL, NULL, m_sCommand, NULL, m_sDirectory, SW_SHOWDEFAULT);
			break;

		case lt_Message:
			PostMessage(m_hWnd, m_uMsg, m_wParam, m_lParam);
			break;
		}
	}

	inline bool operator < (const CHyperLink& Arg)
	{
		return m_iEnd < Arg.m_iEnd;
	}

	inline int Begin()
	{
		return m_iBegin;
	}

	inline int End()
	{
		return m_iEnd;
	}

	inline int Len()
	{
		return m_iEnd - m_iBegin + 1;
	}

	inline CString Title()
	{
		return m_sTitle;
	}

	friend class CPreparedHyperText;
};

// --------------------------------------------------------------
// CPreparedHyperText

class CPreparedHyperText
{
protected:
	CString m_sText;
	std::list<CHyperLink> m_Links;

	inline void RemoveLastSign(CString& sLink)
	{
		int len = sLink.GetLength();
		if(len > 0)
		{
			TCHAR c = sLink[len-1];
			switch(c)
			{
				case _T('.'):
				case _T(','):
				case _T(';'):
				case _T('\"'):
				case _T('\''):
				case _T('('):
				case _T(')'):
				case _T('['):
				case _T(']'):
				case _T('{'):
				case _T('}'):
				sLink.Delete(len -1, 1);
				break;
			}
		}
	}

	void PrepareText(const CString& sText)
	{
		m_sText = sText;
		m_Links.clear();
		
		enum {
			unknown,
			space,
			http0,		/* http://		*/
				http1, http2, http3, http4, http5, http6,
			ftp0,		/* ftp://		*/
				ftp1, ftp2, ftp3, ftp4, ftp5,
			ftp,		/* ftp.			*/
			www0,		/* www.			*/
				www1, www2, www3,
			mailto0, 	/* mailto:		*/
				mailto1, mailto2, mailto3, mailto4, mailto5, mailto6,
			mail		/* xxx@yyy		*/
		} state = space;
		
		int WordPos = 0;
		TCHAR sz[2];
		TCHAR& c = sz[0];
		sz[1] = 0;
		int last = m_sText.GetLength() -1;
		for(int i = 0; i <= last; i++)
		{
			c = m_sText[i];
			_tcslwr(sz);

			switch(state)
			{
			case unknown:
				if(tspace(c))
					state = space;
				else
				if(c == _T('@') && WordPos != i)
					state = mail;		
				break;

			case space:
				WordPos = i;
				switch(c)
				{
				case _T('h'): state = http0; break;
				case _T('f'): state = ftp0; break;
				case _T('w'): state = www0; break;
				case _T('m'): state = mailto0; break;
				default:
					if(!tspace(c))
						state = unknown;
				}
				break;

			/*----------------- http -----------------*/
			case http0:
				if(c == _T('t'))
					state = http1;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case http1:
				if(c == _T('t'))
					state = http2;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case http2:
				if(c == _T('p'))
					state = http3;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case http3:
				if(c == _T(':'))
					state = http4;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case http4:
				if(c == _T('/'))
					state = http5;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case http5:
				if(c == _T('/'))
					state = http6;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case http6:
				if(tspace(c) || i == last)
				{
					int len = i == last ? i - WordPos + 1 : i - WordPos;
					CString s = m_sText.Mid(WordPos, len);
					RemoveLastSign(s);
					m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL));
					state = space;
				}
				break;

			/*----------------- ftp -----------------*/
			case ftp0:
				if(c == _T('t'))
					state = ftp1;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case ftp1:
				if(c == _T('p'))
					state = ftp2;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case ftp2:
				if(c == _T(':'))
					state = ftp3;
				else
				if(c == _T('.'))
					state = ftp;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case ftp3:
				if(c == _T('/'))
					state = ftp4;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case ftp4:
				if(c == _T('/'))
					state = ftp5;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case ftp:
				if(tspace(c) || i == last)
				{
					int len = i == last ? i - WordPos + 1 : i - WordPos;
					CString s = CString(_T("ftp://")) + m_sText.Mid(WordPos, len);
					RemoveLastSign(s);
					m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL));
					state = space;
				}
				break;

			case ftp5:
				if(tspace(c) || i == last)
				{
					int len = i == last ? i - WordPos + 1 : i - WordPos;
					CString s = m_sText.Mid(WordPos, len);
					RemoveLastSign(s);
					m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL));
					state = space;
				}
				break;

			/*----------------- www -----------------*/
			case www0:
				if(c == _T('w'))
					state = www1;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case www1:
				if(c == _T('w'))
					state = www2;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case www2:
				if(c == _T('.'))
					state = www3;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case www3:
				if(tspace(c) || i == last)
				{
					int len = i == last ? i - WordPos + 1 : i - WordPos;
					CString s = CString(_T("http://")) + m_sText.Mid(WordPos, len);
					RemoveLastSign(s);
					m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL));
					state = space;
				}
				break;

			/*----------------- mailto -----------------*/
			case mailto0:
				if(c == _T('a'))
					state = mailto1;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case mailto1:
				if(c == _T('i'))
					state = mailto2;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case mailto2:
				if(c == _T('l'))
					state = mailto3;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case mailto3:
				if(c == _T('t'))
					state = mailto4;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case mailto4:
				if(c == _T('o'))
					state = mailto5;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case mailto5:
				if(c == _T(':'))
					state = mailto6;
				else
				if(tspace(c))
					state = space;
				else
					state = unknown;
				break;

			case mailto6:
				if(tspace(c) || i == last)
				{
					int len = i == last ? i - WordPos + 1 : i - WordPos;
					CString s = m_sText.Mid(WordPos, len);
					RemoveLastSign(s);
					m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL));
					state = space;
				}
				break;

			/*----------------- mailto -----------------*/
			case mail:
				if(tspace(c) || i == last)
				{
					int len = i == last ? i - WordPos + 1 : i - WordPos;
					CString s = CString(_T("mailto:")) + m_sText.Mid(WordPos, len);
					RemoveLastSign(s);
					m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL));
					state = space;
				}
				break;
			}
		}

		m_Links.sort();
	}

	bool tspace(TCHAR c)
	{
		return _istspace(c) || c < _T(' ') || c == _T(';') || c == _T(',') || c == _T('!');
	}

public:

	CPreparedHyperText()
	{
	}

	CPreparedHyperText(const CString& sText)
	{
		PrepareText(sText);
	}

	CPreparedHyperText(const CPreparedHyperText& src)
	{
		m_sText = src.m_sText;
		m_Links.assign(src.m_Links.begin(), src.m_Links.end());
	}

	void Clear()
	{
		m_sText.Empty();
		m_Links.erase(m_Links.begin(), m_Links.end());
	}

	void SetText(const CString& sText)
	{
		Clear();
		PrepareText(sText);
	}

	void AppendText(const CString& sText)
	{
		int len = m_sText.GetLength();
		CPreparedHyperText ht(sText);
		m_sText+=sText;
		for(std::list<CHyperLink>::iterator it = ht.m_Links.begin(); it != ht.m_Links.end(); it++)
		{
			CHyperLink hl = *it;
			hl.m_iBegin += len;
			hl.m_iEnd += len;
			m_Links.push_back(hl);
		}
	}

	void AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory)
	{
		ATLASSERT(sText.GetLength()>0);
		ATLASSERT(sCommand.GetLength()>0);

		int len = m_sText.GetLength();
		m_sText+=sText;
		m_Links.push_back(CHyperLink(len, len + sText.GetLength() - 1, sTitle, sCommand, sDirectory));
	}

	void AppendHyperLink(const CString& sText, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		ATLASSERT(sText.GetLength()>0);

		int len = m_sText.GetLength();
		m_sText+=sText;
		m_Links.push_back(CHyperLink(len, len + sText.GetLength() - 1, sTitle, hWnd, uMsg, wParam, lParam));
	}

	inline CString& GetText()
	{
		return m_sText;
	}

	inline std::list<CHyperLink>& GetLinks()
	{
		return m_Links;
	}
	//friend class CHyperTextCtrl;
};

// --------------------------------------------------------------
// CHyperTextCtrlT

template <typename T>
class CHyperTextCtrlT : public T
{
protected:
	class CLinePartInfo
	{
	public:
		int m_iBegin;
		int m_iEnd;
		CHyperLink* m_pHyperLink;

		inline CLinePartInfo(int iBegin, int iEnd, CHyperLink* pHyperLink = NULL)
		{
			m_iBegin = iBegin;
			m_iEnd = iEnd;
			m_pHyperLink = pHyperLink;
		}

		inline CLinePartInfo(const CLinePartInfo& Src)
		{
			m_iBegin = Src.m_iBegin;
			m_iEnd = Src.m_iEnd;
			m_pHyperLink = Src.m_pHyperLink;
		}

		inline int Begin()
		{
			return m_iBegin;
		}

		inline int End()
		{
			return m_iEnd;
		}

		inline int Len()
		{
			return m_iEnd - m_iBegin + 1;
		}
	};

	class CLineInfo : public std::vector<CLinePartInfo>
	{
	public:
		int m_iBegin;
		int m_iEnd;

		inline CLineInfo(int iBegin, int iEnd)
		{
			m_iBegin = iBegin;
			m_iEnd = iEnd;
		}

		inline CLineInfo(const CLineInfo& Src)
		{
			m_iBegin = Src.m_iBegin;
			m_iEnd = Src.m_iEnd;
			assign(Src.begin(), Src.end());
		}

		inline int Begin()
		{
			return m_iBegin;
		}

		inline int End()
		{
			return m_iEnd;
		}

		inline int Len()
		{
			return m_iEnd - m_iBegin + 1;
		}
	};

	class CVisPart : public CLinePartInfo
	{
	public:
		CRect m_rcBounds;
		int m_iRealBegin;
		int m_iRealLen;
		CVisPart* m_pPrev;
		CVisPart* m_pNext;

		inline CVisPart(
				const CLinePartInfo& LinePartInfo, 
				const CRect& rcBounds, 
				int iRealBegin, 
				int iRealLen,
				CVisPart* pPrev,
				CVisPart* pNext
			) : CLinePartInfo(LinePartInfo)
		{
			m_rcBounds = rcBounds;
			m_iRealBegin = iRealBegin;
			m_iRealLen = iRealLen;
			m_pPrev = pPrev;
			m_pNext = pNext;
		}

		inline CVisPart(const CVisPart& Src) : CLinePartInfo(Src)
		{
			m_rcBounds = Src.m_rcBounds;
			m_iRealBegin = Src.m_iRealBegin;
			m_iRealLen = Src.m_iRealLen;
			m_pPrev = Src.m_pPrev;
			m_pNext = Src.m_pNext;
		}
	};

	class CVisLine : public std::vector<CVisPart>
	{	};


	CPreparedHyperText m_Text;
	std::vector<CLineInfo> m_Lines;	
	CFontHandle m_Font;
	COLORREF m_BkColor;
	COLORREF m_TextColor;
	COLORREF m_LinkColor;
	COLORREF m_HoverColor;
	HCURSOR m_LinkCursor;
	HCURSOR m_DefaultCursor;

	CToolTipCtrl m_tip;

	//temporary variables
	int m_iMaxWidth;				// The maximum line width
	int m_iLineHeight;				// Height of one line
	int m_iLinesHeight;				// Sum of height of all lines
	bool m_bDontUpdateSizeInfo;		// Used to prevent recursive call of the UpdateSize() method
	int m_iVertPos;					// Vertical position in percents
	int m_iHorzPos;					// Horizontal position in percents
	CFont m_DefaultFont;			// This font is set by default
	CFont m_LinksFont;				// Copied from main font to faster draw (link)
	CFont m_HoverFont;				// Copied from main font to faster draw (hover link)
	std::vector<CVisLine> m_VisLines;	// Currently visible text
	CVisPart* m_pActivePart;		// Active part of link (hovered)
	int m_iWheelDelta;				// Mouse wheel scroll delta

	/*void Activate()
	{
		if(GetParent() == NULL)
			SetActiveWindow();
		else
		{
			HWND hwndParent = GetParent();
			HWND hwndParentNext = ::GetParent(hwndParent);
			while(hwndParentNext != NULL)
			{
				hwndParent = hwndParentNext;
				hwndParentNext = ::GetParent(hwndParent);
			}

			::SetActiveWindow(hwndParent);
		}
	}*/

public:
	virtual BOOL PreTranslateMessage(MSG* pMsg)
	{
		return FALSE;
	}

	//message handlers
	LRESULT OnCreate(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{    
		LPCREATESTRUCT lpCreateStruct = (LPCREATESTRUCT)lParam;
		m_iMaxWidth = 0;
		m_iLinesHeight = 0;
		m_bDontUpdateSizeInfo = false;
		m_iHorzPos = 0;
		m_iVertPos = 0;
		m_DefaultFont.CreatePointFont(100,_T("Times New Roman"));
		m_Font = m_DefaultFont;
		m_LinkColor = RGB(0,0,255);
		m_HoverColor = RGB(255,0,0);
		m_BkColor = GetSysColor(COLOR_WINDOW);
		m_TextColor = GetSysColor(COLOR_WINDOWTEXT);
		m_LinkCursor = LoadCursor(NULL,IDC_ARROW);
		m_DefaultCursor = LoadCursor(NULL,IDC_ARROW);
		m_pActivePart = NULL;
		m_iWheelDelta = 0;

		// create a tool tip
		m_tip.Create(*this);
		ATLASSERT(m_tip.IsWindow());
		if(m_tip.IsWindow())
			m_tip.Activate(TRUE);

		UpdateFonts();
		return 0;
	}

	LRESULT OnPaint(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		CPaintDC dc(m_hWnd); // device context for painting

		CFontHandle hOldFont = dc.SelectFont(m_Font);
		
		dc.SetBkColor(m_BkColor);

		int ypos = 0;
		LPCTSTR s = m_Text.GetText();

		CRect rc;
		CRect rcClient;
		GetClientRect(rcClient);
		rc.left = dc.m_ps.rcPaint.left;
		rc.right = 2;
		rc.top = dc.m_ps.rcPaint.top;
		rc.bottom = dc.m_ps.rcPaint.bottom;

		CBrush brBk;
		brBk.CreateSolidBrush(m_BkColor);
		dc.FillRect(rc, brBk);

		for(std::vector<CVisLine>::iterator it = m_VisLines.begin(); it != m_VisLines.end(); it++)
		{
			int iLastX = dc.m_ps.rcPaint.left;
			for(CVisLine::iterator jt = it->begin(); jt != it->end(); jt++)
			{
				if(jt->m_pHyperLink == NULL)
					dc.SetTextColor(m_TextColor);
				else
				{
					if(m_pActivePart != NULL && m_pActivePart->m_pHyperLink == jt->m_pHyperLink)
					{
						dc.SetTextColor(m_HoverColor);
						dc.SelectFont(m_HoverFont);
					}
					else
					{
						dc.SetTextColor(m_LinkColor);
						dc.SelectFont(m_LinksFont);
					}
				}
				
				TextOut(dc, jt->m_rcBounds.left, jt->m_rcBounds.top, s + jt->m_iRealBegin, jt->m_iRealLen);

				if(jt->m_pHyperLink != NULL)
					dc.SelectFont(m_Font);

				iLastX = jt->m_rcBounds.right;
			}

			rc.left = iLastX;
			rc.right = dc.m_ps.rcPaint.right;
			rc.top = ypos;
			rc.bottom = ypos + m_iLineHeight;

			dc.FillRect(rc, brBk);

			ypos+=m_iLineHeight;
		}

		rc.left = dc.m_ps.rcPaint.left;
		rc.right = dc.m_ps.rcPaint.right;
		rc.top = ypos;
		rc.bottom = dc.m_ps.rcPaint.bottom;
		dc.FillRect(rc, brBk);

		dc.SelectFont(hOldFont);
		return 0;
	}

	LRESULT OnSize(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		WORD cx, cy;
		cx = LOWORD(lParam);
		cy = HIWORD(lParam);

		UpdateSize(IsWindowVisible() == TRUE);
		return 0;
	}

	LRESULT OnShowWindow(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		if(TRUE == (BOOL)wParam)
			UpdateSize(false);
		return 0;
	}

	LRESULT OnSetText(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		m_Text.SetText((LPTSTR)lParam);
		UpdateSize(IsWindowVisible() == TRUE);
		return TRUE;
	}

	LRESULT OnGetText(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		int bufsize = wParam;
		LPTSTR buf = (LPTSTR)lParam;
		if(lParam == NULL || bufsize == 0 || m_Text.GetText().IsEmpty())
			return 0;
		int cpy = m_Text.GetText().GetLength() > (bufsize-1) ? (bufsize-1) : m_Text.GetText().GetLength();
		_tcsncpy(buf, m_Text.GetText(), cpy);
		return cpy;
	}

	LRESULT OnSetFont(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		m_Font = (HFONT)wParam;
		UpdateFonts();
		UpdateSize(LOWORD(lParam) != 0);
		return 0;
	}

	LRESULT OnGetFont(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		return (LRESULT)m_Font.m_hFont;
	}

	LRESULT OnHScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		SCROLLINFO si;
		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;

		GetScrollInfo(SB_HORZ, &si);

		switch(LOWORD(wParam))
		{
		case SB_LEFT:
			si.nPos=si.nMin;
			break;

		case SB_RIGHT:
			si.nPos=si.nMax;
			break;

		case SB_LINELEFT:
			if(si.nPos > si.nMin)
				si.nPos-=1;
			break;

		case SB_LINERIGHT:
			if(si.nPos < si.nMax)
				si.nPos+=1;
			break;

		case SB_PAGELEFT:
			if(si.nPos > si.nMin)
				si.nPos-=si.nPage;
			if(si.nPos < si.nMin)
				si.nPos = si.nMin;
			break;

		case SB_PAGERIGHT:
			if(si.nPos < si.nMax)
				si.nPos+=si.nPage;
			if(si.nPos > si.nMax)
				si.nPos = si.nMax;
			break;

		case SB_THUMBTRACK:
			si.nPos=si.nTrackPos;
			break;
		}
			
		if(si.nMax != si.nMin)
			m_iHorzPos = si.nPos * 100 / (si.nMax - si.nMin);
		SetScrollInfo(SB_HORZ, &si);
		UpdateVisLines();
		InvalidateRect(NULL,FALSE);
		return TRUE;
	}

	LRESULT OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		SCROLLINFO si;
		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;
		GetScrollInfo(SB_VERT, &si);

		switch(LOWORD(wParam))
		{
		case SB_TOP:
			si.nPos=si.nMin;
			break;

		case SB_BOTTOM:
			si.nPos=si.nMax;
			break;

		case SB_LINEUP:
			if(si.nPos > si.nMin)
				si.nPos-=1;
			break;

		case SB_LINEDOWN:
			if(si.nPos < si.nMax)
				si.nPos+=1;
			break;

		case SB_PAGEUP:
			if(si.nPos > si.nMin)
				si.nPos-=si.nPage;
			if(si.nPos < si.nMin)
				si.nPos = si.nMin;
			break;

		case SB_PAGEDOWN:
			if(si.nPos < si.nMax)
				si.nPos+=si.nPage;
			if(si.nPos > si.nMax)
				si.nPos = si.nMax;
			break;

		case SB_THUMBTRACK:
			si.nPos=si.nTrackPos;
			break;
		}
			
		if(si.nMax != si.nMin)
			m_iVertPos = si.nPos * 100 / (si.nMax - si.nMin);
		SetScrollInfo(SB_VERT, &si);
		UpdateVisLines();
		InvalidateRect(NULL,FALSE);
		return TRUE;
	}

	LRESULT OnMouseMessage(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{   
		if(Msg == WM_MOUSEMOVE)
		{
			CPoint pt(LOWORD(lParam),HIWORD(lParam));
			CRect rcClient;
			GetClientRect(rcClient);
			if(PtInRect(rcClient, pt))
			{
				bool bFound = false;
				if(GetCapture() != m_hWnd)
					SetCapture();

				unsigned int i = pt.y / m_iLineHeight;
				if(i < m_VisLines.size())
				{
					std::vector<CVisLine>::iterator it = m_VisLines.begin() + i;
					for(CVisLine::iterator jt = it->begin(); jt != it->end(); jt++)
						if(pt.x >= jt->m_rcBounds.left && pt.x <= jt->m_rcBounds.right)
						{
							if(jt->m_pHyperLink != NULL)
							{
								HighlightLink(&*jt, pt);
								bFound = true;
							}
							break;
						}
				}
				if(!bFound)
					RestoreLink();
			}
			else
				ReleaseCapture();
		}
		else
		if(Msg == WM_LBUTTONDOWN)
		{
			CPoint pt(LOWORD(lParam),HIWORD(lParam));
			CRect rcClient;
			GetClientRect(rcClient);
			if(PtInRect(rcClient, pt))
			{
				bool bFound = false;
				unsigned int i = pt.y / m_iLineHeight;
				if(i < m_VisLines.size())
				{
					std::vector<CVisLine>::iterator it = m_VisLines.begin() + i;
					for(CVisLine::iterator jt = it->begin(); jt != it->end(); jt++)
						if(pt.x >= jt->m_rcBounds.left && pt.x <= jt->m_rcBounds.right)
						{
							if(jt->m_pHyperLink != NULL)
							{
								jt->m_pHyperLink->Execute();
								bFound = true;
							}
							break;
						}
				}

				/*if(!bFound)
					Activate();*/
			}
		}
		#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
		else
		if(Msg == WM_MOUSEWHEEL)
		{
			CPoint pt(LOWORD(lParam),HIWORD(lParam));
			CRect rc;
			GetWindowRect(rc);
			if(PtInRect(rc, pt))
			{
				int iScrollLines;
				SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 
									0, 
									&iScrollLines, 
									0);

				m_iWheelDelta -= (short)HIWORD(wParam);
				if(abs(m_iWheelDelta) >= WHEEL_DELTA)
				{
					if(m_iWheelDelta > 0)
					{
						for(int i = 0; i<iScrollLines; i++)
							PostMessage(WM_VSCROLL, SB_LINEDOWN, 0);
					}
					else
					{
						for(int i = 0; i<iScrollLines; i++)
							PostMessage(WM_VSCROLL, SB_LINEUP, 0);
					}

					m_iWheelDelta %= WHEEL_DELTA;
				}
			}
		}
		#endif

		MSG msg = { m_hWnd, Msg, wParam, lParam };
		if(m_tip.IsWindow())
			m_tip.RelayEvent(&msg);
		return 0;
	}

	LRESULT OnCaptureChanged(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{    
		RestoreLink();
		return 0;
	}

	LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return TRUE;
	}
	
	// Operations
	inline const CPreparedHyperText& GetHyperText()
	{
		return m_Text;
	}

	inline void SetHyperText(const CPreparedHyperText& Src, bool bInvalidate = true)
	{
		m_Text = Src;
		UpdateSize(bInvalidate);
	}

	inline void AppendText(const CString& sText, bool bInvalidate = true)
	{
		m_Text.AppendText(sText);
		UpdateSize(bInvalidate);
	}

	inline void AppendHyperLink(const CString& sText, const CString& sTitle, const CString& sCommand, const CString& sDirectory, bool bInvalidate = true)
	{
		m_Text.AppendHyperLink(sText, sTitle, sCommand, sDirectory);
		UpdateSize(bInvalidate);
	}

	inline void AppendHyperLink(const CString& sText, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool bInvalidate = true)
	{
		m_Text.AppendHyperLink(sText, sTitle, hWnd, uMsg, wParam, lParam);
		UpdateSize(bInvalidate);
	}

	inline COLORREF GetBkColor()
	{
		return m_BkColor;
	}

	inline void SetBkColor(COLORREF Color)
	{
		m_BkColor = Color;
	}	

	inline COLORREF GetTextColor()
	{
		return m_TextColor;
	}

	inline void SetTextColor(COLORREF Color)
	{
		m_TextColor = Color;
	}	

	inline COLORREF GetLinkColor()
	{
		return m_LinkColor;
	}

	inline void SetLinkColor(COLORREF LinkColor, bool bInvalidate = true)
	{
		m_LinkColor = LinkColor;
		if(bInvalidate)
			InvalidateRect(NULL,FALSE);
	}	

	inline COLORREF GetHoverColor()
	{
		return m_HoverColor;
	}

	inline void SetHoverColor(COLORREF HoverColor)
	{
		m_HoverColor = HoverColor;
	}	

	inline HCURSOR GetLinkCursor()
	{
		return m_LinkCursor;
	}

	inline void SetLinkCursor(HCURSOR LinkCursor)
	{
		m_LinkCursor = LinkCursor;
	}

	inline HCURSOR GetDefaultCursor()
	{
		return m_DefaultCursor;
	}

	inline void SetDefaultCursor(HCURSOR DefaultCursor)
	{
		m_DefaultCursor = DefaultCursor;
	}

protected:

	inline bool check_bits(DWORD Value, DWORD Mask)
	{
		return (Value & Mask) == Mask;
	}

	void UpdateSize(bool bRepaint/* = false*/)
	{
		if(m_bDontUpdateSizeInfo)
			return;
		m_bDontUpdateSizeInfo = true;
		DWORD dwStyle = GetWindowLongPtr(GWL_STYLE);
		bool bFirstPhase = false;
		if(check_bits(dwStyle, HTC_AUTO_SCROLL_BARS))
		{
			ShowScrollBar(SB_BOTH, FALSE);
			bFirstPhase = true;
		}
		
		CClientDC dc(*this);
		CFontHandle hOldFont = dc.SelectFont(m_Font);

		int iScrollHeight = GetSystemMetrics(SM_CYHSCROLL);

	second_phase:
		m_Lines.clear();
		CRect rc;
		GetClientRect(rc);
		int w = rc.Width();
		rc.DeflateRect(2,0);
		m_iMaxWidth = 0;
		m_iLinesHeight = 0;
		long iMaxWidthChars = 0;
		SIZE sz;

		if(rc.Width() > 5 && rc.Height() > 5)
		{
			std::list<CHyperLink>::iterator it = m_Text.GetLinks().begin();
			LPCTSTR s = m_Text.GetText();
			int len = m_Text.GetText().GetLength();
			int width = rc.Width();
			
			int npos, /* new position */
				pos = 0, /* current position */
				ll, /* line length */
				rll; /* line length with wordwrap (if used)*/

			while(len>0)
			{
				ll = len;
				npos = ll;
				for(int i = 0; i < len; i++)
				{
					if(s[i] == _T('\r') || s[i] == _T('\n'))
					{
						if(s[i] == _T('\r') && ((i+1) < len) && s[i+1] == _T('\n'))
							npos = i + 2;
						else
							npos = i + 1;

						ll = i;
						break;
					}
				}

				if(!::GetTextExtentExPoint(dc, s , (ll > 512) ? 512 : ll, width, &rll, NULL, &sz) || sz.cy == 0)
				{
					::GetTextExtentExPoint(dc, _T(" ") , 1, 0, NULL, NULL, &sz);
					sz.cx = 0;
					rll = ll;
				}

				if(rll>ll)
					rll = ll;

				if(!check_bits(dwStyle, HTC_WORDWRAP))
					rll = ll;
				else
					if(rll < ll)
						npos = rll;

				if(rll>0)
				{
					if((rll < len) && !_istspace(s[rll]))
						for(int i = rll - 1; i >= 0; i--)
							if(_istspace(s[i]))
							{
								rll = i;
								npos = i + 1;
								break;
							}
				}

				if(npos == 0)
					npos = 1;

				CLineInfo li(pos, pos + rll - 1);
				CLinePartInfo pl(pos, pos + rll - 1);

				while(it != m_Text.GetLinks().end() && it->End() < pos)
					it++;

				while(it != m_Text.GetLinks().end() && (it->Begin() >= pl.Begin() && it->Begin() <= pl.End()) || 
					(it->End() >= pl.Begin() && it->End() <= pl.End()) ||
					(pl.Begin()>=it->Begin() && pl.End() <= it->End()))
				{
					int b = it->Begin();
					int e = it->End();
					if(b<pl.Begin())
						b = pl.Begin();
					if(e>pl.End())
						e = pl.End();

					if(b>pl.Begin())
					{
						CLinePartInfo pln(pl.Begin(), b - 1);
						li.push_back(pln);
						pl.m_iBegin = e + 1;
					}

					CLinePartInfo pln(b, e, &*it);
					li.push_back(pln);
					pl.m_iBegin = e + 1;	

					if(e < pl.End())
						it++;
					else
						break;
				}

				if(pl.Len()>0)
					li.push_back(pl);

				m_iLineHeight = sz.cy;
				m_iLinesHeight+=m_iLineHeight;
				if(sz.cx > m_iMaxWidth)
					m_iMaxWidth = sz.cx;
				if(iMaxWidthChars < li.Len())
					iMaxWidthChars = li.Len();

				m_Lines.push_back(li);

				pos+=npos;
				s+=npos;
				len-=npos;

				if(bFirstPhase && ((m_iLinesHeight + iScrollHeight) > rc.Height()))
				{
					bFirstPhase = false;
					ShowScrollBar(SB_VERT,TRUE);
					dwStyle|=WS_VSCROLL;
					goto second_phase;
				}
			}
			if(bRepaint)
				InvalidateRect(rc);
		}

		dc.SelectFont(hOldFont);
		
		// Update scroll bars
		dwStyle = GetWindowLongPtr(GWL_STYLE);
		if(check_bits(dwStyle, HTC_AUTO_SCROLL_BARS) && !check_bits(dwStyle, HTC_WORDWRAP))
		{
			if(m_iMaxWidth > rc.Width())
			{
				ShowScrollBar(SB_HORZ,TRUE);
				dwStyle|=WS_HSCROLL;
			};
		}

		SCROLLINFO si;
		si.cbSize = sizeof(si);
		si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;

		if(check_bits(dwStyle,WS_HSCROLL) && m_iMaxWidth != 0)
		{
			si.nMin = 0;
			si.nMax = iMaxWidthChars + iMaxWidthChars/2;
			int i = m_Text.GetText().GetLength();
			si.nPos = (int)(double(si.nMax) * m_iHorzPos / 100);
			si.nPage = (rc.Width() * si.nMax)/m_iMaxWidth;
			SetScrollInfo(SB_HORZ, &si, FALSE);
		}

		if(check_bits(dwStyle,WS_VSCROLL) && m_iLinesHeight != 0)
		{
			si.nMin = 0;
			si.nMax = m_Lines.size();
			si.nPos = (int)(double(si.nMax) * m_iVertPos / 100);
			si.nPage = (rc.Height() * si.nMax)/m_iLinesHeight;
			SetScrollInfo(SB_VERT, &si, FALSE);
		}

		m_bDontUpdateSizeInfo = false;
		UpdateVisLines();
	}

	void UpdateFonts()
	{
		DWORD dwStyle = GetWindowLongPtr(GWL_STYLE);

		if(!m_LinksFont.IsNull())
			m_LinksFont.DeleteObject();

		if(!m_HoverFont.IsNull())
			m_HoverFont.DeleteObject();

		LOGFONT lf;
		m_Font.GetLogFont(&lf);
		if(check_bits(dwStyle, HTC_UNDERLINE_LINKS))
			lf.lfUnderline = TRUE;
		m_LinksFont.CreateFontIndirect(&lf);

		m_Font.GetLogFont(&lf);
		if(check_bits(dwStyle, HTC_UNDERLINE_HOVER))
			lf.lfUnderline = TRUE;
		m_HoverFont.CreateFontIndirect(&lf);
	}

	void UpdateVisLines()
	{
		RestoreLink();
		DWORD dwStyle = GetWindowLongPtr(GWL_STYLE);

		int id = 1;
		if(check_bits(dwStyle, HTC_ENABLE_TOOLTIPS))
		{
			for(std::vector<CVisLine>::iterator itv = m_VisLines.begin(); itv != m_VisLines.end(); itv++)
				for(CVisLine::iterator jt = itv->begin(); jt != itv->end(); jt++)
				{
					if(jt->m_pHyperLink != NULL)
						m_tip.DelTool(*this, id++);
				}
		}

		m_VisLines.clear();

		CClientDC dc(m_hWnd); // device context for painting

		std::vector<CLineInfo>::iterator it = m_Lines.begin();
		int iVertPos = 0;
		int iHorzPos = 0;
		if(check_bits(dwStyle,WS_VSCROLL))
			iVertPos = GetScrollPos(SB_VERT);
		if(check_bits(dwStyle,WS_HSCROLL))
			iHorzPos = GetScrollPos(SB_HORZ);

		if(iVertPos >= (int)m_Lines.size())
			return;

		it+=iVertPos;

		CFontHandle hOldFont = dc.SelectFont(m_Font);
		
		int ypos = 0;
		LPCTSTR s = m_Text.GetText();

		CRect rcClient;
		GetClientRect(rcClient);

		for(; it != m_Lines.end(); it++)
		{
			int XPos = 2;
			int LinePos = it->Begin();
			int Offset = 0;
			int Len = 0;

			CVisLine vl;
			CRect rcBounds;

			std::vector<CLinePartInfo>::iterator jt;

			for(jt = it->begin(); jt != it->end(); jt++)
			{
				if(jt->Begin() <= (LinePos + iHorzPos) && jt->End() >= (LinePos + iHorzPos))
				{
					Offset = LinePos + iHorzPos;
					Len = jt->Len() - ((LinePos + iHorzPos) - jt->Begin());
					break;
				}
			}

			while(jt != it->end())
			{
				if(Len > 0)
				{
					SIZE sz;
					::GetTextExtentExPoint(dc, s + Offset, Len, 0, NULL, NULL, &sz);

					rcBounds.left = XPos;
					XPos+=sz.cx;
					rcBounds.right = XPos;
					rcBounds.top = ypos;
					rcBounds.bottom = ypos+m_iLineHeight;

					vl.push_back(CVisPart(*jt, rcBounds, Offset, Len, NULL, NULL));
				}

				if(XPos > rcClient.Width())
					break;

				jt++;
				Offset = jt->Begin();
				Len = jt->Len();
			}

			m_VisLines.push_back(vl);
			ypos+=m_iLineHeight;
			if(ypos>rcClient.bottom)
				break;
		}

		CVisPart *pPrev = NULL, *pNext;
		
		id = 1;
		for(std::vector<CVisLine>::iterator it2 = m_VisLines.begin(); it2 != m_VisLines.end(); it2++)
			for(CVisLine::iterator jt = it2->begin(); jt != it2->end(); jt++)
			{
				pNext = &*jt;
				if(pPrev != NULL && 
					pPrev->m_pHyperLink != NULL && 
					pPrev->m_pHyperLink == pNext->m_pHyperLink &&
					pPrev != pNext)
				{
					pPrev->m_pNext = pNext;
					pNext->m_pPrev = pPrev;
				}
				pPrev = pNext;

				if(check_bits(dwStyle, HTC_ENABLE_TOOLTIPS) && jt->m_pHyperLink != NULL)
					m_tip.AddTool(*this, (LPCTSTR)jt->m_pHyperLink->Title(), jt->m_rcBounds, id++);
			}

		dc.SelectFont(hOldFont);
	}

	void HighlightLink(CVisPart* Part, const CPoint& MouseCoords)
	{
		if(m_pActivePart == Part)
			return;

		if(m_pActivePart != Part && m_pActivePart != NULL && Part != NULL && m_pActivePart->m_pHyperLink != Part->m_pHyperLink)
			RestoreLink();

		m_pActivePart = Part;
		while(m_pActivePart->m_pPrev != NULL)
			m_pActivePart = m_pActivePart->m_pPrev;

		CClientDC dc(*this);
		CFontHandle hOldFont = dc.SelectFont(m_HoverFont);
		dc.SetBkColor(m_BkColor);
		dc.SetTextColor(m_HoverColor);
		LPCTSTR s = m_Text.GetText();

		CVisPart* p = m_pActivePart;
		while(p != NULL)
		{
			TextOut(dc, p->m_rcBounds.left, p->m_rcBounds.top, 
				s + p->m_iRealBegin, p->m_iRealLen);
			p = p->m_pNext;
		}

		dc.SelectFont(hOldFont);

		SetCursor(m_LinkCursor);
	}

	void RestoreLink()
	{
		if(m_pActivePart == NULL)
			return;

		CClientDC dc(*this);
		CFontHandle hOldFont = dc.SelectFont(m_LinksFont);
		dc.SetBkColor(m_BkColor);
		dc.SetTextColor(m_LinkColor);
		LPCTSTR s = m_Text.GetText();

		CVisPart* p = m_pActivePart;
		while(p != NULL)
		{
			TextOut(dc, p->m_rcBounds.left, p->m_rcBounds.top, 
				s + p->m_iRealBegin, p->m_iRealLen);
			p = p->m_pNext;
		}

		dc.SelectFont(hOldFont);

		m_pActivePart = NULL;
		SetCursor(m_DefaultCursor);
	}
};

// --------------------------------------------------------------
// CHyperTextCtrl

class CHyperTextCtrl : public CHyperTextCtrlT< CWindowImpl<CHyperTextCtrl, CWindow> >
{
public:
	//message map
	BEGIN_MSG_MAP(CHyperTextCtrl)
	MESSAGE_HANDLER(WM_PAINT, OnPaint)
	MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
	MESSAGE_HANDLER(WM_SIZE, OnSize)
	MESSAGE_HANDLER(WM_SHOWWINDOW, OnShowWindow)
	MESSAGE_HANDLER(WM_CREATE, OnCreate)
	MESSAGE_HANDLER(WM_SETTEXT, OnSetText)
	MESSAGE_HANDLER(WM_GETTEXT, OnGetText)
	MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
	MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
	MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
	MESSAGE_HANDLER(WM_HSCROLL, OnHScroll)
	MESSAGE_HANDLER(WM_VSCROLL, OnVScroll)
	MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
	REFLECT_NOTIFICATIONS()
	END_MSG_MAP()

	//window class
	DECLARE_WND_CLASS_EX(_T("maqHyperTextCtrl"), CS_DBLCLKS | CS_HREDRAW, COLOR_WINDOW);
};

class CHyperTextCtrlFSB : public CHyperTextCtrlT< CWindowImpl<CHyperTextCtrl, CFSBWindow> >
{
public:
	//message map
	BEGIN_MSG_MAP(CHyperTextCtrl)
	MESSAGE_HANDLER(WM_PAINT, OnPaint)
	MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
	MESSAGE_HANDLER(WM_SIZE, OnSize)
	MESSAGE_HANDLER(WM_SHOWWINDOW, OnShowWindow)
	MESSAGE_HANDLER(WM_CREATE, OnCreate)
	MESSAGE_HANDLER(WM_SETTEXT, OnSetText)
	MESSAGE_HANDLER(WM_GETTEXT, OnGetText)
	MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
	MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
	MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
	MESSAGE_HANDLER(WM_HSCROLL, OnHScroll)
	MESSAGE_HANDLER(WM_VSCROLL, OnVScroll)
	MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
	REFLECT_NOTIFICATIONS()
	END_MSG_MAP()

	//window class
	DECLARE_WND_CLASS_EX(_T("maqHyperTextCtrlFSB"), CS_DBLCLKS | CS_HREDRAW, COLOR_WINDOW);

	LRESULT OnCreate(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		FlatSB_Initialize();
		FlatSB_SetScrollProp(WSB_PROP_VSTYLE, FSB_FLAT_MODE, TRUE);
		FlatSB_SetScrollProp(WSB_PROP_HSTYLE, FSB_FLAT_MODE, TRUE);
		return CHyperTextCtrlT< CWindowImpl<CHyperTextCtrl, CFSBWindow> >::OnCreate(Msg, wParam, lParam, bHandled);
	}
};

//
}; //namespace HyperTextControl

#endif

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions