Leaked source code of windows server 2003
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.

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