//-------------------------------------------------------------------------// // markup.cpp - implementation of CMarkup // #include #include #include #include #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("') #define LINKTAG2 TEXT("") #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() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } 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) { StringCchCopy(pwszText, *pcchText, _pszCaption); } } else { for (TEXTBLOCK* pBlock = _rgBlocks; pBlock; pBlock = pBlock->next) { if (pBlock->pszText) { StringCchCat(pwszText, *pcchText, pBlock->pszText); } } } *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: StringCchCopy(pBlock->szID, ARRAYSIZE(pBlock->szID), pwszText); 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(""); } StringCchCopy(pwszText, *pdwCch, pszSource); *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 { StringCchCopy(pBlock->pszText, cch, pszStart); 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 " *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 "" 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]) { StringCchCopy(pBlock->szID, ARRAYSIZE(pBlock->szID), 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="" 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)); StringCchCopy(pszValue, cchValue, *ppszBlock); pszEquals += 2; // Skip past the =" if (IsStringAlphaNumeric(pszValue)) { LPTSTR pszEndOfData = StrChr(pszEquals, TEXT('\"')); if (pszEndOfData) { cchData = MIN(cchData, (pszEndOfData - pszEquals + 1)); StringCchCopy(pszData, cchData, pszEquals); *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)); } }