WindowsXP-SP1/shell/comctl32/v6/markup.cpp

2058 lines
61 KiB
C++

//-------------------------------------------------------------------------//
// markup.cpp - implementation of CMarkup
//
#include <ctlspriv.h>
#include <shpriv.h>
#include <markup.h>
#include <oleacc.h>
#define DllAddRef()
#define DllRelease()
typedef WCHAR TUCHAR, *PTUCHAR;
#define IS_LINK(pBlock) ((pBlock) && (pBlock)->iLink != INVALID_LINK_INDEX)
#ifndef POINTSPERRECT
#define POINTSPERRECT (sizeof(RECT)/sizeof(POINT))
#endif
#ifndef MIN
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#endif
#define TESTKEYSTATE(vk) ((GetKeyState(vk) & 0x8000)!=0)
#define LINKCOLOR_ENABLED GetSysColor(COLOR_HOTLIGHT)
#define LINKCOLOR_DISABLED GetSysColor(COLOR_GRAYTEXT)
#define SZ_ATTRIBUTE_HREF TEXT("HREF")
#define SZ_ATTRIBUTE_ID TEXT("ID")
#define LINKTAG1 TEXT("<A")
#define cchLINKTAG1 (ARRAYSIZE(LINKTAG1) - 1)
#define CH_ENDTAG TEXT('>')
#define LINKTAG2 TEXT("</A>")
#define cchLINKTAG2 (ARRAYSIZE(LINKTAG2) - 1)
#define Markup_DestroyMarkup(hMarkup)\
((IUnknown*)hMarkup)->Release();
struct RECTLISTENTRY // rect list member
{
RECT rc;
UINT uCharStart;
UINT uCharCount;
UINT uLineNumber;
RECTLISTENTRY* next;
};
struct TEXTBLOCK // text segment data
{
friend class CMarkup;
int iLink; // index of link (INVALID_LINK_INDEX if static text)
DWORD state; // state bits
TCHAR szID[MAX_LINKID_TEXT]; // link identifier.
TEXTBLOCK* next; // next block
RECTLISTENTRY* rgrle; // list of bounding rectangle(s)
TCHAR* pszText; // text
TCHAR* pszUrl; // URL.
TEXTBLOCK();
~TEXTBLOCK();
void AddRect(const RECT& rc, UINT uMyCharStart = 0, UINT uMyCharCount = 0, UINT uMyLineNumber = 0);
void FreeRects();
};
class CMarkup : IControlMarkup
{
public:
// API
friend HRESULT Markup_Create(IMarkupCallback *pMarkupCallback, HFONT hf, HFONT hfu, REFIID riid, void **ppv);
// IControlMarkup
STDMETHODIMP SetCallback(IUnknown* punk);
STDMETHODIMP GetCallback(REFIID riid, void** ppvUnk);
STDMETHODIMP SetFonts(HFONT hFont, HFONT hFontUnderline);
STDMETHODIMP GetFonts(HFONT* phFont, HFONT* phFontUnderline);
STDMETHODIMP SetText(LPCWSTR pwszText);
STDMETHODIMP GetText(BOOL bRaw, LPWSTR pwszText, DWORD *pdwCch);
STDMETHODIMP SetLinkText(int iLink, UINT uMarkupLinkText, LPCWSTR pwszText);
STDMETHODIMP GetLinkText(int iLink, UINT uMarkupLinkText, LPWSTR pwszText, DWORD *pdwCch);
STDMETHODIMP SetRenderFlags(UINT uDT);
STDMETHODIMP GetRenderFlags(UINT *puDT, HTHEME *phTheme, int *piPartId, int *piStateIdNormal, int *piStateIdLink);
STDMETHODIMP SetThemeRenderFlags(UINT uDT, HTHEME hTheme, int iPartId, int iStateIdNormal, int iStateIdLink);
STDMETHODIMP GetState(int iLink, UINT uStateMask, UINT* puState);
STDMETHODIMP SetState(int iLink, UINT uStateMask, UINT uState);
STDMETHODIMP DrawText(HDC hdcClient, LPCRECT prcClient);
STDMETHODIMP SetLinkCursor();
STDMETHODIMP CalcIdealSize(HDC hdc, UINT uMarkUpCalc, RECT* prc);
STDMETHODIMP SetFocus();
STDMETHODIMP KillFocus();
STDMETHODIMP IsTabbable();
STDMETHODIMP OnButtonDown(POINT pt);
STDMETHODIMP OnButtonUp(POINT pt);
STDMETHODIMP OnKeyDown(UINT uVitKey);
STDMETHODIMP HitTest(POINT pt, UINT* pidLink);
// commented out of IControlMarkup?
STDMETHODIMP HandleEvent(BOOL keys, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
private:
// private constructor
CMarkup(IMarkupCallback *pMarkupCallback);
CMarkup();
~CMarkup();
friend struct TEXTBLOCK;
HCURSOR GetLinkCursor();
BOOL IsMarkupState(UINT uState)
{
return _pMarkupCallback && _pMarkupCallback->GetState(uState) == S_OK;
}
BOOL IsFocused()
{
return IsMarkupState(MARKUPSTATE_FOCUSED);
}
BOOL IsMarkupAllowed()
{
return IsMarkupState(MARKUPSTATE_ALLOWMARKUP);
}
void Parse(LPCTSTR pszText);
BOOL Add(TEXTBLOCK* pAdd);
TEXTBLOCK* FindLink(int iLink) const;
void FreeBlocks();
void DoNotify(int nCode, int iLink);
int ThemedDrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat, BOOL bLink);
void Paint(HDC hdc, IN OPTIONAL LPCRECT prcClient = NULL, BOOL bDraw = TRUE);
BOOL WantTab(int* biFocus = NULL) const;
void AssignTabFocus(int nDirection);
int GetNextEnabledLink(int iStart, int nDir) const;
int StateCount(DWORD dwStateMask, DWORD dwState) const;
HRESULT _GetNextAnchorTag(LPCTSTR * ppszBlock, int * pcBlocks, LPTSTR pszURL, int cchSize, LPTSTR pszID, int cchID);
static TEXTBLOCK* CreateBlock(LPCTSTR pszStart, LPCTSTR pszEnd, int iLink);
// Data
BOOL _bButtonDown; // true when button is clicked on a link but not yet released
TEXTBLOCK* _rgBlocks; // linked list of text blocks
int _cBlocks; // block count
int _Markups; // link count
int _iFocus; // index of focus link
int _cyIdeal;
int _cxIdeal;
LPTSTR _pszCaption;
HFONT _hfStatic,
_hfLink;
HCURSOR _hcurHand;
IMarkupCallback *_pMarkupCallback;
LONG _cRef;
UINT _uDrawTextFlags;
BOOL _bRefreshText;
RECT _rRefreshRect;
HTHEME _hTheme; // these 3 for theme compatible drawing
int _iThemePartId;
int _iThemeStateIdNormal;
int _iThemeStateIdLink;
// static helper methods
static LPTSTR SkipWhite(LPTSTR);
static BOOL _AssignBit(const DWORD , DWORD& , const DWORD);
static BOOL IsStringAlphaNumeric(LPCTSTR);
static HRESULT _GetNextValueDataPair(LPTSTR * , LPTSTR , int , LPTSTR , int);
static int _IsLineBreakChar(LPCTSTR , int , TCHAR , OUT BOOL* , BOOL fIgnoreSpace);
BOOL static _FindLastBreakChar(IN LPCTSTR , IN int , IN TCHAR , OUT int* , OUT BOOL*);
BOOL _FindFirstLineBreak(IN LPCTSTR pszText, IN int cchText, OUT int* piLast, OUT int* piLineBreakSize);
};
CMarkup::CMarkup() :
_cRef(1),
_iFocus(INVALID_LINK_INDEX),
_uDrawTextFlags(DT_LEFT | DT_WORDBREAK),
_bRefreshText(TRUE),
_iThemeStateIdLink(1)
{
}
CMarkup::~CMarkup()
{
FreeBlocks();
SetText(NULL);
if (_pMarkupCallback)
{
_pMarkupCallback->Release();
_pMarkupCallback = NULL;
}
}
inline void MakePoint(LPARAM lParam, OUT LPPOINT ppt)
{
POINTS pts = MAKEPOINTS(lParam);
ppt->x = pts.x;
ppt->y = pts.y;
}
STDAPI Markup_Create(IMarkupCallback *pMarkupCallback, HFONT hf, HFONT hfUnderline, REFIID riid, void **ppv)
{
// Create CMarkup
HRESULT hr = E_FAIL;
CMarkup* pThis = new CMarkup();
if (pThis)
{
pThis->SetCallback(pMarkupCallback);
// init fonts
pThis->SetFonts(hf, hfUnderline);
// COM stuff
hr = pThis->QueryInterface(riid, ppv);
pThis->Release();
}
return hr;
}
HRESULT CMarkup::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CMarkup, IControlMarkup),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CMarkup::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CMarkup::Release()
{
if (InterlockedDecrement(&_cRef))
{
return _cRef;
}
delete this;
return 0;
}
STDMETHODIMP CMarkup::SetCallback(IUnknown* punk)
{
if (_pMarkupCallback)
{
_pMarkupCallback->Release();
_pMarkupCallback = NULL;
}
if (punk)
return punk->QueryInterface(IID_PPV_ARG(IMarkupCallback, &_pMarkupCallback));
// To break reference, pass NULL.
return S_OK;
}
STDMETHODIMP CMarkup::GetCallback(REFIID riid, void** ppvUnk)
{
if (_pMarkupCallback)
return _pMarkupCallback->QueryInterface(riid, ppvUnk);
return E_NOINTERFACE;
}
// IControlMarkup interface implementation
STDMETHODIMP CMarkup::SetFocus()
{
AssignTabFocus(0);
_pMarkupCallback->InvalidateRect(NULL);
return S_OK;
}
STDMETHODIMP CMarkup::KillFocus()
{
// Reset the focus position on request
_iFocus=INVALID_LINK_INDEX;
_pMarkupCallback->InvalidateRect(NULL);
return S_OK;
}
STDMETHODIMP CMarkup::IsTabbable()
{
HRESULT hr = S_FALSE;
int nDir = TESTKEYSTATE(VK_SHIFT) ? -1 : 1;
if (GetNextEnabledLink(_iFocus, nDir) != INVALID_LINK_INDEX)
{
hr = S_OK;
}
return hr;
}
//bugs: calculating ideal 'width' returns bogus valuez
STDMETHODIMP CMarkup::CalcIdealSize(HDC hdc, UINT uMarkUpCalc, RECT* prc)
{
// prc is changed (prc.height or prc.width) only if hr = S_OK
/* currently:
MARKUPSIZE_CALCHEIGHT: takes an initial max width (right-left) and calculates
and ideal height (bottom=ideal_height+top) and the actual width used, which
is always less than the maximum (right=width_used+left).
MARKUPSIZE_CALCWIDTH: doesn't do anything correctly. don't try it. */
HRESULT hr = E_FAIL;
BOOL bQuitNow = FALSE;
if (prc == NULL)
return E_INVALIDARG;
if (NULL != _rgBlocks && 0 != _cBlocks)
{
int cyRet = -1;
SIZE sizeDC;
RECT rc;
if (uMarkUpCalc == MARKUPSIZE_CALCWIDTH)
{
// Come up with a conservative estimate for the new width.
sizeDC.cx = MulDiv(prc->right-prc->left, 1, prc->top-prc->bottom) * 2;
sizeDC.cy = prc->bottom - prc->top;
if (sizeDC.cy < 0)
{
bQuitNow = TRUE;
}
}
if (uMarkUpCalc == MARKUPSIZE_CALCHEIGHT)
{
// Come up with a conservative estimate for the new height.
sizeDC.cy = MulDiv(prc->top-prc->bottom, 1, prc->right-prc->left) * 2;
sizeDC.cx = prc->right-prc->left;
if (sizeDC.cx < 0)
{
bQuitNow = TRUE;
}
// If no x size is specified, make a big estimate
// (i.e. the estimate is the x size of the unparsed text)
if (sizeDC.cx == 0)
{
if (!_hTheme)
{
GetTextExtentPoint(hdc, _pszCaption, lstrlen(_pszCaption), &sizeDC);
}
if (_hTheme)
{
// Get theme font size estimate for the larger part-font type
RECT rcTemp;
GetThemeTextExtent(_hTheme, hdc, _iThemePartId, _iThemeStateIdNormal, _pszCaption, -1, 0, NULL, &rcTemp);
sizeDC.cx = rcTemp.right - rcTemp.left;
GetThemeTextExtent(_hTheme, hdc, _iThemePartId, _iThemeStateIdLink, _pszCaption, -1, 0, NULL, &rcTemp);
if ((rcTemp.right - rcTemp.left) > sizeDC.cx)
{
sizeDC.cx = rcTemp.right - rcTemp.left;
}
}
}
}
hr = E_FAIL;
if (!bQuitNow)
{
int cyPrev = _cyIdeal; // push ideal
int cxPrev = _cxIdeal;
SetRect(&rc, 0, 0, sizeDC.cx, sizeDC.cy);
Paint(hdc, &rc, FALSE);
// save the result
hr = S_OK;
if (uMarkUpCalc == MARKUPSIZE_CALCHEIGHT)
{
prc->bottom = prc->top + _cyIdeal;
prc->right = prc->left + _cxIdeal;
}
if (uMarkUpCalc == MARKUPSIZE_CALCWIDTH)
{
// not implemented -- need to do
}
_cyIdeal = cyPrev; // pop ideal
_cxIdeal = cxPrev;
}
}
if (FAILED(hr))
{
SetRect(prc, 0, 0, 0, 0);
}
return hr;
}
STDMETHODIMP CMarkup::SetLinkCursor()
{
SetCursor(GetLinkCursor());
return S_OK;
}
STDMETHODIMP CMarkup::GetFonts(HFONT* phFont, HFONT* phFontUnderline)
{
ASSERTMSG(IsBadWritePtr(phFont, sizeof(*phFont)), "Invalid phFont passed to CMarkup::GetFont");
HRESULT hr = E_FAIL;
*phFont = NULL;
*phFontUnderline = NULL;
if (_hfStatic)
{
LOGFONT lf;
if (GetObject(_hfStatic, sizeof(lf), &lf))
{
*phFont = CreateFontIndirect(&lf);
if (GetObject(_hfLink, sizeof(lf), &lf))
*phFontUnderline = CreateFontIndirect(&lf);
hr = S_OK;
}
}
return hr;
}
HRESULT CMarkup::SetFonts(HFONT hFont, HFONT hFontUnderline)
{
HRESULT hr = S_FALSE;
_bRefreshText = TRUE;
_hfStatic = hFont;
_hfLink = hFontUnderline;
if (_hfLink != NULL && _hfStatic != NULL)
{
hr = S_OK;
}
return hr;
}
STDMETHODIMP CMarkup::HandleEvent(BOOL keys, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
/* this function handles:
WM_KEYDOWN
WM_BUTTONDOWN
WM_BUTTONUP
WM_MOUSEMOVE
pass it:
keys - TRUE if you want to handle WM_KEYDOWN
others - the params from the WndProc
returns: S_OK if event handled, S_FALSE if no event handled */
HRESULT hr = S_FALSE;
if (!hwnd)
{
hr = E_INVALIDARG;
}
else
{
switch (uMsg)
{
case WM_KEYDOWN:
{
if (keys==TRUE)
{
OnKeyDown((UINT)wParam);
hr = S_OK;
}
break;
}
case WM_LBUTTONDOWN:
{
POINT pt;
MakePoint(lParam, &pt);
OnButtonDown(pt);
hr = S_OK;
break;
}
case WM_LBUTTONUP:
{
POINT pt;
MakePoint(lParam, &pt);
OnButtonUp(pt);
hr = S_OK;
break;
}
case WM_MOUSEMOVE:
{
POINT pt;
UINT pidLink;
MakePoint(lParam, &pt);
if (HitTest(pt, &pidLink) == S_OK)
{
SetLinkCursor();
}
hr = S_OK;
break;
}
}
}
return hr;
}
STDMETHODIMP CMarkup::DrawText(HDC hdcClient, LPCRECT prcClient)
{
HRESULT hr = E_INVALIDARG;
if (prcClient != NULL && hdcClient != NULL)
{
Paint(hdcClient, prcClient);
hr = S_OK;
}
return hr;
}
STDMETHODIMP CMarkup::GetText(BOOL bRaw, LPWSTR pwszText, DWORD *pcchText)
{
// if passed pwszText==NULL, return the number of characters needed in pcchText
if (!pwszText)
{
// for now, always return raw text, as it will always be larger than necessary
*pcchText = lstrlen(_pszCaption)+1;
}
else
{
*pwszText = 0;
if (bRaw)
{
if (_pszCaption)
{
lstrcpyn(pwszText, _pszCaption, *pcchText);
}
}
else
{
for (TEXTBLOCK* pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
if (pBlock->pszText)
StrCatBuff(pwszText, pBlock->pszText, *pcchText);
}
}
*pcchText = lstrlen(pwszText);
}
return S_OK;
}
STDMETHODIMP CMarkup::SetText(LPCWSTR pwszText)
{
// Note: we don't reparse in the case of same strings
if (pwszText && 0 == lstrcmp(pwszText, _pszCaption))
{
return S_FALSE; // nothing to do.
}
// set the text
_bRefreshText = TRUE;
if (_pszCaption)
{
LocalFree(_pszCaption);
_pszCaption = NULL;
}
_iFocus = INVALID_LINK_INDEX;
if (pwszText && *pwszText)
{
_pszCaption = StrDup(pwszText); // StrDup gets free'd with LocalFree
if (_pszCaption)
{
Parse(pwszText);
}
else
return E_OUTOFMEMORY;
}
return S_OK;
}
STDMETHODIMP CMarkup::SetRenderFlags(UINT uDT)
{
HRESULT hr = E_INVALIDARG;
_bRefreshText = TRUE;
// Set drawtext flags, but filter out unsupported modes
_uDrawTextFlags = uDT;
_uDrawTextFlags &= ~(DT_CALCRECT | DT_INTERNAL | DT_NOCLIP | DT_NOFULLWIDTHCHARBREAK | DT_EDITCONTROL);
// Turn off themedraw
_hTheme = NULL;
hr = S_OK;
return hr;
}
STDMETHODIMP CMarkup::SetThemeRenderFlags(UINT uDT, HTHEME hTheme, int iPartId, int iStateIdNormal, int iStateIdLink)
{
HRESULT hr = SetRenderFlags(uDT);
if (hr == S_OK)
{
// Turn on themedraw
_hTheme = hTheme;
_iThemePartId = iPartId;
_iThemeStateIdNormal = iStateIdNormal;
_iThemeStateIdLink = iStateIdLink;
}
return hr;
}
HRESULT CMarkup::GetRenderFlags(UINT *puDT, HTHEME *phTheme, int *piPartId, int *piStateIdNormal, int *piStateIdLink)
{
*puDT = _uDrawTextFlags;
*phTheme = _hTheme;
*piPartId = _iThemePartId;
*piStateIdNormal = _iThemeStateIdNormal;
*piStateIdLink = _iThemeStateIdLink;
return S_OK;
}
// WM_KEYDOWN handler - exposed as COM
STDMETHODIMP CMarkup::OnKeyDown(UINT virtKey)
{
// returns: S_FALSE unless key handled, then S_OK
// (so if you pass a VK_TAB and it isn't handled, pass on focus)
HRESULT hr = S_FALSE;
switch(virtKey)
{
case VK_TAB:
if (WantTab(&_iFocus))
{
hr = S_OK;
}
_pMarkupCallback->InvalidateRect(NULL);
break;
case VK_RETURN:
case VK_SPACE:
{
TEXTBLOCK * pBlock = FindLink(_iFocus);
if (pBlock)
{
DoNotify (MARKUPMESSAGE_KEYEXECUTE, _iFocus);
hr = S_OK;
}
}
break;
}
return hr;
}
HRESULT CMarkup::OnButtonDown(const POINT pt)
{
// returns: S_FALSE unless button down on link, then S_OK
// note: OnButtonDown no longer turns on capturing all mouse events. Not sure if this will have any negative effect.
HRESULT hr = S_FALSE;
UINT iLink;
if (HitTest(pt, &iLink) == S_OK)
{
hr = S_OK;
SetLinkCursor();
_iFocus = iLink;
_bButtonDown = TRUE;
if (! (IsFocused()))
{
/* this is our way of telling the host we want focus. */
DoNotify (MARKUPMESSAGE_WANTFOCUS, _iFocus);
}
_pMarkupCallback->InvalidateRect(NULL);
}
return hr;
}
HRESULT CMarkup::OnButtonUp(const POINT pt)
{
// returns: S_FALSE unless notification sent, then S_OK
HRESULT hr = S_FALSE;
if (_bButtonDown == TRUE)
{
_bButtonDown = FALSE;
// if the focus link contains the point, we can
// notify the callback of a click event.
INT iHit;
HitTest(pt, (UINT*) &iHit);
TEXTBLOCK* pBlock = FindLink(_iFocus);
if (pBlock &&
(pBlock->state & LIS_ENABLED) != 0 &&
_iFocus == iHit)
{
hr = S_OK;
DoNotify (MARKUPMESSAGE_CLICKEXECUTE, _iFocus);
}
}
return hr;
}
HRESULT CMarkup::HitTest(const POINT pt, UINT* pidLink)
{
// returns S_OK only if pidLink is not INVALID_LINK_INDEX
HRESULT hr = S_FALSE;
*pidLink = INVALID_LINK_INDEX;
// Walk blocks until we find a link rect that contains the point
TEXTBLOCK* pBlock;
for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
if (IS_LINK(pBlock) && (pBlock->state & LIS_ENABLED)!=0)
{
RECTLISTENTRY* prce;
for(prce = pBlock->rgrle; prce; prce = prce->next)
{
if (PtInRect(&prce->rc, pt))
{
hr = S_OK;
*pidLink = pBlock->iLink;
}
}
}
}
return hr;
}
HRESULT CMarkup::SetLinkText(int iLink, UINT uMarkupLinkText, LPCWSTR pwszText)
{
HRESULT hr = E_INVALIDARG;
TEXTBLOCK* pBlock = FindLink(iLink);
if (pBlock)
{
hr = S_OK;
switch (uMarkupLinkText)
{
case MARKUPLINKTEXT_ID:
lstrcpyn(pBlock->szID, pwszText, ARRAYSIZE(pBlock->szID));
break;
case MARKUPLINKTEXT_URL:
Str_SetPtr(&pBlock->pszUrl, pwszText);
break;
case MARKUPLINKTEXT_TEXT:
Str_SetPtr(&pBlock->pszText, pwszText);
break;
default:
hr = S_FALSE;
break;
}
}
return hr;
}
HRESULT CMarkup::GetLinkText(int iLink, UINT uMarkupLinkText, LPWSTR pwszText, DWORD *pdwCch)
{
HRESULT hr = E_INVALIDARG;
TEXTBLOCK* pBlock = FindLink(iLink);
if (pBlock)
{
LPCTSTR pszSource;
switch (uMarkupLinkText)
{
case MARKUPLINKTEXT_ID:
pszSource = pBlock->szID;
hr = S_OK;
break;
case MARKUPLINKTEXT_URL:
pszSource = pBlock->pszUrl;
hr = S_OK;
break;
case MARKUPLINKTEXT_TEXT:
pszSource = pBlock->pszText;
hr = S_OK;
break;
}
if (hr == S_OK)
{
if (pwszText)
{
if (pszSource == NULL)
pszSource = TEXT("");
lstrcpyn(pwszText, pszSource, *pdwCch);
*pdwCch = lstrlen(pwszText); // fill in number of characters actually copied
}
else
*pdwCch = lstrlen(pszSource)+1; // fill in number of characters needed, including NULL
}
}
return hr;
}
#define MARKUPSTATE_VALID (MARKUPSTATE_ENABLED | MARKUPSTATE_VISITED | MARKUPSTATE_FOCUSED)
HRESULT CMarkup::SetState(int iLink, UINT uStateMask, UINT uState)
{
BOOL bRedraw = FALSE;
HRESULT hr = E_FAIL;
TEXTBLOCK* pBlock = FindLink(iLink);
if (uStateMask & ~MARKUPSTATE_VALID)
return E_INVALIDARG;
if (pBlock)
{
hr = S_OK;
if (uStateMask & MARKUPSTATE_ENABLED)
{
bRedraw |= _AssignBit(MARKUPSTATE_ENABLED, pBlock->state, uState);
int cEnabledLinks = StateCount(MARKUPSTATE_ENABLED, MARKUPSTATE_ENABLED);
}
if (uStateMask & MARKUPSTATE_VISITED)
{
bRedraw |= _AssignBit(MARKUPSTATE_VISITED, pBlock->state, uState);
}
if (uStateMask & MARKUPSTATE_FOCUSED)
{
// Focus assignment is handled differently;
// one and only one link can have focus...
if (uState & MARKUPSTATE_FOCUSED)
{
bRedraw |= (_iFocus != iLink);
_iFocus = iLink;
}
else
{
bRedraw |= (_iFocus == iLink);
_iFocus = INVALID_LINK_INDEX;
}
}
}
if (bRedraw)
{
_pMarkupCallback->InvalidateRect(NULL);
}
return hr;
}
HRESULT CMarkup::GetState(int iLink, UINT uStateMask, UINT* puState)
{
HRESULT hr = E_FAIL;
TEXTBLOCK* pBlock = FindLink(iLink);
if (pBlock && puState != NULL)
{
hr = S_FALSE;
*puState = 0;
if (uStateMask & MARKUPSTATE_FOCUSED)
{
if (_iFocus == iLink)
*puState |= MARKUPSTATE_FOCUSED;
hr = S_OK;
}
if (uStateMask & MARKUPSTATE_ENABLED)
{
if (pBlock->state & MARKUPSTATE_ENABLED)
*puState |= MARKUPSTATE_ENABLED;
hr = S_OK;
}
if (uStateMask & MARKUPSTATE_VISITED)
{
if (pBlock->state & MARKUPSTATE_VISITED)
*puState |= MARKUPSTATE_VISITED;
hr = S_OK;
}
}
return hr;
}
//-------------------------------------------------------------------------//
// CMarkup internal implementation
//-------------------------------------------------------------------------//
void CMarkup::FreeBlocks()
{
for(TEXTBLOCK* pBlock = _rgBlocks; pBlock; )
{
TEXTBLOCK* pNext = pBlock->next;
delete pBlock;
pBlock = pNext;
}
_rgBlocks = NULL;
_cBlocks = _Markups = 0;
}
TEXTBLOCK* CMarkup::CreateBlock(LPCTSTR pszStart, LPCTSTR pszEnd, int iLink)
{
TEXTBLOCK* pBlock = NULL;
int cch = (int)(pszEnd - pszStart) + 1;
if (cch > 0)
{
pBlock = new TEXTBLOCK;
if (pBlock)
{
pBlock->pszText = new TCHAR[cch];
if (pBlock->pszText == NULL)
{
delete pBlock;
pBlock = NULL;
}
else
{
lstrcpyn(pBlock->pszText, pszStart, cch);
pBlock->iLink = iLink;
}
}
}
return pBlock;
}
HCURSOR CMarkup::GetLinkCursor()
{
if (!_hcurHand)
{
_hcurHand = LoadCursor(NULL, IDC_HAND);
}
return _hcurHand;
}
HRESULT CMarkup::_GetNextAnchorTag(LPCTSTR * ppszBlock, int * pcBlocks, LPTSTR pszURL, int cchSize, LPTSTR pszID, int cchID)
{
HRESULT hr = E_FAIL;
LPTSTR pszStartOfTag;
LPTSTR pszIterate = (LPTSTR)*ppszBlock;
LPTSTR pszStartTry = (LPTSTR)*ppszBlock; // We start looking for "<A" at the beginning.
pszURL[0] = 0;
pszID[0] = 0;
// While we find a possible start of a tag.
while ((pszStartOfTag = StrStrI(pszStartTry, LINKTAG1)) != NULL)
{
// See if the rest of the string completes the tag.
pszIterate = pszStartOfTag;
pszStartTry = CharNext(pszStartOfTag); // Do this so the while loop will end when we finish don't find any more "<A".
if (pszIterate[0])
{
pszIterate += cchLINKTAG1; // Skip past the start of the tag.
// Walk thru the Value/Data pairs in the tag
TCHAR szValue[MAX_PATH];
TCHAR szData[L_MAX_URL_LENGTH];
pszIterate = SkipWhite(pszIterate); // SkipWhiteSpace
while ((CH_ENDTAG != pszIterate[0]) &&
SUCCEEDED(_GetNextValueDataPair(&pszIterate, szValue, ARRAYSIZE(szValue), szData, ARRAYSIZE(szData))))
{
if (0 == StrCmpI(szValue, SZ_ATTRIBUTE_HREF))
{
StrCpyN(pszURL, szData, cchSize);
}
else if (0 == StrCmpI(szValue, SZ_ATTRIBUTE_ID))
{
StrCpyN(pszID, szData, cchID);
}
else
{
// We ignore other pairs in order to be back-compat with future
// supported attributes.
}
pszIterate = SkipWhite(pszIterate);
}
if (CH_ENDTAG == pszIterate[0])
{
hr = S_OK;
}
}
if (SUCCEEDED(hr))
{
// Add run between psz1 and pszBlock as static text
if (pszStartOfTag > *ppszBlock)
{
TEXTBLOCK * pBlock = CreateBlock(*ppszBlock, pszStartOfTag, INVALID_LINK_INDEX);
if (NULL != pBlock)
{
Add(pBlock);
(*pcBlocks)++;
}
}
*ppszBlock = CharNext(pszIterate); // Skip past the tag's ">"
// We found an entire tag. Stop looking.
break;
}
else
{
// The "<A" we tried wasn't a valid tag. Are we at the end of the string?
// If not, let's keep looking for other "<A" that may be valid.
}
}
return hr;
}
void CMarkup::Parse(LPCTSTR pszText)
{
TEXTBLOCK* pBlock;
int cBlocks = 0, Markups = 0;
LPCTSTR psz1, psz2, pszBlock;
LPTSTR pszBuf = NULL;
FreeBlocks(); // free existing blocks
pszBuf = (LPTSTR)pszText;
if (!(pszBuf && *pszBuf))
{
goto exit;
}
for(pszBlock = pszBuf; pszBlock && *pszBlock;)
{
TCHAR szURL[L_MAX_URL_LENGTH];
TCHAR szID[MAX_LINKID_TEXT];
// Search for "<a>" tag
if (IsMarkupAllowed() &&
SUCCEEDED(_GetNextAnchorTag(&pszBlock, &cBlocks, szURL, ARRAYSIZE(szURL), szID, ARRAYSIZE(szID))))
{
psz1 = pszBlock; // After _GetNextAnchorTag(), pszBlock points to the char after the start tag.
if (psz1 && *psz1)
{
if ((psz2 = StrStrI(pszBlock, LINKTAG2)) != NULL)
{
if ((pBlock = CreateBlock(psz1, psz2, Markups)) != NULL)
{
if (szURL[0])
{
Str_SetPtr(&pBlock->pszUrl, szURL);
}
if (szID[0])
{
StrCpyN(pBlock->szID, szID, ARRAYSIZE(pBlock->szID));
}
Add(pBlock);
cBlocks++;
Markups++;
}
// safe-skip over tag
for(int i = 0;
i < cchLINKTAG2 && psz2 && *psz2;
i++, psz2 = CharNext(psz2));
pszBlock = psz2;
}
else // syntax error; mark trailing run is static text.
{
psz2 = pszBlock + lstrlen(pszBlock);
if ((pBlock = CreateBlock(psz1, psz2, INVALID_LINK_INDEX)) != NULL)
{
Add(pBlock);
cBlocks++;
}
pszBlock = psz2;
}
}
}
else // no more tags. Mark the last run of static text
{
psz2 = pszBlock + lstrlen(pszBlock);
if ((pBlock = CreateBlock(pszBlock, psz2, INVALID_LINK_INDEX)) != NULL)
{
Add(pBlock);
cBlocks++;
}
pszBlock = psz2;
}
}
ASSERT(cBlocks == _cBlocks);
ASSERT(Markups == _Markups);
exit:
if (!pszText && pszBuf) // delete text buffer if we had alloc'd it.
{
delete [] pszBuf;
}
}
BOOL CMarkup::Add(TEXTBLOCK* pAdd)
{
BOOL bAdded = FALSE;
pAdd->next = NULL;
if (!_rgBlocks)
{
_rgBlocks = pAdd;
bAdded = TRUE;
}
else
{
for(TEXTBLOCK* pBlock = _rgBlocks; pBlock && !bAdded; pBlock = pBlock->next)
{
if (!pBlock->next)
{
pBlock->next = pAdd;
bAdded = TRUE;
}
}
}
if (bAdded)
{
_cBlocks++;
if (IS_LINK(pAdd))
{
_Markups++;
}
}
return bAdded;
}
TEXTBLOCK* CMarkup::FindLink(int iLink) const
{
if (iLink == INVALID_LINK_INDEX)
{
return NULL;
}
for(TEXTBLOCK* pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
if (IS_LINK(pBlock) && pBlock->iLink == iLink)
return pBlock;
}
return NULL;
}
// NOTE: optimizatation! skip drawing loop when called for calcrect!
void CMarkup::Paint(HDC hdcClient, LPCRECT prcClient, BOOL bDraw)
{
HDC hdc = hdcClient;
COLORREF rgbOld = GetTextColor(hdc); // save text color
HFONT hFontOld = (HFONT) GetCurrentObject(hdc, OBJ_FONT);
TEXTBLOCK* pBlock;
BOOL fFocus = IsFocused();
if (_cBlocks == 1)
{
pBlock = _rgBlocks;
HFONT hFont = _hfStatic;
pBlock->FreeRects(); // free hit/focus rects; we're going to recompute.
if (IS_LINK(pBlock))
{
SetTextColor(hdc, (pBlock->state & LIS_ENABLED) ? LINKCOLOR_ENABLED : LINKCOLOR_DISABLED);
hFont = _hfLink;
}
if (hFont)
{
SelectObject(hdc, hFont);
}
RECT rc = *prcClient;
int cch = lstrlen(pBlock->pszText);
ThemedDrawText(hdc, pBlock->pszText, cch, &rc, _uDrawTextFlags | DT_CALCRECT, IS_LINK(pBlock));
pBlock->AddRect(rc, 0, cch, 0);
_cyIdeal = RECTHEIGHT(rc);
_cxIdeal = RECTWIDTH(rc);
if (bDraw)
{
ThemedDrawText(hdc, pBlock->pszText, cch, &rc, _uDrawTextFlags, IS_LINK(pBlock));
if (fFocus)
{
SetTextColor(hdc, rgbOld); // restore text color
DrawFocusRect(hdc, &rc);
}
}
}
else
{
TEXTMETRIC tm;
int iLineWidth[255]; // line index offset
int iLine = 0, // current line index
cyLine = 0, // line height.
cyLeading = 0, // internal leading
_cchOldDrawn = 1; // get out of infinite loop if window too small t-jklann
RECT rcDraw = *prcClient; // initialize line rect
_cxIdeal = 0;
// Initialize iLineWidth (just index 0, others init on use)
iLineWidth[0]=0;
// Get font metrics into cyLeading
if (!_hTheme)
{
SelectObject(hdc, _hfLink);
GetTextMetrics(hdc, &tm);
if (tm.tmExternalLeading > cyLeading)
{
cyLeading = tm.tmExternalLeading;
}
SelectObject(hdc, _hfStatic);
GetTextMetrics(hdc, &tm);
if (tm.tmExternalLeading > cyLeading)
{
cyLeading = tm.tmExternalLeading;
}
}
else
{
GetThemeTextMetrics(_hTheme, hdc, _iThemePartId, _iThemeStateIdNormal, &tm);
if (tm.tmExternalLeading > cyLeading)
{
cyLeading = tm.tmExternalLeading;
}
GetThemeTextMetrics(_hTheme, hdc, _iThemePartId, _iThemeStateIdLink, &tm);
if (tm.tmExternalLeading > cyLeading)
{
cyLeading = tm.tmExternalLeading;
}
}
// Save us a lot of time if text hasn't changed...
if (_bRefreshText == TRUE || !EqualRect(&_rRefreshRect, prcClient))
{
UINT uDrawTextCalc = _uDrawTextFlags | DT_CALCRECT | DT_SINGLELINE;
uDrawTextCalc &= ~(DT_CENTER | DT_LEFT | DT_RIGHT | DT_VCENTER | DT_BOTTOM);
BOOL bKillingLine = FALSE;
// For each block of text (calculation loop)...
for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
// font select (so text will draw correctly)
if (!_hTheme)
{
BOOL bLink = IS_LINK(pBlock);
HFONT hFont = bLink ? _hfLink : _hfStatic;
if (hFont)
{
SelectObject(hdc, hFont);
}
}
int cchDraw = lstrlen(pBlock->pszText); // chars to draw, this block
int cchDrawn = 0; // chars to draw, this block
LPTSTR pszText = &pBlock->pszText[cchDrawn];
LPTSTR pszTextOriginal = &pBlock->pszText[cchDrawn];
pBlock->FreeRects(); // free hit/focus rects; we're going to recompute.
// while text remains in this block...
_cchOldDrawn = 1;
while(cchDraw > 0 && !((_uDrawTextFlags & DT_SINGLELINE) && (iLine>0)))
{
// compute line height and maximum text width to rcBlock
RECT rcBlock;
int cchTry = cchDraw;
int cchTrySave = cchTry;
int cchBreak = 0;
int iLineBreakSize;
BOOL bRemoveBreak = FALSE;
BOOL bRemoveLineBreak = FALSE;
RECT rcCalc;
CopyRect(&rcCalc, &rcDraw);
// support multiline text phrases
bRemoveLineBreak = _FindFirstLineBreak(pszText, cchTry, &cchBreak, &iLineBreakSize);
if (bRemoveLineBreak)
{
cchTry = cchBreak;
}
// find out how much we can fit on this line within the rectangle
// calc rect breaking at breakpoints (or -1 char) until rectangle fits inside drawing rect.
for(;;)
{
// choose codepath: themes or normal drawtext (no exttextout path)
// now we use drawtext to preserve formatting options (tabs/underlines)
ThemedDrawText(hdc, pszText, cchTry, &rcCalc, uDrawTextCalc, IS_LINK(pBlock));
cyLine = RECTHEIGHT(rcCalc);
// special case: support \n as only character on line (we need a valid line width & length)
if (cchTry == 0 && bRemoveLineBreak==TRUE)
{
// these two lines adjust drawing to within a valid range when the \n is barely cut off
rcCalc.left = prcClient->left;
rcCalc.right = prcClient->right;
cyLine = ThemedDrawText(hdc, TEXT("a"), 1, &rcCalc, uDrawTextCalc, IS_LINK(pBlock));
// the "a" could be any text. It exists because passing "\n" to DrawText doesn't return a valid line height.
rcCalc.right = rcCalc.left;
}
if (RECTWIDTH(rcCalc) > RECTWIDTH(rcDraw))
{
// too big
cchTrySave = cchTry;
BOOL fBreak = _FindLastBreakChar(pszText, cchTry, tm.tmBreakChar, &cchTry, &bRemoveBreak);
// case that our strings ends with a valid break char
if (cchTrySave == cchTry && cchTry > 0)
{
cchTry--;
}
// this code allows character wrapping instead of just word wrapping.
// keep it in case we want to change the behavior.
if (!fBreak && prcClient->left == rcDraw.left)
{
// no break character found, so force a break if we can.
if (cchTrySave > 0)
{
cchTry = cchTrySave - 1;
}
}
if (cchTry > 0)
{
continue;
}
}
break;
}
// if our line break got clipped, turn off line break..
if (bRemoveLineBreak && cchBreak > cchTry)
{
bRemoveLineBreak = FALSE;
}
// Count the # chars drawn, account for clipping
cchDrawn = cchTry;
if ((cchTry < cchDraw) && bRemoveLineBreak)
{
cchDrawn+=iLineBreakSize;
}
// DT_WORDBREAK off support
// Kill this line if bKillingLine is true; i.e. pretend we drew it, but do nothing
if (bKillingLine)
{
pszText += cchDrawn;
}
else
{
// initialize drawing rectangle and block rectangle
SetRect(&rcBlock, rcCalc.left , 0, rcCalc.right , RECTHEIGHT(rcCalc));
rcDraw.right = min(rcDraw.left + RECTWIDTH(rcBlock), prcClient->right);
rcDraw.bottom = rcDraw.top + cyLine;
// Add rectangle to block's list and update line width and ideal x width
// (Only if we're actually going to draw this line, though)
if (cchTry)
{
// DT_SINGLELINE support
if (!((_uDrawTextFlags & DT_SINGLELINE) == DT_SINGLELINE) || (iLine == 0))
{
pBlock->AddRect(rcDraw, (UINT) (pszText-pszTextOriginal), cchDrawn, iLine);
}
iLineWidth[iLine] = max(iLineWidth[iLine], rcDraw.left - prcClient->left + RECTWIDTH(rcBlock));
_cxIdeal = max(_cxIdeal, iLineWidth[iLine]);
}
if (cchTry < cchDraw) // we got clipped
{
if (bRemoveBreak)
{
cchDrawn++;
}
pszText += cchDrawn;
// advance to next line and init next linewidth
iLine++;
iLineWidth[iLine]=0;
// t-jklann 6/00: added support for line wrap in displaced text (left&top)
rcDraw.left = prcClient->left;
if (!(_uDrawTextFlags & DT_SINGLELINE))
{
rcDraw.top = prcClient->top + iLine * cyLine;
}
else
{
rcDraw.top = prcClient->top;
}
rcDraw.bottom = rcDraw.top + cyLine + cyLeading;
rcDraw.right = prcClient->right;
}
else // we were able to draw the entire text
{
// adjust drawing rectangle
rcDraw.left += RECTWIDTH(rcBlock);
rcDraw.right = prcClient->right;
}
// Update ideal y width
_cyIdeal = rcDraw.bottom - prcClient->top;
}
// support for: DT_WORDBREAK turned off
// Kill the next line if we got clipped and there's no wordbreak
if (((_uDrawTextFlags & DT_WORDBREAK) != DT_WORDBREAK))
{
if (cchTry < cchDraw )
{
bKillingLine = TRUE;
}
if (bRemoveLineBreak)
{
bKillingLine = FALSE;
}
}
// Update calculation of chars drawn
cchDraw -= cchDrawn;
// bug catch: get out if we really can't draw
if (_cchOldDrawn == 0 && cchDrawn == 0)
{
iLine--;
rcDraw.top = prcClient->top + iLine * cyLine;
cchDraw = 0;
}
_cchOldDrawn = cchDrawn;
}
}
// Handle justification issues (DT_VCENTER, DT_TOP, DT_BOTTOM)
if (((_uDrawTextFlags & DT_SINGLELINE) == DT_SINGLELINE) &&
((_uDrawTextFlags & (DT_VCENTER | DT_BOTTOM)) > 0))
{
// Calc offset
int cyOffset = 0;
if ((_uDrawTextFlags & DT_VCENTER) == DT_VCENTER)
{
cyOffset = (RECTHEIGHT(*prcClient) - _cyIdeal)/2;
}
if ((_uDrawTextFlags & DT_BOTTOM) == DT_BOTTOM)
{
cyOffset = (RECTHEIGHT(*prcClient) - _cyIdeal);
}
// Offset every rectangle
for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
{
prce->rc.top += cyOffset;
prce->rc.bottom += cyOffset;
}
}
}
// Handle justification issues (DT_CENTER, DT_LEFT, DT_RIGHT)
if (((_uDrawTextFlags & DT_CENTER) == DT_CENTER) || ((_uDrawTextFlags & DT_RIGHT) == DT_RIGHT))
{
// Step 1: turn iLineWidth into an offset vector
for (int i = 0; i <= iLine; i++)
{
if (RECTWIDTH(*prcClient) > iLineWidth[i])
{
if ((_uDrawTextFlags & DT_CENTER) == DT_CENTER)
{
iLineWidth[i] = (RECTWIDTH(*prcClient)-iLineWidth[i])/2;
}
if ((_uDrawTextFlags & DT_RIGHT) == DT_RIGHT)
{
iLineWidth[i] = (RECTWIDTH(*prcClient)-iLineWidth[i]);
}
}
else
{
iLineWidth[i] = 0;
}
}
// Step 2: offset every rect-angle
for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
{
prce->rc.left += iLineWidth[prce->uLineNumber];
prce->rc.right += iLineWidth[prce->uLineNumber];
}
}
}
CopyRect(&_rRefreshRect, prcClient);
_bRefreshText = FALSE;
}
if (bDraw)
{
// For each block of text (drawing loop)...
UINT uDrawTextDraw = _uDrawTextFlags | DT_SINGLELINE;
uDrawTextDraw &= ~(DT_CENTER | DT_LEFT | DT_RIGHT | DT_CALCRECT | DT_VCENTER | DT_BOTTOM);
LRESULT dwCustomDraw=0;
_pMarkupCallback->OnCustomDraw(CDDS_PREPAINT, hdc, prcClient, 0, 0, &dwCustomDraw);
for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
BOOL bLink = IS_LINK(pBlock);
BOOL bEnabled = pBlock->state & LIS_ENABLED;
// font select
if (!_hTheme)
{
HFONT hFont = bLink ? _hfLink : _hfStatic;
if (hFont)
{
SelectObject(hdc, hFont);
}
}
// initialize foreground color
if (!_hTheme)
{
if (bLink)
{
SetTextColor(hdc, bEnabled ? LINKCOLOR_ENABLED : LINKCOLOR_DISABLED);
}
else
{
SetTextColor(hdc, rgbOld); // restore text color
}
}
if (dwCustomDraw & CDRF_NOTIFYITEMDRAW)
{
_pMarkupCallback->OnCustomDraw(CDDS_ITEMPREPAINT, hdc, NULL, pBlock->iLink, bEnabled ? CDIS_DEFAULT : CDIS_DISABLED, NULL);
}
// draw the text
LPTSTR pszText = pBlock->pszText;
LPTSTR pszTextOriginal = pBlock->pszText;
for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
{
RECT rc = prce->rc;
pszText = pszTextOriginal + prce->uCharStart;
ThemedDrawText(hdc, pszText, prce->uCharCount, &rc, uDrawTextDraw, IS_LINK(pBlock));
}
// Draw focus rect(s)
if (fFocus && pBlock->iLink == _iFocus && IS_LINK(pBlock))
{
SetTextColor(hdc, rgbOld); // restore text color
for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
{
DrawFocusRect(hdc, &prce->rc);
}
}
if (dwCustomDraw & CDRF_NOTIFYITEMDRAW)
{
_pMarkupCallback->OnCustomDraw(CDDS_ITEMPOSTPAINT, hdc, NULL, pBlock->iLink, bEnabled ? CDIS_DEFAULT : CDIS_DISABLED, NULL);
}
}
if (dwCustomDraw & CDRF_NOTIFYPOSTPAINT)
{
_pMarkupCallback->OnCustomDraw(CDDS_POSTPAINT, hdc, NULL, 0, 0, NULL);
}
}
}
SetTextColor(hdc, rgbOld); // restore text color
if (hFontOld)
{
SelectObject(hdc, hFontOld);
}
}
int CMarkup::GetNextEnabledLink(int iStart, int nDir) const
{
ASSERT(-1 == nDir || 1 == nDir);
if (_Markups > 0)
{
if (INVALID_LINK_INDEX == iStart)
{
iStart = nDir > 0 ? -1 : _Markups;
}
for(iStart += nDir; iStart >= 0; iStart += nDir)
{
TEXTBLOCK* pBlock = FindLink(iStart);
if (!pBlock)
{
return INVALID_LINK_INDEX;
}
if (pBlock->state & LIS_ENABLED)
{
ASSERT(iStart == pBlock->iLink);
return iStart;
}
}
}
return INVALID_LINK_INDEX;
}
int CMarkup::StateCount(DWORD dwStateMask, DWORD dwState) const
{
TEXTBLOCK* pBlock;
int cnt = 0;
for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
{
if (IS_LINK(pBlock) &&
(pBlock->state & dwStateMask) == dwState)
{
cnt++;
}
}
return cnt;
}
BOOL CMarkup::WantTab(int* biFocus) const
{
int nDir = TESTKEYSTATE(VK_SHIFT) ? -1 : 1;
int iFocus = GetNextEnabledLink(_iFocus, nDir);
if (INVALID_LINK_INDEX != iFocus)
{
if (biFocus)
{
*biFocus = iFocus;
}
return TRUE;
}
else
{
// if we can't handle the focus, prepare for the next round
//iFocus = GetNextEnabledLink(-1, nDir);
*biFocus = -1;
return FALSE;
}
}
void CMarkup::AssignTabFocus(int nDirection)
{
if (_Markups)
{
if (0 == nDirection)
{
if (INVALID_LINK_INDEX != _iFocus)
{
return;
}
nDirection = 1;
}
_iFocus = GetNextEnabledLink(_iFocus, nDirection);
}
}
//-------------------------------------------------------------------------//
TEXTBLOCK::TEXTBLOCK()
: iLink(INVALID_LINK_INDEX),
next(NULL),
state(LIS_ENABLED),
pszText(NULL),
pszUrl(NULL),
rgrle(NULL)
{
*szID = 0;
}
TEXTBLOCK::~TEXTBLOCK()
{
// free block text
Str_SetPtr(&pszText, NULL);
Str_SetPtr(&pszUrl, NULL);
// free rectangle(s)
FreeRects();
}
void TEXTBLOCK::AddRect(const RECT& rc, UINT uMyCharStart, UINT uMyCharCount, UINT uMyLineNumber)
{
RECTLISTENTRY* prce;
if ((prce = new RECTLISTENTRY) != NULL)
{
CopyRect(&(prce->rc), &rc);
prce->next = NULL;
prce->uCharStart = uMyCharStart;
prce->uCharCount = uMyCharCount;
prce->uLineNumber = uMyLineNumber;
}
if (rgrle == NULL)
{
rgrle = prce;
}
else
{
for(RECTLISTENTRY* p = rgrle; p; p = p->next)
{
if (p->next == NULL)
{
p->next = prce;
break;
}
}
}
}
void TEXTBLOCK::FreeRects()
{
for(RECTLISTENTRY* p = rgrle; p;)
{
RECTLISTENTRY* next = p->next;
delete p;
p = next;
}
rgrle = NULL;
}
//-------------------------------------------------------------------------//
// t-jklann 6/00: added these formerly global methods to the CMarkup class
// Returns a pointer to the first non-whitespace character in a string.
LPTSTR CMarkup::SkipWhite(LPTSTR lpsz)
{
/* prevent sign extension in case of DBCS */
while (*lpsz && (TUCHAR)*lpsz <= TEXT(' '))
lpsz++;
return(lpsz);
}
BOOL CMarkup::_AssignBit(const DWORD dwBit, DWORD& dwDest, const DWORD dwSrc) // returns TRUE if changed
{
if (((dwSrc & dwBit) != 0) != ((dwDest & dwBit) != 0))
{
if (((dwSrc & dwBit) != 0))
{
dwDest |= dwBit;
}
else
{
dwDest &= ~dwBit;
}
return TRUE;
}
return FALSE;
}
BOOL CMarkup::IsStringAlphaNumeric(LPCTSTR pszString)
{
while (pszString[0])
{
if (!IsCharAlphaNumeric(pszString[0]))
{
return FALSE;
}
pszString = CharNext(pszString);
}
return TRUE;
}
// We are looking for the next value/data pair. Formated like this:
// VALUE="<data>"
HRESULT CMarkup::_GetNextValueDataPair(LPTSTR * ppszBlock, LPTSTR pszValue, int cchValue, LPTSTR pszData, int cchData)
{
HRESULT hr = E_FAIL;
LPCTSTR pszIterate = *ppszBlock;
LPCTSTR pszEquals = StrStr(pszIterate, TEXT("=\""));
if (pszEquals)
{
cchValue = MIN(cchValue, (pszEquals - *ppszBlock + 1));
StrCpyN(pszValue, *ppszBlock, cchValue);
pszEquals += 2; // Skip past the ="
if (IsStringAlphaNumeric(pszValue))
{
LPTSTR pszEndOfData = StrChr(pszEquals, TEXT('\"'));
if (pszEndOfData)
{
cchData = MIN(cchData, (pszEndOfData - pszEquals + 1));
StrCpyN(pszData, pszEquals, cchData);
*ppszBlock = CharNext(pszEndOfData);
hr = S_OK;
}
}
}
return hr;
}
//-------------------------------------------------------------------------
//
// IsFEChar - Detects East Asia FullWidth character.
// borrowed from UserIsFullWidth in ntuser\rtl\drawtext.c
//
BOOL IsFEChar(WCHAR wChar)
{
static struct
{
WCHAR wchStart;
WCHAR wchEnd;
} rgFullWidthUnicodes[] =
{
{ 0x4E00, 0x9FFF }, // CJK_UNIFIED_IDOGRAPHS
{ 0x3040, 0x309F }, // HIRAGANA
{ 0x30A0, 0x30FF }, // KATAKANA
{ 0xAC00, 0xD7A3 } // HANGUL
};
BOOL fRet = FALSE;
//
// Early out for ASCII. If the character < 0x0080, it should be a halfwidth character.
//
if (wChar >= 0x0080)
{
int i;
//
// Scan FullWdith definition table... most of FullWidth character is
// defined here... this is faster than call NLS API.
//
for (i = 0; i < ARRAYSIZE(rgFullWidthUnicodes); i++)
{
if ((wChar >= rgFullWidthUnicodes[i].wchStart) &&
(wChar <= rgFullWidthUnicodes[i].wchEnd))
{
fRet = TRUE;
break;
}
}
}
return fRet;
}
//-------------------------------------------------------------------------
BOOL IsFEString(IN LPCTSTR psz, IN int cchText)
{
for(int i=0; i < cchText; i++)
{
if (IsFEChar(psz[i]))
{
return TRUE;
}
}
return FALSE;
}
//-------------------------------------------------------------------------
int CMarkup::_IsLineBreakChar(LPCTSTR psz, int ich, TCHAR chBreak, OUT BOOL* pbRemove, BOOL fIgnoreSpace)
{
LPTSTR pch;
*pbRemove = FALSE;
ASSERT(psz != NULL)
ASSERT(psz[ich] != 0);
// Try caller-provided break character (assumed a 'remove' break char).
if (!(fIgnoreSpace && (chBreak == 0x20)) && (psz[ich] == chBreak))
{
*pbRemove = TRUE;
return ich;
}
#define MAX_LINEBREAK_RESOURCE 128
static TCHAR _szBreakRemove [MAX_LINEBREAK_RESOURCE] = {0};
static TCHAR _szBreakPreserve [MAX_LINEBREAK_RESOURCE] = {0};
#define LOAD_BREAKCHAR_RESOURCE(nIDS, buff) \
if (0==*buff) { LoadString(HINST_THISDLL, nIDS, buff, ARRAYSIZE(buff)); }
// Try 'remove' break chars
LOAD_BREAKCHAR_RESOURCE(IDS_LINEBREAK_REMOVE, _szBreakRemove);
for (pch = _szBreakRemove; *pch; pch = CharNext(pch))
{
if (!(fIgnoreSpace && (*pch == 0x20)) && (psz[ich] == *pch))
{
*pbRemove = TRUE;
return ich;
}
}
// Try 'preserve prior' break chars:
LOAD_BREAKCHAR_RESOURCE(IDS_LINEBREAK_PRESERVE, _szBreakPreserve);
for(pch = _szBreakPreserve; *pch; pch = CharNext(pch))
{
if (!(fIgnoreSpace && (*pch == 0x20)) && (psz[ich] == *pch))
{
return ++ich;
}
}
return -1;
}
//-------------------------------------------------------------------------
BOOL CMarkup::_FindLastBreakChar(
IN LPCTSTR pszText,
IN int cchText,
IN TCHAR chBreak, // official break char (from TEXTMETRIC).
OUT int* piLast,
OUT BOOL* pbRemove)
{
*piLast = 0;
*pbRemove = FALSE;
// 338710: Far East writing doesn't use the space character to separate
// words, ignore the space char as a possible line delimiter.
BOOL fIgnoreSpace = IsFEString(pszText, cchText);
for(int i = cchText-1; i >= 0; i--)
{
int ich = _IsLineBreakChar(pszText, i, chBreak, pbRemove, fIgnoreSpace);
if (ich >= 0)
{
*piLast = ich;
return TRUE;
}
}
return FALSE;
}
BOOL CMarkup::_FindFirstLineBreak(
IN LPCTSTR pszText,
IN int cchText,
OUT int* piLast,
OUT int* piLineBreakSize)
{
*piLast = 0;
*piLineBreakSize = 0;
// Searches for \n, \r, or \r\n
for(int i = 0; i < cchText; i++)
{
if ((*(pszText+i)=='\n') || (*(pszText+i)=='\r'))
{
*piLast = i;
if ((*(pszText+i)=='\r') && (*(pszText+i+1)=='\n'))
{
*piLineBreakSize = 2;
}
else
{
*piLineBreakSize = 1;
}
return TRUE;
}
}
return FALSE;
}
void CMarkup::DoNotify(int nCode, int iLink)
{
_pMarkupCallback->Notify(nCode, iLink);
}
int CMarkup::ThemedDrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat, BOOL bLink)
{
if (!_hTheme)
{
// NORMAL DRAWTEXT
return ::DrawText(hdc, lpString, nCount, lpRect, uFormat);
}
else
{
int iThemeStateId;
iThemeStateId = bLink ? _iThemeStateIdLink : _iThemeStateIdNormal;
if (uFormat & DT_CALCRECT)
{
// THEME CALC RECT SUPPORT
LPRECT lpBoundRect = lpRect;
if (RECTWIDTH(*lpRect)==0 && RECTHEIGHT(*lpRect)==0)
{
lpBoundRect = NULL;
}
GetThemeTextExtent(_hTheme, hdc, _iThemePartId, iThemeStateId, lpString, nCount, uFormat, lpBoundRect, lpRect);
}
else
{
// THEME DRAW SUPPORT
DrawThemeText(_hTheme, hdc, _iThemePartId, iThemeStateId, lpString, nCount, uFormat, 0, lpRect);
}
return (RECTHEIGHT(*lpRect));
}
}