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 + -
显示快捷键?