urlricheditctrl.cpp

来自「管理项目进度工具的原代码」· C++ 代码 · 共 1,009 行 · 第 1/2 页

CPP
1,009
字号
// urlricheditctrl.cpp : implementation file
//

#include "stdafx.h"
#include "urlricheditctrl.h"
#include "autoflag.h"
#include "richedithelper.h"
#include "winclasses.h"
#include "wclassdefines.h"
#include "filemisc.h"

#include "afxole.h"

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

static LPSTR URLDELIMS[] = 
{ 
	" ", 
	"\n",
	"\t",
	", ",
	". ",
//	";",
//	"[",
//	"]",
	"{",
	"}"
};

const LPCTSTR FILEPREFIX = "file://";

enum 
{ 
	TIMER_REPARSE = 1, 
};

const UINT PAUSE = 1000; // 1 second
CString CUrlRichEditCtrl::s_sGotoErrMsg;

/////////////////////////////////////////////////////////////////////////////
// CUrlRichEditCtrl

CUrlRichEditCtrl::CUrlRichEditCtrl() : m_nContextUrl(-1)
{
	EnableToolTips();

	AddProtocol("www.", FALSE);
	AddProtocol("http://", FALSE);
	AddProtocol("https://", FALSE);
	AddProtocol("ftp://", FALSE);
	AddProtocol("outlook:", FALSE);
	AddProtocol("mailto:", FALSE);
	AddProtocol("Notes://", FALSE);
	AddProtocol("evernote://", FALSE);
	AddProtocol(FILEPREFIX, FALSE);
}

CUrlRichEditCtrl::~CUrlRichEditCtrl()
{
}

BEGIN_MESSAGE_MAP(CUrlRichEditCtrl, CRichEditBaseCtrl)
//{{AFX_MSG_MAP(CUrlRichEditCtrl)
	ON_CONTROL_REFLECT_EX(EN_CHANGE, OnChangeText)
	ON_WM_CHAR()
	ON_WM_RBUTTONUP()
	ON_WM_KEYUP()
	ON_WM_RBUTTONDOWN()
	ON_WM_SHOWWINDOW()
	ON_WM_CREATE()
	ON_WM_CONTEXTMENU()
	ON_WM_SYSKEYDOWN()
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_SETTEXT, OnSetText)
	ON_MESSAGE(WM_SETFONT, OnSetFont)
	ON_MESSAGE(WM_DROPFILES, OnDropFiles)
	ON_NOTIFY_REFLECT_EX(EN_LINK, OnNotifyLink)
	ON_NOTIFY_RANGE(TTN_NEEDTEXT, 0, 0xffff, OnNeedTooltip)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CUrlRichEditCtrl message handlers

BOOL CUrlRichEditCtrl::AddProtocol(LPCTSTR szProtocol, BOOL bWantNotify)
{
	if (MatchProtocol(szProtocol) == -1)
	{
		PROTOCOL prot(szProtocol, bWantNotify);
		m_aProtocols.Add(prot);
	}
	
	return TRUE;
}

int CUrlRichEditCtrl::MatchProtocol(LPCTSTR szText) const
{
   int nProt = m_aProtocols.GetSize();

   while (nProt--)
   {
      const PROTOCOL& prot = m_aProtocols[nProt];

      if (_strnicmp(szText, prot.sProtocol, prot.sProtocol.GetLength()) == 0)
         return nProt;
   }

   return -1;
}

BOOL CUrlRichEditCtrl::OnChangeText() 
{
	// kill any existing timer
	KillTimer(TIMER_REPARSE);

	// and start a new one
	SetTimer(TIMER_REPARSE, PAUSE, NULL);
	
	return FALSE;
}

LRESULT CUrlRichEditCtrl::OnDropFiles(WPARAM wp, LPARAM /*lp*/) 
{
	CHARRANGE crSelOrg;
	GetSel(crSelOrg); // save this off
	BOOL bEnable = !(GetStyle() & ES_READONLY) && IsWindowEnabled();
	
	if (!bEnable)
		return 0;
	
	long nChar = -1;
	GetSel(nChar, nChar);
	
	CString sText, sFile;
	TCHAR szFileName[_MAX_PATH];
	UINT nFileCount = ::DragQueryFile((HDROP)wp, 0xFFFFFFFF, NULL, 0);
	
	ASSERT(nFileCount != 0);
	
	for (UINT i=0; i < nFileCount; i++)
	{
		::DragQueryFile((HDROP)wp, i, szFileName, _MAX_PATH);
		::GetLongPathName(szFileName, szFileName, _MAX_PATH);
		
		sFile.Format("%s%s", FILEPREFIX, szFileName);
		sFile.Replace(" ", "%20");
		sFile.Replace('\\', '/');
		
		sText += sFile;
	}
	::DragFinish((HDROP)wp);

	// set selection to that which we saved during the drag
	SetSel(m_crDropSel);
	
	if (!sText.IsEmpty())
		PathReplaceSel(sText, FALSE);
	
	return FALSE;
}

