Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2057 lines
59 KiB

  1. //-------------------------------------------------------------------------//
  2. // markup.cpp - implementation of CMarkup
  3. //
  4. #include <ctlspriv.h>
  5. #include <shpriv.h>
  6. #include <markup.h>
  7. #include <oleacc.h>
  8. #define DllAddRef()
  9. #define DllRelease()
  10. typedef WCHAR TUCHAR, *PTUCHAR;
  11. #define IS_LINK(pBlock) ((pBlock) && (pBlock)->iLink != INVALID_LINK_INDEX)
  12. #ifndef POINTSPERRECT
  13. #define POINTSPERRECT (sizeof(RECT)/sizeof(POINT))
  14. #endif
  15. #ifndef MIN
  16. #define MIN(a,b) (((a) < (b)) ? (a) : (b))
  17. #endif
  18. #define TESTKEYSTATE(vk) ((GetKeyState(vk) & 0x8000)!=0)
  19. #define LINKCOLOR_ENABLED GetSysColor(COLOR_HOTLIGHT)
  20. #define LINKCOLOR_DISABLED GetSysColor(COLOR_GRAYTEXT)
  21. #define SZ_ATTRIBUTE_HREF TEXT("HREF")
  22. #define SZ_ATTRIBUTE_ID TEXT("ID")
  23. #define LINKTAG1 TEXT("<A")
  24. #define cchLINKTAG1 (ARRAYSIZE(LINKTAG1) - 1)
  25. #define CH_ENDTAG TEXT('>')
  26. #define LINKTAG2 TEXT("</A>")
  27. #define cchLINKTAG2 (ARRAYSIZE(LINKTAG2) - 1)
  28. #define Markup_DestroyMarkup(hMarkup)\
  29. ((IUnknown*)hMarkup)->Release();
  30. struct RECTLISTENTRY // rect list member
  31. {
  32. RECT rc;
  33. UINT uCharStart;
  34. UINT uCharCount;
  35. UINT uLineNumber;
  36. RECTLISTENTRY* next;
  37. };
  38. struct TEXTBLOCK // text segment data
  39. {
  40. friend class CMarkup;
  41. int iLink; // index of link (INVALID_LINK_INDEX if static text)
  42. DWORD state; // state bits
  43. TCHAR szID[MAX_LINKID_TEXT]; // link identifier.
  44. TEXTBLOCK* next; // next block
  45. RECTLISTENTRY* rgrle; // list of bounding rectangle(s)
  46. TCHAR* pszText; // text
  47. TCHAR* pszUrl; // URL.
  48. TEXTBLOCK();
  49. ~TEXTBLOCK();
  50. void AddRect(const RECT& rc, UINT uMyCharStart = 0, UINT uMyCharCount = 0, UINT uMyLineNumber = 0);
  51. void FreeRects();
  52. };
  53. class CMarkup : IControlMarkup
  54. {
  55. public:
  56. // API
  57. friend HRESULT Markup_Create(IMarkupCallback *pMarkupCallback, HFONT hf, HFONT hfu, REFIID riid, void **ppv);
  58. // IControlMarkup
  59. STDMETHODIMP SetCallback(IUnknown* punk);
  60. STDMETHODIMP GetCallback(REFIID riid, void** ppvUnk);
  61. STDMETHODIMP SetFonts(HFONT hFont, HFONT hFontUnderline);
  62. STDMETHODIMP GetFonts(HFONT* phFont, HFONT* phFontUnderline);
  63. STDMETHODIMP SetText(LPCWSTR pwszText);
  64. STDMETHODIMP GetText(BOOL bRaw, LPWSTR pwszText, DWORD *pdwCch);
  65. STDMETHODIMP SetLinkText(int iLink, UINT uMarkupLinkText, LPCWSTR pwszText);
  66. STDMETHODIMP GetLinkText(int iLink, UINT uMarkupLinkText, LPWSTR pwszText, DWORD *pdwCch);
  67. STDMETHODIMP SetRenderFlags(UINT uDT);
  68. STDMETHODIMP GetRenderFlags(UINT *puDT, HTHEME *phTheme, int *piPartId, int *piStateIdNormal, int *piStateIdLink);
  69. STDMETHODIMP SetThemeRenderFlags(UINT uDT, HTHEME hTheme, int iPartId, int iStateIdNormal, int iStateIdLink);
  70. STDMETHODIMP GetState(int iLink, UINT uStateMask, UINT* puState);
  71. STDMETHODIMP SetState(int iLink, UINT uStateMask, UINT uState);
  72. STDMETHODIMP DrawText(HDC hdcClient, LPCRECT prcClient);
  73. STDMETHODIMP SetLinkCursor();
  74. STDMETHODIMP CalcIdealSize(HDC hdc, UINT uMarkUpCalc, RECT* prc);
  75. STDMETHODIMP SetFocus();
  76. STDMETHODIMP KillFocus();
  77. STDMETHODIMP IsTabbable();
  78. STDMETHODIMP OnButtonDown(POINT pt);
  79. STDMETHODIMP OnButtonUp(POINT pt);
  80. STDMETHODIMP OnKeyDown(UINT uVitKey);
  81. STDMETHODIMP HitTest(POINT pt, UINT* pidLink);
  82. // commented out of IControlMarkup?
  83. STDMETHODIMP HandleEvent(BOOL keys, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  84. // IUnknown
  85. STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
  86. STDMETHODIMP_(ULONG) AddRef();
  87. STDMETHODIMP_(ULONG) Release();
  88. private:
  89. // private constructor
  90. CMarkup(IMarkupCallback *pMarkupCallback);
  91. CMarkup();
  92. ~CMarkup();
  93. friend struct TEXTBLOCK;
  94. HCURSOR GetLinkCursor();
  95. BOOL IsMarkupState(UINT uState)
  96. {
  97. return _pMarkupCallback && _pMarkupCallback->GetState(uState) == S_OK;
  98. }
  99. BOOL IsFocused()
  100. {
  101. return IsMarkupState(MARKUPSTATE_FOCUSED);
  102. }
  103. BOOL IsMarkupAllowed()
  104. {
  105. return IsMarkupState(MARKUPSTATE_ALLOWMARKUP);
  106. }
  107. void Parse(LPCTSTR pszText);
  108. BOOL Add(TEXTBLOCK* pAdd);
  109. TEXTBLOCK* FindLink(int iLink) const;
  110. void FreeBlocks();
  111. void DoNotify(int nCode, int iLink);
  112. int ThemedDrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat, BOOL bLink);
  113. void Paint(HDC hdc, IN OPTIONAL LPCRECT prcClient = NULL, BOOL bDraw = TRUE);
  114. BOOL WantTab(int* biFocus = NULL) const;
  115. void AssignTabFocus(int nDirection);
  116. int GetNextEnabledLink(int iStart, int nDir) const;
  117. int StateCount(DWORD dwStateMask, DWORD dwState) const;
  118. HRESULT _GetNextAnchorTag(LPCTSTR * ppszBlock, int * pcBlocks, LPTSTR pszURL, int cchSize, LPTSTR pszID, int cchID);
  119. static TEXTBLOCK* CreateBlock(LPCTSTR pszStart, LPCTSTR pszEnd, int iLink);
  120. // Data
  121. BOOL _bButtonDown; // true when button is clicked on a link but not yet released
  122. TEXTBLOCK* _rgBlocks; // linked list of text blocks
  123. int _cBlocks; // block count
  124. int _Markups; // link count
  125. int _iFocus; // index of focus link
  126. int _cyIdeal;
  127. int _cxIdeal;
  128. LPTSTR _pszCaption;
  129. HFONT _hfStatic,
  130. _hfLink;
  131. HCURSOR _hcurHand;
  132. IMarkupCallback *_pMarkupCallback;
  133. LONG _cRef;
  134. UINT _uDrawTextFlags;
  135. BOOL _bRefreshText;
  136. RECT _rRefreshRect;
  137. HTHEME _hTheme; // these 3 for theme compatible drawing
  138. int _iThemePartId;
  139. int _iThemeStateIdNormal;
  140. int _iThemeStateIdLink;
  141. // static helper methods
  142. static LPTSTR SkipWhite(LPTSTR);
  143. static BOOL _AssignBit(const DWORD , DWORD& , const DWORD);
  144. static BOOL IsStringAlphaNumeric(LPCTSTR);
  145. static HRESULT _GetNextValueDataPair(LPTSTR * , LPTSTR , int , LPTSTR , int);
  146. static int _IsLineBreakChar(LPCTSTR , int , TCHAR , OUT BOOL* , BOOL fIgnoreSpace);
  147. BOOL static _FindLastBreakChar(IN LPCTSTR , IN int , IN TCHAR , OUT int* , OUT BOOL*);
  148. BOOL _FindFirstLineBreak(IN LPCTSTR pszText, IN int cchText, OUT int* piLast, OUT int* piLineBreakSize);
  149. };
  150. CMarkup::CMarkup() :
  151. _cRef(1),
  152. _iFocus(INVALID_LINK_INDEX),
  153. _uDrawTextFlags(DT_LEFT | DT_WORDBREAK),
  154. _bRefreshText(TRUE),
  155. _iThemeStateIdLink(1)
  156. {
  157. }
  158. CMarkup::~CMarkup()
  159. {
  160. FreeBlocks();
  161. SetText(NULL);
  162. if (_pMarkupCallback)
  163. {
  164. _pMarkupCallback->Release();
  165. _pMarkupCallback = NULL;
  166. }
  167. }
  168. inline void MakePoint(LPARAM lParam, OUT LPPOINT ppt)
  169. {
  170. POINTS pts = MAKEPOINTS(lParam);
  171. ppt->x = pts.x;
  172. ppt->y = pts.y;
  173. }
  174. STDAPI Markup_Create(IMarkupCallback *pMarkupCallback, HFONT hf, HFONT hfUnderline, REFIID riid, void **ppv)
  175. {
  176. // Create CMarkup
  177. HRESULT hr = E_FAIL;
  178. CMarkup* pThis = new CMarkup();
  179. if (pThis)
  180. {
  181. pThis->SetCallback(pMarkupCallback);
  182. // init fonts
  183. pThis->SetFonts(hf, hfUnderline);
  184. // COM stuff
  185. hr = pThis->QueryInterface(riid, ppv);
  186. pThis->Release();
  187. }
  188. return hr;
  189. }
  190. HRESULT CMarkup::QueryInterface(REFIID riid, void **ppv)
  191. {
  192. static const QITAB qit[] =
  193. {
  194. QITABENT(CMarkup, IControlMarkup),
  195. { 0 },
  196. };
  197. return QISearch(this, qit, riid, ppv);
  198. }
  199. ULONG CMarkup::AddRef()
  200. {
  201. return InterlockedIncrement(&_cRef);
  202. }
  203. ULONG CMarkup::Release()
  204. {
  205. if (InterlockedDecrement(&_cRef))
  206. {
  207. return _cRef;
  208. }
  209. delete this;
  210. return 0;
  211. }
  212. STDMETHODIMP CMarkup::SetCallback(IUnknown* punk)
  213. {
  214. if (_pMarkupCallback)
  215. {
  216. _pMarkupCallback->Release();
  217. _pMarkupCallback = NULL;
  218. }
  219. if (punk)
  220. return punk->QueryInterface(IID_PPV_ARG(IMarkupCallback, &_pMarkupCallback));
  221. // To break reference, pass NULL.
  222. return S_OK;
  223. }
  224. STDMETHODIMP CMarkup::GetCallback(REFIID riid, void** ppvUnk)
  225. {
  226. if (_pMarkupCallback)
  227. return _pMarkupCallback->QueryInterface(riid, ppvUnk);
  228. return E_NOINTERFACE;
  229. }
  230. // IControlMarkup interface implementation
  231. STDMETHODIMP CMarkup::SetFocus()
  232. {
  233. AssignTabFocus(0);
  234. _pMarkupCallback->InvalidateRect(NULL);
  235. return S_OK;
  236. }
  237. STDMETHODIMP CMarkup::KillFocus()
  238. {
  239. // Reset the focus position on request
  240. _iFocus=INVALID_LINK_INDEX;
  241. _pMarkupCallback->InvalidateRect(NULL);
  242. return S_OK;
  243. }
  244. STDMETHODIMP CMarkup::IsTabbable()
  245. {
  246. HRESULT hr = S_FALSE;
  247. int nDir = TESTKEYSTATE(VK_SHIFT) ? -1 : 1;
  248. if (GetNextEnabledLink(_iFocus, nDir) != INVALID_LINK_INDEX)
  249. {
  250. hr = S_OK;
  251. }
  252. return hr;
  253. }
  254. //bugs: calculating ideal 'width' returns bogus valuez
  255. STDMETHODIMP CMarkup::CalcIdealSize(HDC hdc, UINT uMarkUpCalc, RECT* prc)
  256. {
  257. // prc is changed (prc.height or prc.width) only if hr = S_OK
  258. /* currently:
  259. MARKUPSIZE_CALCHEIGHT: takes an initial max width (right-left) and calculates
  260. and ideal height (bottom=ideal_height+top) and the actual width used, which
  261. is always less than the maximum (right=width_used+left).
  262. MARKUPSIZE_CALCWIDTH: doesn't do anything correctly. don't try it. */
  263. HRESULT hr = E_FAIL;
  264. BOOL bQuitNow = FALSE;
  265. if (prc == NULL)
  266. return E_INVALIDARG;
  267. if (NULL != _rgBlocks && 0 != _cBlocks)
  268. {
  269. int cyRet = -1;
  270. SIZE sizeDC;
  271. RECT rc;
  272. if (uMarkUpCalc == MARKUPSIZE_CALCWIDTH)
  273. {
  274. // Come up with a conservative estimate for the new width.
  275. sizeDC.cx = MulDiv(prc->right-prc->left, 1, prc->top-prc->bottom) * 2;
  276. sizeDC.cy = prc->bottom - prc->top;
  277. if (sizeDC.cy < 0)
  278. {
  279. bQuitNow = TRUE;
  280. }
  281. }
  282. if (uMarkUpCalc == MARKUPSIZE_CALCHEIGHT)
  283. {
  284. // Come up with a conservative estimate for the new height.
  285. sizeDC.cy = MulDiv(prc->top-prc->bottom, 1, prc->right-prc->left) * 2;
  286. sizeDC.cx = prc->right-prc->left;
  287. if (sizeDC.cx < 0)
  288. {
  289. bQuitNow = TRUE;
  290. }
  291. // If no x size is specified, make a big estimate
  292. // (i.e. the estimate is the x size of the unparsed text)
  293. if (sizeDC.cx == 0)
  294. {
  295. if (!_hTheme)
  296. {
  297. GetTextExtentPoint(hdc, _pszCaption, lstrlen(_pszCaption), &sizeDC);
  298. }
  299. if (_hTheme)
  300. {
  301. // Get theme font size estimate for the larger part-font type
  302. RECT rcTemp;
  303. GetThemeTextExtent(_hTheme, hdc, _iThemePartId, _iThemeStateIdNormal, _pszCaption, -1, 0, NULL, &rcTemp);
  304. sizeDC.cx = rcTemp.right - rcTemp.left;
  305. GetThemeTextExtent(_hTheme, hdc, _iThemePartId, _iThemeStateIdLink, _pszCaption, -1, 0, NULL, &rcTemp);
  306. if ((rcTemp.right - rcTemp.left) > sizeDC.cx)
  307. {
  308. sizeDC.cx = rcTemp.right - rcTemp.left;
  309. }
  310. }
  311. }
  312. }
  313. hr = E_FAIL;
  314. if (!bQuitNow)
  315. {
  316. int cyPrev = _cyIdeal; // push ideal
  317. int cxPrev = _cxIdeal;
  318. SetRect(&rc, 0, 0, sizeDC.cx, sizeDC.cy);
  319. Paint(hdc, &rc, FALSE);
  320. // save the result
  321. hr = S_OK;
  322. if (uMarkUpCalc == MARKUPSIZE_CALCHEIGHT)
  323. {
  324. prc->bottom = prc->top + _cyIdeal;
  325. prc->right = prc->left + _cxIdeal;
  326. }
  327. if (uMarkUpCalc == MARKUPSIZE_CALCWIDTH)
  328. {
  329. // not implemented -- need to do
  330. }
  331. _cyIdeal = cyPrev; // pop ideal
  332. _cxIdeal = cxPrev;
  333. }
  334. }
  335. if (FAILED(hr))
  336. {
  337. SetRect(prc, 0, 0, 0, 0);
  338. }
  339. return hr;
  340. }
  341. STDMETHODIMP CMarkup::SetLinkCursor()
  342. {
  343. SetCursor(GetLinkCursor());
  344. return S_OK;
  345. }
  346. STDMETHODIMP CMarkup::GetFonts(HFONT* phFont, HFONT* phFontUnderline)
  347. {
  348. ASSERTMSG(IsBadWritePtr(phFont, sizeof(*phFont)), "Invalid phFont passed to CMarkup::GetFont");
  349. HRESULT hr = E_FAIL;
  350. *phFont = NULL;
  351. *phFontUnderline = NULL;
  352. if (_hfStatic)
  353. {
  354. LOGFONT lf;
  355. if (GetObject(_hfStatic, sizeof(lf), &lf))
  356. {
  357. *phFont = CreateFontIndirect(&lf);
  358. if (GetObject(_hfLink, sizeof(lf), &lf))
  359. *phFontUnderline = CreateFontIndirect(&lf);
  360. hr = S_OK;
  361. }
  362. }
  363. return hr;
  364. }
  365. HRESULT CMarkup::SetFonts(HFONT hFont, HFONT hFontUnderline)
  366. {
  367. HRESULT hr = S_FALSE;
  368. _bRefreshText = TRUE;
  369. _hfStatic = hFont;
  370. _hfLink = hFontUnderline;
  371. if (_hfLink != NULL && _hfStatic != NULL)
  372. {
  373. hr = S_OK;
  374. }
  375. return hr;
  376. }
  377. STDMETHODIMP CMarkup::HandleEvent(BOOL keys, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  378. {
  379. /* this function handles:
  380. WM_KEYDOWN
  381. WM_BUTTONDOWN
  382. WM_BUTTONUP
  383. WM_MOUSEMOVE
  384. pass it:
  385. keys - TRUE if you want to handle WM_KEYDOWN
  386. others - the params from the WndProc
  387. returns: S_OK if event handled, S_FALSE if no event handled */
  388. HRESULT hr = S_FALSE;
  389. if (!hwnd)
  390. {
  391. hr = E_INVALIDARG;
  392. }
  393. else
  394. {
  395. switch (uMsg)
  396. {
  397. case WM_KEYDOWN:
  398. {
  399. if (keys==TRUE)
  400. {
  401. OnKeyDown((UINT)wParam);
  402. hr = S_OK;
  403. }
  404. break;
  405. }
  406. case WM_LBUTTONDOWN:
  407. {
  408. POINT pt;
  409. MakePoint(lParam, &pt);
  410. OnButtonDown(pt);
  411. hr = S_OK;
  412. break;
  413. }
  414. case WM_LBUTTONUP:
  415. {
  416. POINT pt;
  417. MakePoint(lParam, &pt);
  418. OnButtonUp(pt);
  419. hr = S_OK;
  420. break;
  421. }
  422. case WM_MOUSEMOVE:
  423. {
  424. POINT pt;
  425. UINT pidLink;
  426. MakePoint(lParam, &pt);
  427. if (HitTest(pt, &pidLink) == S_OK)
  428. {
  429. SetLinkCursor();
  430. }
  431. hr = S_OK;
  432. break;
  433. }
  434. }
  435. }
  436. return hr;
  437. }
  438. STDMETHODIMP CMarkup::DrawText(HDC hdcClient, LPCRECT prcClient)
  439. {
  440. HRESULT hr = E_INVALIDARG;
  441. if (prcClient != NULL && hdcClient != NULL)
  442. {
  443. Paint(hdcClient, prcClient);
  444. hr = S_OK;
  445. }
  446. return hr;
  447. }
  448. STDMETHODIMP CMarkup::GetText(BOOL bRaw, LPWSTR pwszText, DWORD *pcchText)
  449. {
  450. // if passed pwszText==NULL, return the number of characters needed in pcchText
  451. if (!pwszText)
  452. {
  453. // for now, always return raw text, as it will always be larger than necessary
  454. *pcchText = lstrlen(_pszCaption)+1;
  455. }
  456. else
  457. {
  458. *pwszText = 0;
  459. if (bRaw)
  460. {
  461. if (_pszCaption)
  462. {
  463. lstrcpyn(pwszText, _pszCaption, *pcchText);
  464. }
  465. }
  466. else
  467. {
  468. for (TEXTBLOCK* pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  469. {
  470. if (pBlock->pszText)
  471. StrCatBuff(pwszText, pBlock->pszText, *pcchText);
  472. }
  473. }
  474. *pcchText = lstrlen(pwszText);
  475. }
  476. return S_OK;
  477. }
  478. STDMETHODIMP CMarkup::SetText(LPCWSTR pwszText)
  479. {
  480. // Note: we don't reparse in the case of same strings
  481. if (pwszText && 0 == lstrcmp(pwszText, _pszCaption))
  482. {
  483. return S_FALSE; // nothing to do.
  484. }
  485. // set the text
  486. _bRefreshText = TRUE;
  487. if (_pszCaption)
  488. {
  489. LocalFree(_pszCaption);
  490. _pszCaption = NULL;
  491. }
  492. _iFocus = INVALID_LINK_INDEX;
  493. if (pwszText && *pwszText)
  494. {
  495. _pszCaption = StrDup(pwszText); // StrDup gets free'd with LocalFree
  496. if (_pszCaption)
  497. {
  498. Parse(pwszText);
  499. }
  500. else
  501. return E_OUTOFMEMORY;
  502. }
  503. return S_OK;
  504. }
  505. STDMETHODIMP CMarkup::SetRenderFlags(UINT uDT)
  506. {
  507. HRESULT hr = E_INVALIDARG;
  508. _bRefreshText = TRUE;
  509. // Set drawtext flags, but filter out unsupported modes
  510. _uDrawTextFlags = uDT;
  511. _uDrawTextFlags &= ~(DT_CALCRECT | DT_INTERNAL | DT_NOCLIP | DT_NOFULLWIDTHCHARBREAK | DT_EDITCONTROL);
  512. // Turn off themedraw
  513. _hTheme = NULL;
  514. hr = S_OK;
  515. return hr;
  516. }
  517. STDMETHODIMP CMarkup::SetThemeRenderFlags(UINT uDT, HTHEME hTheme, int iPartId, int iStateIdNormal, int iStateIdLink)
  518. {
  519. HRESULT hr = SetRenderFlags(uDT);
  520. if (hr == S_OK)
  521. {
  522. // Turn on themedraw
  523. _hTheme = hTheme;
  524. _iThemePartId = iPartId;
  525. _iThemeStateIdNormal = iStateIdNormal;
  526. _iThemeStateIdLink = iStateIdLink;
  527. }
  528. return hr;
  529. }
  530. HRESULT CMarkup::GetRenderFlags(UINT *puDT, HTHEME *phTheme, int *piPartId, int *piStateIdNormal, int *piStateIdLink)
  531. {
  532. *puDT = _uDrawTextFlags;
  533. *phTheme = _hTheme;
  534. *piPartId = _iThemePartId;
  535. *piStateIdNormal = _iThemeStateIdNormal;
  536. *piStateIdLink = _iThemeStateIdLink;
  537. return S_OK;
  538. }
  539. // WM_KEYDOWN handler - exposed as COM
  540. STDMETHODIMP CMarkup::OnKeyDown(UINT virtKey)
  541. {
  542. // returns: S_FALSE unless key handled, then S_OK
  543. // (so if you pass a VK_TAB and it isn't handled, pass on focus)
  544. HRESULT hr = S_FALSE;
  545. switch(virtKey)
  546. {
  547. case VK_TAB:
  548. if (WantTab(&_iFocus))
  549. {
  550. hr = S_OK;
  551. }
  552. _pMarkupCallback->InvalidateRect(NULL);
  553. break;
  554. case VK_RETURN:
  555. case VK_SPACE:
  556. {
  557. TEXTBLOCK * pBlock = FindLink(_iFocus);
  558. if (pBlock)
  559. {
  560. DoNotify (MARKUPMESSAGE_KEYEXECUTE, _iFocus);
  561. hr = S_OK;
  562. }
  563. }
  564. break;
  565. }
  566. return hr;
  567. }
  568. HRESULT CMarkup::OnButtonDown(const POINT pt)
  569. {
  570. // returns: S_FALSE unless button down on link, then S_OK
  571. // note: OnButtonDown no longer turns on capturing all mouse events. Not sure if this will have any negative effect.
  572. HRESULT hr = S_FALSE;
  573. UINT iLink;
  574. if (HitTest(pt, &iLink) == S_OK)
  575. {
  576. hr = S_OK;
  577. SetLinkCursor();
  578. _iFocus = iLink;
  579. _bButtonDown = TRUE;
  580. if (! (IsFocused()))
  581. {
  582. /* this is our way of telling the host we want focus. */
  583. DoNotify (MARKUPMESSAGE_WANTFOCUS, _iFocus);
  584. }
  585. _pMarkupCallback->InvalidateRect(NULL);
  586. }
  587. return hr;
  588. }
  589. HRESULT CMarkup::OnButtonUp(const POINT pt)
  590. {
  591. // returns: S_FALSE unless notification sent, then S_OK
  592. HRESULT hr = S_FALSE;
  593. if (_bButtonDown == TRUE)
  594. {
  595. _bButtonDown = FALSE;
  596. // if the focus link contains the point, we can
  597. // notify the callback of a click event.
  598. INT iHit;
  599. HitTest(pt, (UINT*) &iHit);
  600. TEXTBLOCK* pBlock = FindLink(_iFocus);
  601. if (pBlock &&
  602. (pBlock->state & LIS_ENABLED) != 0 &&
  603. _iFocus == iHit)
  604. {
  605. hr = S_OK;
  606. DoNotify (MARKUPMESSAGE_CLICKEXECUTE, _iFocus);
  607. }
  608. }
  609. return hr;
  610. }
  611. HRESULT CMarkup::HitTest(const POINT pt, UINT* pidLink)
  612. {
  613. // returns S_OK only if pidLink is not INVALID_LINK_INDEX
  614. HRESULT hr = S_FALSE;
  615. *pidLink = INVALID_LINK_INDEX;
  616. // Walk blocks until we find a link rect that contains the point
  617. TEXTBLOCK* pBlock;
  618. for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  619. {
  620. if (IS_LINK(pBlock) && (pBlock->state & LIS_ENABLED)!=0)
  621. {
  622. RECTLISTENTRY* prce;
  623. for(prce = pBlock->rgrle; prce; prce = prce->next)
  624. {
  625. if (PtInRect(&prce->rc, pt))
  626. {
  627. hr = S_OK;
  628. *pidLink = pBlock->iLink;
  629. }
  630. }
  631. }
  632. }
  633. return hr;
  634. }
  635. HRESULT CMarkup::SetLinkText(int iLink, UINT uMarkupLinkText, LPCWSTR pwszText)
  636. {
  637. HRESULT hr = E_INVALIDARG;
  638. TEXTBLOCK* pBlock = FindLink(iLink);
  639. if (pBlock)
  640. {
  641. hr = S_OK;
  642. switch (uMarkupLinkText)
  643. {
  644. case MARKUPLINKTEXT_ID:
  645. lstrcpyn(pBlock->szID, pwszText, ARRAYSIZE(pBlock->szID));
  646. break;
  647. case MARKUPLINKTEXT_URL:
  648. Str_SetPtr(&pBlock->pszUrl, pwszText);
  649. break;
  650. case MARKUPLINKTEXT_TEXT:
  651. Str_SetPtr(&pBlock->pszText, pwszText);
  652. break;
  653. default:
  654. hr = S_FALSE;
  655. break;
  656. }
  657. }
  658. return hr;
  659. }
  660. HRESULT CMarkup::GetLinkText(int iLink, UINT uMarkupLinkText, LPWSTR pwszText, DWORD *pdwCch)
  661. {
  662. HRESULT hr = E_INVALIDARG;
  663. TEXTBLOCK* pBlock = FindLink(iLink);
  664. if (pBlock)
  665. {
  666. LPCTSTR pszSource;
  667. switch (uMarkupLinkText)
  668. {
  669. case MARKUPLINKTEXT_ID:
  670. pszSource = pBlock->szID;
  671. hr = S_OK;
  672. break;
  673. case MARKUPLINKTEXT_URL:
  674. pszSource = pBlock->pszUrl;
  675. hr = S_OK;
  676. break;
  677. case MARKUPLINKTEXT_TEXT:
  678. pszSource = pBlock->pszText;
  679. hr = S_OK;
  680. break;
  681. }
  682. if (hr == S_OK)
  683. {
  684. if (pwszText)
  685. {
  686. if (pszSource == NULL)
  687. pszSource = TEXT("");
  688. lstrcpyn(pwszText, pszSource, *pdwCch);
  689. *pdwCch = lstrlen(pwszText); // fill in number of characters actually copied
  690. }
  691. else
  692. *pdwCch = lstrlen(pszSource)+1; // fill in number of characters needed, including NULL
  693. }
  694. }
  695. return hr;
  696. }
  697. #define MARKUPSTATE_VALID (MARKUPSTATE_ENABLED | MARKUPSTATE_VISITED | MARKUPSTATE_FOCUSED)
  698. HRESULT CMarkup::SetState(int iLink, UINT uStateMask, UINT uState)
  699. {
  700. BOOL bRedraw = FALSE;
  701. HRESULT hr = E_FAIL;
  702. TEXTBLOCK* pBlock = FindLink(iLink);
  703. if (uStateMask & ~MARKUPSTATE_VALID)
  704. return E_INVALIDARG;
  705. if (pBlock)
  706. {
  707. hr = S_OK;
  708. if (uStateMask & MARKUPSTATE_ENABLED)
  709. {
  710. bRedraw |= _AssignBit(MARKUPSTATE_ENABLED, pBlock->state, uState);
  711. int cEnabledLinks = StateCount(MARKUPSTATE_ENABLED, MARKUPSTATE_ENABLED);
  712. }
  713. if (uStateMask & MARKUPSTATE_VISITED)
  714. {
  715. bRedraw |= _AssignBit(MARKUPSTATE_VISITED, pBlock->state, uState);
  716. }
  717. if (uStateMask & MARKUPSTATE_FOCUSED)
  718. {
  719. // Focus assignment is handled differently;
  720. // one and only one link can have focus...
  721. if (uState & MARKUPSTATE_FOCUSED)
  722. {
  723. bRedraw |= (_iFocus != iLink);
  724. _iFocus = iLink;
  725. }
  726. else
  727. {
  728. bRedraw |= (_iFocus == iLink);
  729. _iFocus = INVALID_LINK_INDEX;
  730. }
  731. }
  732. }
  733. if (bRedraw)
  734. {
  735. _pMarkupCallback->InvalidateRect(NULL);
  736. }
  737. return hr;
  738. }
  739. HRESULT CMarkup::GetState(int iLink, UINT uStateMask, UINT* puState)
  740. {
  741. HRESULT hr = E_FAIL;
  742. TEXTBLOCK* pBlock = FindLink(iLink);
  743. if (pBlock && puState != NULL)
  744. {
  745. hr = S_FALSE;
  746. *puState = 0;
  747. if (uStateMask & MARKUPSTATE_FOCUSED)
  748. {
  749. if (_iFocus == iLink)
  750. *puState |= MARKUPSTATE_FOCUSED;
  751. hr = S_OK;
  752. }
  753. if (uStateMask & MARKUPSTATE_ENABLED)
  754. {
  755. if (pBlock->state & MARKUPSTATE_ENABLED)
  756. *puState |= MARKUPSTATE_ENABLED;
  757. hr = S_OK;
  758. }
  759. if (uStateMask & MARKUPSTATE_VISITED)
  760. {
  761. if (pBlock->state & MARKUPSTATE_VISITED)
  762. *puState |= MARKUPSTATE_VISITED;
  763. hr = S_OK;
  764. }
  765. }
  766. return hr;
  767. }
  768. //-------------------------------------------------------------------------//
  769. // CMarkup internal implementation
  770. //-------------------------------------------------------------------------//
  771. void CMarkup::FreeBlocks()
  772. {
  773. for(TEXTBLOCK* pBlock = _rgBlocks; pBlock; )
  774. {
  775. TEXTBLOCK* pNext = pBlock->next;
  776. delete pBlock;
  777. pBlock = pNext;
  778. }
  779. _rgBlocks = NULL;
  780. _cBlocks = _Markups = 0;
  781. }
  782. TEXTBLOCK* CMarkup::CreateBlock(LPCTSTR pszStart, LPCTSTR pszEnd, int iLink)
  783. {
  784. TEXTBLOCK* pBlock = NULL;
  785. int cch = (int)(pszEnd - pszStart) + 1;
  786. if (cch > 0)
  787. {
  788. pBlock = new TEXTBLOCK;
  789. if (pBlock)
  790. {
  791. pBlock->pszText = new TCHAR[cch];
  792. if (pBlock->pszText == NULL)
  793. {
  794. delete pBlock;
  795. pBlock = NULL;
  796. }
  797. else
  798. {
  799. lstrcpyn(pBlock->pszText, pszStart, cch);
  800. pBlock->iLink = iLink;
  801. }
  802. }
  803. }
  804. return pBlock;
  805. }
  806. HCURSOR CMarkup::GetLinkCursor()
  807. {
  808. if (!_hcurHand)
  809. {
  810. _hcurHand = LoadCursor(NULL, IDC_HAND);
  811. }
  812. return _hcurHand;
  813. }
  814. HRESULT CMarkup::_GetNextAnchorTag(LPCTSTR * ppszBlock, int * pcBlocks, LPTSTR pszURL, int cchSize, LPTSTR pszID, int cchID)
  815. {
  816. HRESULT hr = E_FAIL;
  817. LPTSTR pszStartOfTag;
  818. LPTSTR pszIterate = (LPTSTR)*ppszBlock;
  819. LPTSTR pszStartTry = (LPTSTR)*ppszBlock; // We start looking for "<A" at the beginning.
  820. pszURL[0] = 0;
  821. pszID[0] = 0;
  822. // While we find a possible start of a tag.
  823. while ((pszStartOfTag = StrStrI(pszStartTry, LINKTAG1)) != NULL)
  824. {
  825. // See if the rest of the string completes the tag.
  826. pszIterate = pszStartOfTag;
  827. pszStartTry = CharNext(pszStartOfTag); // Do this so the while loop will end when we finish don't find any more "<A".
  828. if (pszIterate[0])
  829. {
  830. pszIterate += cchLINKTAG1; // Skip past the start of the tag.
  831. // Walk thru the Value/Data pairs in the tag
  832. TCHAR szValue[MAX_PATH];
  833. TCHAR szData[L_MAX_URL_LENGTH];
  834. pszIterate = SkipWhite(pszIterate); // SkipWhiteSpace
  835. while ((CH_ENDTAG != pszIterate[0]) &&
  836. SUCCEEDED(_GetNextValueDataPair(&pszIterate, szValue, ARRAYSIZE(szValue), szData, ARRAYSIZE(szData))))
  837. {
  838. if (0 == StrCmpI(szValue, SZ_ATTRIBUTE_HREF))
  839. {
  840. StrCpyN(pszURL, szData, cchSize);
  841. }
  842. else if (0 == StrCmpI(szValue, SZ_ATTRIBUTE_ID))
  843. {
  844. StrCpyN(pszID, szData, cchID);
  845. }
  846. else
  847. {
  848. // We ignore other pairs in order to be back-compat with future
  849. // supported attributes.
  850. }
  851. pszIterate = SkipWhite(pszIterate);
  852. }
  853. if (CH_ENDTAG == pszIterate[0])
  854. {
  855. hr = S_OK;
  856. }
  857. }
  858. if (SUCCEEDED(hr))
  859. {
  860. // Add run between psz1 and pszBlock as static text
  861. if (pszStartOfTag > *ppszBlock)
  862. {
  863. TEXTBLOCK * pBlock = CreateBlock(*ppszBlock, pszStartOfTag, INVALID_LINK_INDEX);
  864. if (NULL != pBlock)
  865. {
  866. Add(pBlock);
  867. (*pcBlocks)++;
  868. }
  869. }
  870. *ppszBlock = CharNext(pszIterate); // Skip past the tag's ">"
  871. // We found an entire tag. Stop looking.
  872. break;
  873. }
  874. else
  875. {
  876. // The "<A" we tried wasn't a valid tag. Are we at the end of the string?
  877. // If not, let's keep looking for other "<A" that may be valid.
  878. }
  879. }
  880. return hr;
  881. }
  882. void CMarkup::Parse(LPCTSTR pszText)
  883. {
  884. TEXTBLOCK* pBlock;
  885. int cBlocks = 0, Markups = 0;
  886. LPCTSTR psz1, psz2, pszBlock;
  887. LPTSTR pszBuf = NULL;
  888. FreeBlocks(); // free existing blocks
  889. pszBuf = (LPTSTR)pszText;
  890. if (!(pszBuf && *pszBuf))
  891. {
  892. goto exit;
  893. }
  894. for(pszBlock = pszBuf; pszBlock && *pszBlock;)
  895. {
  896. TCHAR szURL[L_MAX_URL_LENGTH];
  897. TCHAR szID[MAX_LINKID_TEXT];
  898. // Search for "<a>" tag
  899. if (IsMarkupAllowed() &&
  900. SUCCEEDED(_GetNextAnchorTag(&pszBlock, &cBlocks, szURL, ARRAYSIZE(szURL), szID, ARRAYSIZE(szID))))
  901. {
  902. psz1 = pszBlock; // After _GetNextAnchorTag(), pszBlock points to the char after the start tag.
  903. if (psz1 && *psz1)
  904. {
  905. if ((psz2 = StrStrI(pszBlock, LINKTAG2)) != NULL)
  906. {
  907. if ((pBlock = CreateBlock(psz1, psz2, Markups)) != NULL)
  908. {
  909. if (szURL[0])
  910. {
  911. Str_SetPtr(&pBlock->pszUrl, szURL);
  912. }
  913. if (szID[0])
  914. {
  915. StrCpyN(pBlock->szID, szID, ARRAYSIZE(pBlock->szID));
  916. }
  917. Add(pBlock);
  918. cBlocks++;
  919. Markups++;
  920. }
  921. // safe-skip over tag
  922. for(int i = 0;
  923. i < cchLINKTAG2 && psz2 && *psz2;
  924. i++, psz2 = CharNext(psz2));
  925. pszBlock = psz2;
  926. }
  927. else // syntax error; mark trailing run is static text.
  928. {
  929. psz2 = pszBlock + lstrlen(pszBlock);
  930. if ((pBlock = CreateBlock(psz1, psz2, INVALID_LINK_INDEX)) != NULL)
  931. {
  932. Add(pBlock);
  933. cBlocks++;
  934. }
  935. pszBlock = psz2;
  936. }
  937. }
  938. }
  939. else // no more tags. Mark the last run of static text
  940. {
  941. psz2 = pszBlock + lstrlen(pszBlock);
  942. if ((pBlock = CreateBlock(pszBlock, psz2, INVALID_LINK_INDEX)) != NULL)
  943. {
  944. Add(pBlock);
  945. cBlocks++;
  946. }
  947. pszBlock = psz2;
  948. }
  949. }
  950. ASSERT(cBlocks == _cBlocks);
  951. ASSERT(Markups == _Markups);
  952. exit:
  953. if (!pszText && pszBuf) // delete text buffer if we had alloc'd it.
  954. {
  955. delete [] pszBuf;
  956. }
  957. }
  958. BOOL CMarkup::Add(TEXTBLOCK* pAdd)
  959. {
  960. BOOL bAdded = FALSE;
  961. pAdd->next = NULL;
  962. if (!_rgBlocks)
  963. {
  964. _rgBlocks = pAdd;
  965. bAdded = TRUE;
  966. }
  967. else
  968. {
  969. for(TEXTBLOCK* pBlock = _rgBlocks; pBlock && !bAdded; pBlock = pBlock->next)
  970. {
  971. if (!pBlock->next)
  972. {
  973. pBlock->next = pAdd;
  974. bAdded = TRUE;
  975. }
  976. }
  977. }
  978. if (bAdded)
  979. {
  980. _cBlocks++;
  981. if (IS_LINK(pAdd))
  982. {
  983. _Markups++;
  984. }
  985. }
  986. return bAdded;
  987. }
  988. TEXTBLOCK* CMarkup::FindLink(int iLink) const
  989. {
  990. if (iLink == INVALID_LINK_INDEX)
  991. {
  992. return NULL;
  993. }
  994. for(TEXTBLOCK* pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  995. {
  996. if (IS_LINK(pBlock) && pBlock->iLink == iLink)
  997. return pBlock;
  998. }
  999. return NULL;
  1000. }
  1001. // NOTE: optimizatation! skip drawing loop when called for calcrect!
  1002. void CMarkup::Paint(HDC hdcClient, LPCRECT prcClient, BOOL bDraw)
  1003. {
  1004. HDC hdc = hdcClient;
  1005. COLORREF rgbOld = GetTextColor(hdc); // save text color
  1006. HFONT hFontOld = (HFONT) GetCurrentObject(hdc, OBJ_FONT);
  1007. TEXTBLOCK* pBlock;
  1008. BOOL fFocus = IsFocused();
  1009. if (_cBlocks == 1)
  1010. {
  1011. pBlock = _rgBlocks;
  1012. HFONT hFont = _hfStatic;
  1013. pBlock->FreeRects(); // free hit/focus rects; we're going to recompute.
  1014. if (IS_LINK(pBlock))
  1015. {
  1016. SetTextColor(hdc, (pBlock->state & LIS_ENABLED) ? LINKCOLOR_ENABLED : LINKCOLOR_DISABLED);
  1017. hFont = _hfLink;
  1018. }
  1019. if (hFont)
  1020. {
  1021. SelectObject(hdc, hFont);
  1022. }
  1023. RECT rc = *prcClient;
  1024. int cch = lstrlen(pBlock->pszText);
  1025. ThemedDrawText(hdc, pBlock->pszText, cch, &rc, _uDrawTextFlags | DT_CALCRECT, IS_LINK(pBlock));
  1026. pBlock->AddRect(rc, 0, cch, 0);
  1027. _cyIdeal = RECTHEIGHT(rc);
  1028. _cxIdeal = RECTWIDTH(rc);
  1029. if (bDraw)
  1030. {
  1031. ThemedDrawText(hdc, pBlock->pszText, cch, &rc, _uDrawTextFlags, IS_LINK(pBlock));
  1032. if (fFocus)
  1033. {
  1034. SetTextColor(hdc, rgbOld); // restore text color
  1035. DrawFocusRect(hdc, &rc);
  1036. }
  1037. }
  1038. }
  1039. else
  1040. {
  1041. TEXTMETRIC tm;
  1042. int iLineWidth[255]; // line index offset
  1043. int iLine = 0, // current line index
  1044. cyLine = 0, // line height.
  1045. cyLeading = 0, // internal leading
  1046. _cchOldDrawn = 1; // get out of infinite loop if window too small t-jklann
  1047. RECT rcDraw = *prcClient; // initialize line rect
  1048. _cxIdeal = 0;
  1049. // Initialize iLineWidth (just index 0, others init on use)
  1050. iLineWidth[0]=0;
  1051. // Get font metrics into cyLeading
  1052. if (!_hTheme)
  1053. {
  1054. SelectObject(hdc, _hfLink);
  1055. GetTextMetrics(hdc, &tm);
  1056. if (tm.tmExternalLeading > cyLeading)
  1057. {
  1058. cyLeading = tm.tmExternalLeading;
  1059. }
  1060. SelectObject(hdc, _hfStatic);
  1061. GetTextMetrics(hdc, &tm);
  1062. if (tm.tmExternalLeading > cyLeading)
  1063. {
  1064. cyLeading = tm.tmExternalLeading;
  1065. }
  1066. }
  1067. else
  1068. {
  1069. GetThemeTextMetrics(_hTheme, hdc, _iThemePartId, _iThemeStateIdNormal, &tm);
  1070. if (tm.tmExternalLeading > cyLeading)
  1071. {
  1072. cyLeading = tm.tmExternalLeading;
  1073. }
  1074. GetThemeTextMetrics(_hTheme, hdc, _iThemePartId, _iThemeStateIdLink, &tm);
  1075. if (tm.tmExternalLeading > cyLeading)
  1076. {
  1077. cyLeading = tm.tmExternalLeading;
  1078. }
  1079. }
  1080. // Save us a lot of time if text hasn't changed...
  1081. if (_bRefreshText == TRUE || !EqualRect(&_rRefreshRect, prcClient))
  1082. {
  1083. UINT uDrawTextCalc = _uDrawTextFlags | DT_CALCRECT | DT_SINGLELINE;
  1084. uDrawTextCalc &= ~(DT_CENTER | DT_LEFT | DT_RIGHT | DT_VCENTER | DT_BOTTOM);
  1085. BOOL bKillingLine = FALSE;
  1086. // For each block of text (calculation loop)...
  1087. for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  1088. {
  1089. // font select (so text will draw correctly)
  1090. if (!_hTheme)
  1091. {
  1092. BOOL bLink = IS_LINK(pBlock);
  1093. HFONT hFont = bLink ? _hfLink : _hfStatic;
  1094. if (hFont)
  1095. {
  1096. SelectObject(hdc, hFont);
  1097. }
  1098. }
  1099. int cchDraw = lstrlen(pBlock->pszText); // chars to draw, this block
  1100. int cchDrawn = 0; // chars to draw, this block
  1101. LPTSTR pszText = &pBlock->pszText[cchDrawn];
  1102. LPTSTR pszTextOriginal = &pBlock->pszText[cchDrawn];
  1103. pBlock->FreeRects(); // free hit/focus rects; we're going to recompute.
  1104. // while text remains in this block...
  1105. _cchOldDrawn = 1;
  1106. while(cchDraw > 0 && !((_uDrawTextFlags & DT_SINGLELINE) && (iLine>0)))
  1107. {
  1108. // compute line height and maximum text width to rcBlock
  1109. RECT rcBlock;
  1110. int cchTry = cchDraw;
  1111. int cchTrySave = cchTry;
  1112. int cchBreak = 0;
  1113. int iLineBreakSize;
  1114. BOOL bRemoveBreak = FALSE;
  1115. BOOL bRemoveLineBreak = FALSE;
  1116. RECT rcCalc;
  1117. CopyRect(&rcCalc, &rcDraw);
  1118. // support multiline text phrases
  1119. bRemoveLineBreak = _FindFirstLineBreak(pszText, cchTry, &cchBreak, &iLineBreakSize);
  1120. if (bRemoveLineBreak)
  1121. {
  1122. cchTry = cchBreak;
  1123. }
  1124. // find out how much we can fit on this line within the rectangle
  1125. // calc rect breaking at breakpoints (or -1 char) until rectangle fits inside drawing rect.
  1126. for(;;)
  1127. {
  1128. // choose codepath: themes or normal drawtext (no exttextout path)
  1129. // now we use drawtext to preserve formatting options (tabs/underlines)
  1130. ThemedDrawText(hdc, pszText, cchTry, &rcCalc, uDrawTextCalc, IS_LINK(pBlock));
  1131. cyLine = RECTHEIGHT(rcCalc);
  1132. // special case: support \n as only character on line (we need a valid line width & length)
  1133. if (cchTry == 0 && bRemoveLineBreak==TRUE)
  1134. {
  1135. // these two lines adjust drawing to within a valid range when the \n is barely cut off
  1136. rcCalc.left = prcClient->left;
  1137. rcCalc.right = prcClient->right;
  1138. cyLine = ThemedDrawText(hdc, TEXT("a"), 1, &rcCalc, uDrawTextCalc, IS_LINK(pBlock));
  1139. // the "a" could be any text. It exists because passing "\n" to DrawText doesn't return a valid line height.
  1140. rcCalc.right = rcCalc.left;
  1141. }
  1142. if (RECTWIDTH(rcCalc) > RECTWIDTH(rcDraw))
  1143. {
  1144. // too big
  1145. cchTrySave = cchTry;
  1146. BOOL fBreak = _FindLastBreakChar(pszText, cchTry, tm.tmBreakChar, &cchTry, &bRemoveBreak);
  1147. // case that our strings ends with a valid break char
  1148. if (cchTrySave == cchTry && cchTry > 0)
  1149. {
  1150. cchTry--;
  1151. }
  1152. // this code allows character wrapping instead of just word wrapping.
  1153. // keep it in case we want to change the behavior.
  1154. if (!fBreak && prcClient->left == rcDraw.left)
  1155. {
  1156. // no break character found, so force a break if we can.
  1157. if (cchTrySave > 0)
  1158. {
  1159. cchTry = cchTrySave - 1;
  1160. }
  1161. }
  1162. if (cchTry > 0)
  1163. {
  1164. continue;
  1165. }
  1166. }
  1167. break;
  1168. }
  1169. // if our line break got clipped, turn off line break..
  1170. if (bRemoveLineBreak && cchBreak > cchTry)
  1171. {
  1172. bRemoveLineBreak = FALSE;
  1173. }
  1174. // Count the # chars drawn, account for clipping
  1175. cchDrawn = cchTry;
  1176. if ((cchTry < cchDraw) && bRemoveLineBreak)
  1177. {
  1178. cchDrawn+=iLineBreakSize;
  1179. }
  1180. // DT_WORDBREAK off support
  1181. // Kill this line if bKillingLine is true; i.e. pretend we drew it, but do nothing
  1182. if (bKillingLine)
  1183. {
  1184. pszText += cchDrawn;
  1185. }
  1186. else
  1187. {
  1188. // initialize drawing rectangle and block rectangle
  1189. SetRect(&rcBlock, rcCalc.left , 0, rcCalc.right , RECTHEIGHT(rcCalc));
  1190. rcDraw.right = min(rcDraw.left + RECTWIDTH(rcBlock), prcClient->right);
  1191. rcDraw.bottom = rcDraw.top + cyLine;
  1192. // Add rectangle to block's list and update line width and ideal x width
  1193. // (Only if we're actually going to draw this line, though)
  1194. if (cchTry)
  1195. {
  1196. // DT_SINGLELINE support
  1197. if (!((_uDrawTextFlags & DT_SINGLELINE) == DT_SINGLELINE) || (iLine == 0))
  1198. {
  1199. pBlock->AddRect(rcDraw, (UINT) (pszText-pszTextOriginal), cchDrawn, iLine);
  1200. }
  1201. iLineWidth[iLine] = max(iLineWidth[iLine], rcDraw.left - prcClient->left + RECTWIDTH(rcBlock));
  1202. _cxIdeal = max(_cxIdeal, iLineWidth[iLine]);
  1203. }
  1204. if (cchTry < cchDraw) // we got clipped
  1205. {
  1206. if (bRemoveBreak)
  1207. {
  1208. cchDrawn++;
  1209. }
  1210. pszText += cchDrawn;
  1211. // advance to next line and init next linewidth
  1212. iLine++;
  1213. iLineWidth[iLine]=0;
  1214. // t-jklann 6/00: added support for line wrap in displaced text (left&top)
  1215. rcDraw.left = prcClient->left;
  1216. if (!(_uDrawTextFlags & DT_SINGLELINE))
  1217. {
  1218. rcDraw.top = prcClient->top + iLine * cyLine;
  1219. }
  1220. else
  1221. {
  1222. rcDraw.top = prcClient->top;
  1223. }
  1224. rcDraw.bottom = rcDraw.top + cyLine + cyLeading;
  1225. rcDraw.right = prcClient->right;
  1226. }
  1227. else // we were able to draw the entire text
  1228. {
  1229. // adjust drawing rectangle
  1230. rcDraw.left += RECTWIDTH(rcBlock);
  1231. rcDraw.right = prcClient->right;
  1232. }
  1233. // Update ideal y width
  1234. _cyIdeal = rcDraw.bottom - prcClient->top;
  1235. }
  1236. // support for: DT_WORDBREAK turned off
  1237. // Kill the next line if we got clipped and there's no wordbreak
  1238. if (((_uDrawTextFlags & DT_WORDBREAK) != DT_WORDBREAK))
  1239. {
  1240. if (cchTry < cchDraw )
  1241. {
  1242. bKillingLine = TRUE;
  1243. }
  1244. if (bRemoveLineBreak)
  1245. {
  1246. bKillingLine = FALSE;
  1247. }
  1248. }
  1249. // Update calculation of chars drawn
  1250. cchDraw -= cchDrawn;
  1251. // bug catch: get out if we really can't draw
  1252. if (_cchOldDrawn == 0 && cchDrawn == 0)
  1253. {
  1254. iLine--;
  1255. rcDraw.top = prcClient->top + iLine * cyLine;
  1256. cchDraw = 0;
  1257. }
  1258. _cchOldDrawn = cchDrawn;
  1259. }
  1260. }
  1261. // Handle justification issues (DT_VCENTER, DT_TOP, DT_BOTTOM)
  1262. if (((_uDrawTextFlags & DT_SINGLELINE) == DT_SINGLELINE) &&
  1263. ((_uDrawTextFlags & (DT_VCENTER | DT_BOTTOM)) > 0))
  1264. {
  1265. // Calc offset
  1266. int cyOffset = 0;
  1267. if ((_uDrawTextFlags & DT_VCENTER) == DT_VCENTER)
  1268. {
  1269. cyOffset = (RECTHEIGHT(*prcClient) - _cyIdeal)/2;
  1270. }
  1271. if ((_uDrawTextFlags & DT_BOTTOM) == DT_BOTTOM)
  1272. {
  1273. cyOffset = (RECTHEIGHT(*prcClient) - _cyIdeal);
  1274. }
  1275. // Offset every rectangle
  1276. for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  1277. {
  1278. for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
  1279. {
  1280. prce->rc.top += cyOffset;
  1281. prce->rc.bottom += cyOffset;
  1282. }
  1283. }
  1284. }
  1285. // Handle justification issues (DT_CENTER, DT_LEFT, DT_RIGHT)
  1286. if (((_uDrawTextFlags & DT_CENTER) == DT_CENTER) || ((_uDrawTextFlags & DT_RIGHT) == DT_RIGHT))
  1287. {
  1288. // Step 1: turn iLineWidth into an offset vector
  1289. for (int i = 0; i <= iLine; i++)
  1290. {
  1291. if (RECTWIDTH(*prcClient) > iLineWidth[i])
  1292. {
  1293. if ((_uDrawTextFlags & DT_CENTER) == DT_CENTER)
  1294. {
  1295. iLineWidth[i] = (RECTWIDTH(*prcClient)-iLineWidth[i])/2;
  1296. }
  1297. if ((_uDrawTextFlags & DT_RIGHT) == DT_RIGHT)
  1298. {
  1299. iLineWidth[i] = (RECTWIDTH(*prcClient)-iLineWidth[i]);
  1300. }
  1301. }
  1302. else
  1303. {
  1304. iLineWidth[i] = 0;
  1305. }
  1306. }
  1307. // Step 2: offset every rect-angle
  1308. for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  1309. {
  1310. for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
  1311. {
  1312. prce->rc.left += iLineWidth[prce->uLineNumber];
  1313. prce->rc.right += iLineWidth[prce->uLineNumber];
  1314. }
  1315. }
  1316. }
  1317. CopyRect(&_rRefreshRect, prcClient);
  1318. _bRefreshText = FALSE;
  1319. }
  1320. if (bDraw)
  1321. {
  1322. // For each block of text (drawing loop)...
  1323. UINT uDrawTextDraw = _uDrawTextFlags | DT_SINGLELINE;
  1324. uDrawTextDraw &= ~(DT_CENTER | DT_LEFT | DT_RIGHT | DT_CALCRECT | DT_VCENTER | DT_BOTTOM);
  1325. LRESULT dwCustomDraw=0;
  1326. _pMarkupCallback->OnCustomDraw(CDDS_PREPAINT, hdc, prcClient, 0, 0, &dwCustomDraw);
  1327. for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  1328. {
  1329. BOOL bLink = IS_LINK(pBlock);
  1330. BOOL bEnabled = pBlock->state & LIS_ENABLED;
  1331. // font select
  1332. if (!_hTheme)
  1333. {
  1334. HFONT hFont = bLink ? _hfLink : _hfStatic;
  1335. if (hFont)
  1336. {
  1337. SelectObject(hdc, hFont);
  1338. }
  1339. }
  1340. // initialize foreground color
  1341. if (!_hTheme)
  1342. {
  1343. if (bLink)
  1344. {
  1345. SetTextColor(hdc, bEnabled ? LINKCOLOR_ENABLED : LINKCOLOR_DISABLED);
  1346. }
  1347. else
  1348. {
  1349. SetTextColor(hdc, rgbOld); // restore text color
  1350. }
  1351. }
  1352. if (dwCustomDraw & CDRF_NOTIFYITEMDRAW)
  1353. {
  1354. _pMarkupCallback->OnCustomDraw(CDDS_ITEMPREPAINT, hdc, NULL, pBlock->iLink, bEnabled ? CDIS_DEFAULT : CDIS_DISABLED, NULL);
  1355. }
  1356. // draw the text
  1357. LPTSTR pszText = pBlock->pszText;
  1358. LPTSTR pszTextOriginal = pBlock->pszText;
  1359. for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
  1360. {
  1361. RECT rc = prce->rc;
  1362. pszText = pszTextOriginal + prce->uCharStart;
  1363. ThemedDrawText(hdc, pszText, prce->uCharCount, &rc, uDrawTextDraw, IS_LINK(pBlock));
  1364. }
  1365. // Draw focus rect(s)
  1366. if (fFocus && pBlock->iLink == _iFocus && IS_LINK(pBlock))
  1367. {
  1368. SetTextColor(hdc, rgbOld); // restore text color
  1369. for(RECTLISTENTRY* prce = pBlock->rgrle; prce; prce = prce->next)
  1370. {
  1371. DrawFocusRect(hdc, &prce->rc);
  1372. }
  1373. }
  1374. if (dwCustomDraw & CDRF_NOTIFYITEMDRAW)
  1375. {
  1376. _pMarkupCallback->OnCustomDraw(CDDS_ITEMPOSTPAINT, hdc, NULL, pBlock->iLink, bEnabled ? CDIS_DEFAULT : CDIS_DISABLED, NULL);
  1377. }
  1378. }
  1379. if (dwCustomDraw & CDRF_NOTIFYPOSTPAINT)
  1380. {
  1381. _pMarkupCallback->OnCustomDraw(CDDS_POSTPAINT, hdc, NULL, 0, 0, NULL);
  1382. }
  1383. }
  1384. }
  1385. SetTextColor(hdc, rgbOld); // restore text color
  1386. if (hFontOld)
  1387. {
  1388. SelectObject(hdc, hFontOld);
  1389. }
  1390. }
  1391. int CMarkup::GetNextEnabledLink(int iStart, int nDir) const
  1392. {
  1393. ASSERT(-1 == nDir || 1 == nDir);
  1394. if (_Markups > 0)
  1395. {
  1396. if (INVALID_LINK_INDEX == iStart)
  1397. {
  1398. iStart = nDir > 0 ? -1 : _Markups;
  1399. }
  1400. for(iStart += nDir; iStart >= 0; iStart += nDir)
  1401. {
  1402. TEXTBLOCK* pBlock = FindLink(iStart);
  1403. if (!pBlock)
  1404. {
  1405. return INVALID_LINK_INDEX;
  1406. }
  1407. if (pBlock->state & LIS_ENABLED)
  1408. {
  1409. ASSERT(iStart == pBlock->iLink);
  1410. return iStart;
  1411. }
  1412. }
  1413. }
  1414. return INVALID_LINK_INDEX;
  1415. }
  1416. int CMarkup::StateCount(DWORD dwStateMask, DWORD dwState) const
  1417. {
  1418. TEXTBLOCK* pBlock;
  1419. int cnt = 0;
  1420. for(pBlock = _rgBlocks; pBlock; pBlock = pBlock->next)
  1421. {
  1422. if (IS_LINK(pBlock) &&
  1423. (pBlock->state & dwStateMask) == dwState)
  1424. {
  1425. cnt++;
  1426. }
  1427. }
  1428. return cnt;
  1429. }
  1430. BOOL CMarkup::WantTab(int* biFocus) const
  1431. {
  1432. int nDir = TESTKEYSTATE(VK_SHIFT) ? -1 : 1;
  1433. int iFocus = GetNextEnabledLink(_iFocus, nDir);
  1434. if (INVALID_LINK_INDEX != iFocus)
  1435. {
  1436. if (biFocus)
  1437. {
  1438. *biFocus = iFocus;
  1439. }
  1440. return TRUE;
  1441. }
  1442. else
  1443. {
  1444. // if we can't handle the focus, prepare for the next round
  1445. //iFocus = GetNextEnabledLink(-1, nDir);
  1446. *biFocus = -1;
  1447. return FALSE;
  1448. }
  1449. }
  1450. void CMarkup::AssignTabFocus(int nDirection)
  1451. {
  1452. if (_Markups)
  1453. {
  1454. if (0 == nDirection)
  1455. {
  1456. if (INVALID_LINK_INDEX != _iFocus)
  1457. {
  1458. return;
  1459. }
  1460. nDirection = 1;
  1461. }
  1462. _iFocus = GetNextEnabledLink(_iFocus, nDirection);
  1463. }
  1464. }
  1465. //-------------------------------------------------------------------------//
  1466. TEXTBLOCK::TEXTBLOCK()
  1467. : iLink(INVALID_LINK_INDEX),
  1468. next(NULL),
  1469. state(LIS_ENABLED),
  1470. pszText(NULL),
  1471. pszUrl(NULL),
  1472. rgrle(NULL)
  1473. {
  1474. *szID = 0;
  1475. }
  1476. TEXTBLOCK::~TEXTBLOCK()
  1477. {
  1478. // free block text
  1479. Str_SetPtr(&pszText, NULL);
  1480. Str_SetPtr(&pszUrl, NULL);
  1481. // free rectangle(s)
  1482. FreeRects();
  1483. }
  1484. void TEXTBLOCK::AddRect(const RECT& rc, UINT uMyCharStart, UINT uMyCharCount, UINT uMyLineNumber)
  1485. {
  1486. RECTLISTENTRY* prce;
  1487. if ((prce = new RECTLISTENTRY) != NULL)
  1488. {
  1489. CopyRect(&(prce->rc), &rc);
  1490. prce->next = NULL;
  1491. prce->uCharStart = uMyCharStart;
  1492. prce->uCharCount = uMyCharCount;
  1493. prce->uLineNumber = uMyLineNumber;
  1494. }
  1495. if (rgrle == NULL)
  1496. {
  1497. rgrle = prce;
  1498. }
  1499. else
  1500. {
  1501. for(RECTLISTENTRY* p = rgrle; p; p = p->next)
  1502. {
  1503. if (p->next == NULL)
  1504. {
  1505. p->next = prce;
  1506. break;
  1507. }
  1508. }
  1509. }
  1510. }
  1511. void TEXTBLOCK::FreeRects()
  1512. {
  1513. for(RECTLISTENTRY* p = rgrle; p;)
  1514. {
  1515. RECTLISTENTRY* next = p->next;
  1516. delete p;
  1517. p = next;
  1518. }
  1519. rgrle = NULL;
  1520. }
  1521. //-------------------------------------------------------------------------//
  1522. // t-jklann 6/00: added these formerly global methods to the CMarkup class
  1523. // Returns a pointer to the first non-whitespace character in a string.
  1524. LPTSTR CMarkup::SkipWhite(LPTSTR lpsz)
  1525. {
  1526. /* prevent sign extension in case of DBCS */
  1527. while (*lpsz && (TUCHAR)*lpsz <= TEXT(' '))
  1528. lpsz++;
  1529. return(lpsz);
  1530. }
  1531. BOOL CMarkup::_AssignBit(const DWORD dwBit, DWORD& dwDest, const DWORD dwSrc) // returns TRUE if changed
  1532. {
  1533. if (((dwSrc & dwBit) != 0) != ((dwDest & dwBit) != 0))
  1534. {
  1535. if (((dwSrc & dwBit) != 0))
  1536. {
  1537. dwDest |= dwBit;
  1538. }
  1539. else
  1540. {
  1541. dwDest &= ~dwBit;
  1542. }
  1543. return TRUE;
  1544. }
  1545. return FALSE;
  1546. }
  1547. BOOL CMarkup::IsStringAlphaNumeric(LPCTSTR pszString)
  1548. {
  1549. while (pszString[0])
  1550. {
  1551. if (!IsCharAlphaNumeric(pszString[0]))
  1552. {
  1553. return FALSE;
  1554. }
  1555. pszString = CharNext(pszString);
  1556. }
  1557. return TRUE;
  1558. }
  1559. // We are looking for the next value/data pair. Formated like this:
  1560. // VALUE="<data>"
  1561. HRESULT CMarkup::_GetNextValueDataPair(LPTSTR * ppszBlock, LPTSTR pszValue, int cchValue, LPTSTR pszData, int cchData)
  1562. {
  1563. HRESULT hr = E_FAIL;
  1564. LPCTSTR pszIterate = *ppszBlock;
  1565. LPCTSTR pszEquals = StrStr(pszIterate, TEXT("=\""));
  1566. if (pszEquals)
  1567. {
  1568. cchValue = MIN(cchValue, (pszEquals - *ppszBlock + 1));
  1569. StrCpyN(pszValue, *ppszBlock, cchValue);
  1570. pszEquals += 2; // Skip past the ="
  1571. if (IsStringAlphaNumeric(pszValue))
  1572. {
  1573. LPTSTR pszEndOfData = StrChr(pszEquals, TEXT('\"'));
  1574. if (pszEndOfData)
  1575. {
  1576. cchData = MIN(cchData, (pszEndOfData - pszEquals + 1));
  1577. StrCpyN(pszData, pszEquals, cchData);
  1578. *ppszBlock = CharNext(pszEndOfData);
  1579. hr = S_OK;
  1580. }
  1581. }
  1582. }
  1583. return hr;
  1584. }
  1585. //-------------------------------------------------------------------------
  1586. //
  1587. // IsFEChar - Detects East Asia FullWidth character.
  1588. // borrowed from UserIsFullWidth in ntuser\rtl\drawtext.c
  1589. //
  1590. BOOL IsFEChar(WCHAR wChar)
  1591. {
  1592. static struct
  1593. {
  1594. WCHAR wchStart;
  1595. WCHAR wchEnd;
  1596. } rgFullWidthUnicodes[] =
  1597. {
  1598. { 0x4E00, 0x9FFF }, // CJK_UNIFIED_IDOGRAPHS
  1599. { 0x3040, 0x309F }, // HIRAGANA
  1600. { 0x30A0, 0x30FF }, // KATAKANA
  1601. { 0xAC00, 0xD7A3 } // HANGUL
  1602. };
  1603. BOOL fRet = FALSE;
  1604. //
  1605. // Early out for ASCII. If the character < 0x0080, it should be a halfwidth character.
  1606. //
  1607. if (wChar >= 0x0080)
  1608. {
  1609. int i;
  1610. //
  1611. // Scan FullWdith definition table... most of FullWidth character is
  1612. // defined here... this is faster than call NLS API.
  1613. //
  1614. for (i = 0; i < ARRAYSIZE(rgFullWidthUnicodes); i++)
  1615. {
  1616. if ((wChar >= rgFullWidthUnicodes[i].wchStart) &&
  1617. (wChar <= rgFullWidthUnicodes[i].wchEnd))
  1618. {
  1619. fRet = TRUE;
  1620. break;
  1621. }
  1622. }
  1623. }
  1624. return fRet;
  1625. }
  1626. //-------------------------------------------------------------------------
  1627. BOOL IsFEString(IN LPCTSTR psz, IN int cchText)
  1628. {
  1629. for(int i=0; i < cchText; i++)
  1630. {
  1631. if (IsFEChar(psz[i]))
  1632. {
  1633. return TRUE;
  1634. }
  1635. }
  1636. return FALSE;
  1637. }
  1638. //-------------------------------------------------------------------------
  1639. int CMarkup::_IsLineBreakChar(LPCTSTR psz, int ich, TCHAR chBreak, OUT BOOL* pbRemove, BOOL fIgnoreSpace)
  1640. {
  1641. LPTSTR pch;
  1642. *pbRemove = FALSE;
  1643. ASSERT(psz != NULL)
  1644. ASSERT(psz[ich] != 0);
  1645. // Try caller-provided break character (assumed a 'remove' break char).
  1646. if (!(fIgnoreSpace && (chBreak == 0x20)) && (psz[ich] == chBreak))
  1647. {
  1648. *pbRemove = TRUE;
  1649. return ich;
  1650. }
  1651. #define MAX_LINEBREAK_RESOURCE 128
  1652. static TCHAR _szBreakRemove [MAX_LINEBREAK_RESOURCE] = {0};
  1653. static TCHAR _szBreakPreserve [MAX_LINEBREAK_RESOURCE] = {0};
  1654. #define LOAD_BREAKCHAR_RESOURCE(nIDS, buff) \
  1655. if (0==*buff) { LoadString(HINST_THISDLL, nIDS, buff, ARRAYSIZE(buff)); }
  1656. // Try 'remove' break chars
  1657. LOAD_BREAKCHAR_RESOURCE(IDS_LINEBREAK_REMOVE, _szBreakRemove);
  1658. for (pch = _szBreakRemove; *pch; pch = CharNext(pch))
  1659. {
  1660. if (!(fIgnoreSpace && (*pch == 0x20)) && (psz[ich] == *pch))
  1661. {
  1662. *pbRemove = TRUE;
  1663. return ich;
  1664. }
  1665. }
  1666. // Try 'preserve prior' break chars:
  1667. LOAD_BREAKCHAR_RESOURCE(IDS_LINEBREAK_PRESERVE, _szBreakPreserve);
  1668. for(pch = _szBreakPreserve; *pch; pch = CharNext(pch))
  1669. {
  1670. if (!(fIgnoreSpace && (*pch == 0x20)) && (psz[ich] == *pch))
  1671. {
  1672. return ++ich;
  1673. }
  1674. }
  1675. return -1;
  1676. }
  1677. //-------------------------------------------------------------------------
  1678. BOOL CMarkup::_FindLastBreakChar(
  1679. IN LPCTSTR pszText,
  1680. IN int cchText,
  1681. IN TCHAR chBreak, // official break char (from TEXTMETRIC).
  1682. OUT int* piLast,
  1683. OUT BOOL* pbRemove)
  1684. {
  1685. *piLast = 0;
  1686. *pbRemove = FALSE;
  1687. // 338710: Far East writing doesn't use the space character to separate
  1688. // words, ignore the space char as a possible line delimiter.
  1689. BOOL fIgnoreSpace = IsFEString(pszText, cchText);
  1690. for(int i = cchText-1; i >= 0; i--)
  1691. {
  1692. int ich = _IsLineBreakChar(pszText, i, chBreak, pbRemove, fIgnoreSpace);
  1693. if (ich >= 0)
  1694. {
  1695. *piLast = ich;
  1696. return TRUE;
  1697. }
  1698. }
  1699. return FALSE;
  1700. }
  1701. BOOL CMarkup::_FindFirstLineBreak(
  1702. IN LPCTSTR pszText,
  1703. IN int cchText,
  1704. OUT int* piLast,
  1705. OUT int* piLineBreakSize)
  1706. {
  1707. *piLast = 0;
  1708. *piLineBreakSize = 0;
  1709. // Searches for \n, \r, or \r\n
  1710. for(int i = 0; i < cchText; i++)
  1711. {
  1712. if ((*(pszText+i)=='\n') || (*(pszText+i)=='\r'))
  1713. {
  1714. *piLast = i;
  1715. if ((*(pszText+i)=='\r') && (*(pszText+i+1)=='\n'))
  1716. {
  1717. *piLineBreakSize = 2;
  1718. }
  1719. else
  1720. {
  1721. *piLineBreakSize = 1;
  1722. }
  1723. return TRUE;
  1724. }
  1725. }
  1726. return FALSE;
  1727. }
  1728. void CMarkup::DoNotify(int nCode, int iLink)
  1729. {
  1730. _pMarkupCallback->Notify(nCode, iLink);
  1731. }
  1732. int CMarkup::ThemedDrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat, BOOL bLink)
  1733. {
  1734. if (!_hTheme)
  1735. {
  1736. // NORMAL DRAWTEXT
  1737. return ::DrawText(hdc, lpString, nCount, lpRect, uFormat);
  1738. }
  1739. else
  1740. {
  1741. int iThemeStateId;
  1742. iThemeStateId = bLink ? _iThemeStateIdLink : _iThemeStateIdNormal;
  1743. if (uFormat & DT_CALCRECT)
  1744. {
  1745. // THEME CALC RECT SUPPORT
  1746. LPRECT lpBoundRect = lpRect;
  1747. if (RECTWIDTH(*lpRect)==0 && RECTHEIGHT(*lpRect)==0)
  1748. {
  1749. lpBoundRect = NULL;
  1750. }
  1751. GetThemeTextExtent(_hTheme, hdc, _iThemePartId, iThemeStateId, lpString, nCount, uFormat, lpBoundRect, lpRect);
  1752. }
  1753. else
  1754. {
  1755. // THEME DRAW SUPPORT
  1756. DrawThemeText(_hTheme, hdc, _iThemePartId, iThemeStateId, lpString, nCount, uFormat, 0, lpRect);
  1757. }
  1758. return (RECTHEIGHT(*lpRect));
  1759. }
  1760. }