Click here to Skip to main content
15,896,269 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.2K   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.cpp: implementation of the CHyperTextCtrl class.
//
// Copyright (c) 2001 Magomed Abdurakhmanov
// maq@hotbox.ru, http://mickels.iwt.ru/en
//
//
//
// No warranties are given. Use at your own risk.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "HyperTextCtrl.h"

//////////////////////////////////////////////////////////////////////
// CHyperLink

namespace HyperTextControl{

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

CHyperLink::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::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::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;
}

CHyperLink::~CHyperLink()
{
	;
}

void CHyperLink::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;
	}
}

//////////////////////////////////////////////////////////////////////
// CPreparedHyperText

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

CPreparedHyperText::CPreparedHyperText()
{
}

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

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

CPreparedHyperText::~CPreparedHyperText()
{
	;
}

void CPreparedHyperText::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);
				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);
				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);
				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);
				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);
				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);
				m_Links.push_back(CHyperLink(WordPos, WordPos + len - 1, s, s, (LPCTSTR)NULL));
				state = space;
			}
			break;
		}
	}

	m_Links.sort();
}

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

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

void CPreparedHyperText::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 CPreparedHyperText::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 CPreparedHyperText::AppendHyperLink(const CString& sText, const CString& sTitle, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	ATLASSERT(sText.GetLength()>0);
	ATLASSERT(hWnd != 0);

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

//////////////////////////////////////////////////////////////////////
// CHyperTextCtrl

CHyperTextCtrl::CHyperTextCtrl()
{
}

CHyperTextCtrl::~CHyperTextCtrl()
{

}

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

LRESULT CHyperTextCtrl::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_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 CHyperTextCtrl::OnPaint(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	CPaintDC dc(m_hWnd); // device context for painting

	CFontHandle hOldFont = dc.SelectFont(m_Font);
	
	COLORREF clText = GetSysColor(COLOR_WINDOWTEXT);
	COLORREF clWindow = GetSysColor(COLOR_WINDOW);
	dc.SetBkColor(clWindow);

	int ypos = 0;
	LPCTSTR s = m_Text.m_sText;

	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;
	dc.FillRect(rc, (HBRUSH) (COLOR_WINDOW+1));

	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(clText);
			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, (HBRUSH) (COLOR_WINDOW+1));

		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, (HBRUSH) (COLOR_WINDOW+1));

	dc.SelectFont(hOldFont);
	return 0;
}

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

	UpdateSize(true);
	return 0;
}

LRESULT CHyperTextCtrl::OnSetText(UINT Msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	m_Text.PrepareText((LPTSTR)lParam);
	UpdateSize(true);
	return TRUE;
}

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

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

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

LRESULT CHyperTextCtrl::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 CHyperTextCtrl::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 CHyperTextCtrl::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();

			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;
			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();
						break;
					}
			}
		}
	}
	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;
			}
		}
	}

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

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

void CHyperTextCtrl::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);
	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.m_Links.begin();
		LPCTSTR s = m_Text.m_sText;
		int len = m_Text.m_sText.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))
			{
				::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;
						}
			}

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

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

			while(it != m_Text.m_Links.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))
	{
		si.nMin = 0;
		si.nMax = iMaxWidthChars + iMaxWidthChars/2;
		int i = m_Text.m_sText.GetLength();
		si.nPos = 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))
	{
		si.nMin = 0;
		si.nMax = m_Lines.size();
		si.nPos = double(si.nMax) * m_iVertPos / 100;
		si.nPage = (rc.Height() * si.nMax)/m_iLinesHeight;
		SetScrollInfo(SB_VERT, &si, FALSE);
	}

	m_bDontUpdateSizeInfo = false;
	UpdateVisLines();
}

void CHyperTextCtrl::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 CHyperTextCtrl::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 >= m_Lines.size())
		return;

	it+=iVertPos;

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

	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 CHyperTextCtrl::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);
	COLORREF clWindow = GetSysColor(COLOR_WINDOW);
	dc.SetBkColor(clWindow);
	dc.SetTextColor(m_HoverColor);
	LPCTSTR s = m_Text.m_sText;

	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 CHyperTextCtrl::RestoreLink()
{
	if(m_pActivePart == NULL)
		return;

	CClientDC dc(*this);
	CFontHandle hOldFont = dc.SelectFont(m_LinksFont);
	COLORREF clWindow = GetSysColor(COLOR_WINDOW);
	dc.SetBkColor(clWindow);
	dc.SetTextColor(m_LinkColor);
	LPCTSTR s = m_Text.m_sText;

	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);
}

LRESULT CHyperTextCtrl::OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	return TRUE;
}

//
}; //namespace HyperTextControl

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