int CUrlRichEditCtrl::CharFromPoint(const CPoint& point)
{
/*	int nFirstLine = GetFirstVisibleLine();
	int nLineCount = GetLineCount();
	
	for (int nLine = nFirstLine; nLine < nLineCount; nLine++)
	{
		int nFirstChar = LineIndex(nLine);
		CPoint ptChar = GetCharPos(nFirstChar);
		int nLineHeight = GetLineHeight();
		
		if (point.y >= ptChar.y && point.y < ptChar.y + nLineHeight)
		{
			int nLineLength = LineLength(nFirstChar);
			
			for (int nChar = nFirstChar; nChar < (nFirstChar + nLineLength); nChar++)
			{
				ptChar = GetCharPos(nChar);
				
				if (point.x < ptChar.x)
					return nChar;
			}
		}
	}
	
	return GetTextLength();
*/
	POINTL ptl = { point.x, point.y };

	return SendMessage(EM_CHARFROMPOS, 0, (LPARAM)&ptl);
}

void CUrlRichEditCtrl::PreSubclassWindow() 
{
	SetEventMask(GetEventMask() | ENM_CHANGE | ENM_DROPFILES | ENM_DRAGDROPDONE );
	DragAcceptFiles();
	
	// enable multilevel undo
	SendMessage(EM_SETTEXTMODE, TM_MULTILEVELUNDO);

	m_ncBorder.Initialize(GetSafeHwnd());
//	CRichEditThemed::Attach(*this);

	CRichEditBaseCtrl::PreSubclassWindow();
}

LRESULT CUrlRichEditCtrl::OnSetText(WPARAM /*wp*/, LPARAM lp)
{
	// eat duplicate messages
	CString sText;
	GetWindowText(sText);
	
	BOOL bChange = (sText != (LPCTSTR)lp);
	LRESULT lr = 0;
	
	if (bChange)
	{
		CRichEditHelper::ClearUndo(GetSafeHwnd());
		
		lr = Default();
		ParseAndFormatText(TRUE);
	}
	
	return lr;
}

LRESULT CUrlRichEditCtrl::OnSetFont(WPARAM /*wp*/, LPARAM /*lp*/)
{
	LRESULT lr = Default();
	
	ParseAndFormatText(TRUE);
	
	return lr;
}

BOOL CUrlRichEditCtrl::IsDelim(LPCTSTR szText)
{
	if (!szText || !*szText)
		return TRUE; // end of string
	
	int nDelims = sizeof(URLDELIMS) / sizeof(LPCTSTR);
	char ch = *szText;
				
	for (int nDelim = 0; nDelim < nDelims; nDelim++)
	{
		LPCTSTR szDelim = URLDELIMS[nDelim];
		
		if (ch == szDelim[0])
		{
			// test char after ch if 2 char delim
			if (szDelim[1] == 0 || *(szText + 1) == szDelim[1])
				return TRUE;
		}
	}
	
	return FALSE;
}

void CUrlRichEditCtrl::ParseAndFormatText(BOOL bForceReformat)
{
	AF_NOREENTRANT // prevent reentrancy
		
	// parse the control content
	CString sText;
	GetWindowText(sText);
	
	// richedit2 uses '\r\n' whereas richedit uses just '\n'
	if (!CWinClasses::IsClass(*this, WC_RICHEDIT))
		sText.Replace("\r\n", "\n");
	
	// parse the text into an array of URLPOS
	CUrlArray aUrls;
	LPCTSTR szText = sText;
	BOOL bPrevDelim = TRUE;
	int nPos = 0;
	
	while (*szText) 
	{
		BYTE nChar = *szText;
		
		// if nChar < 0 then its a multibyte char and can't be part
		// of a url, so we bump the text buffer by 2 but the pos by 1
		if (IsDBCSLeadByte(nChar))
		{
			szText++; 
			szText++;
			nPos++;
			continue;
		}

		// if the previous item was not a delimiter then there's no
		// point checking for a protocol match so we just update the
		// value of bPrevDelim for the current char
		if (!bPrevDelim)
		{
			bPrevDelim = IsDelim(szText);
			szText++;
			nPos++;
			continue;
		}
		// if the current char is a delim then this can't be the start
		// of a url either
		else if (IsDelim(szText))
		{
			bPrevDelim = TRUE;
			szText++;
			nPos++;
			continue;
		}

		// now check for a protocol
		int nProt = MatchProtocol(szText);
		
		// if no match then increment pos and go to next char
		if (nProt == -1)
		{
			bPrevDelim = FALSE;
			szText++;
			nPos++;
			continue;
		}
		
		// find the end of the url (URLDELIMS)
		int nLen = 0;
		LPCTSTR szStart = szText;
		
		while (!IsDelim(szText))
		{
			szText++;
			nLen++;
		}
		bPrevDelim = TRUE;
		
		// save the result
		URLITEM urli;
		urli.cr.cpMin = nPos;
		urli.cr.cpMax = urli.cr.cpMin + nLen;
		nPos += nLen;
		
		urli.sUrl = CString(szStart, nLen);
		urli.bWantNotify = m_aProtocols[nProt].bWantNotify;
		
		InsertInOrder(urli, aUrls);
	}
	
	// compare aUrls with m_aUrls to see if anything has changed
	BOOL bReformat = !sText.IsEmpty() && (bForceReformat || !UrlsMatch(aUrls));

	// then overwrite (always)	
	m_aUrls.Copy(aUrls);
	
	if (bReformat)
	{
		BOOL bVisible = IsWindowVisible();
		CRePauseUndo rep(*this);
		
		if (bVisible)
			SetRedraw(FALSE);
		
		// save current selection
		CHARRANGE crSel;
		GetSel(crSel);
		
		// and first line
		int nFirstLine = GetFirstVisibleLine();
		
		// retrieve default character attribs
		CHARFORMAT cf;
		cf.cbSize = sizeof(cf);
		cf.dwMask = CFM_LINK;
		
		// format urls
		int nUrls = aUrls.GetSize();
		CHARRANGE cr = { 0, 0 };
		
		for (int nUrl = 0; nUrl < nUrls; nUrl++)
		{
			// clear formatting from the end of the previous
			// url to the start of this url
			cr.cpMax = aUrls[nUrl].cr.cpMin;
			cf.dwEffects = 0;
			
			SetSel(cr);
			SetSelectionCharFormat(cf);
			
			// update for next url
			cr.cpMin = aUrls[nUrl].cr.cpMax;
			
			// then format url
			cf.dwEffects = CFM_LINK;
			
			SetSel(aUrls[nUrl].cr);
			SetSelectionCharFormat(cf);
		}	
		
		// clear formatting for whatever's left
		cr.cpMax = -1;
		cf.dwEffects = 0;
		
		SetSel(cr);
		SetSelectionCharFormat(cf);
		
		// restore selection
		SetSel(crSel);
		
		// and first line
		SetFirstVisibleLine(nFirstLine);

		if (bVisible)
		{
			SetRedraw(TRUE);
			Invalidate(FALSE);
		}
	}
}

BOOL CUrlRichEditCtrl::UrlsMatch(const CUrlArray& aUrls)
{
	int nUrls = aUrls.GetSize();

	if (nUrls !=  m_aUrls.GetSize())
		return FALSE;

	// compare aUrls with m_aUrls to see if anything has changed
	for (int nUrl = 0; nUrl < nUrls; nUrl++)
	{
		if (aUrls[nUrl].cr.cpMin != m_aUrls[nUrl].cr.cpMin ||
			aUrls[nUrl].cr.cpMax != m_aUrls[nUrl].cr.cpMax)
		{
			return FALSE;
		}
	}

	// no change
	return TRUE;
}

void CUrlRichEditCtrl::InsertInOrder(URLITEM& urli, CUrlArray& aUrls)
{
	// work backwards looking for first item that comes before us
	int nUrl = aUrls.GetSize();

	while (nUrl--)
	{
		if (aUrls[nUrl].cr.cpMin < urli.cr.cpMin)
		{
			aUrls.InsertAt(nUrl + 1, urli);
			return;
		}
	}

	// else insert at start
	aUrls.InsertAt(0, urli);
}

void CUrlRichEditCtrl::SetFirstVisibleLine(int nLine)
{
	int nFirst = GetFirstVisibleLine();
	
	if (nLine < nFirst)
	{
		int nPrevFirst;
		
		do
		{
			LineScroll(-1);
			
			nPrevFirst = nFirst;
			nFirst = GetFirstVisibleLine();
		}
		while  (nLine < nFirst && (nFirst != nPrevFirst));
	}
	else if (nLine > nFirst)
	{
		int nPrevFirst;
		
		do
		{
			LineScroll(1);
			
			nPrevFirst = nFirst;
			nFirst = GetFirstVisibleLine();
		}
		while  (nLine > nFirst && (nFirst != nPrevFirst));
	}
}

int CUrlRichEditCtrl::GetLineHeight() const
{
	CHARFORMAT cf;
	GetDefaultCharFormat(cf);
	
	// convert height in Twips to pixels
	int nTwipsPerInch = 1440;
	HDC hdc = ::GetDC(NULL);
    int nPixelsPerInch = GetDeviceCaps(hdc, LOGPIXELSY);
	::ReleaseDC(NULL, hdc);
	
	return (cf.yHeight * nPixelsPerInch) / nTwipsPerInch + 2;
}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?