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.

2036 lines
51 KiB

  1. /*++
  2. Copyright (c) 1997 Microsoft Corporation
  3. Module Name:
  4. textview.c
  5. Abstract:
  6. The code in this module implements a simple text view control
  7. that supports a few HTML-style tags. The caller sets the control
  8. font with WM_SETFONT, then adds text line-by-line by sending
  9. WMX_ADDLINE messages, and finally sends a WMX_GOTO to jump to
  10. a specific bookmark.
  11. WMX_ADDLINE
  12. wParam: Specifies a pointer to a LINEATTRIBS struct, or
  13. NULL if the last line's attributes are to be
  14. used. (If no lines exist and wParam is NULL,
  15. default attributes are used.)
  16. lParam: Specifes a pointer to the text to add.
  17. Return Value: The number of bytes of the lParam string
  18. that are visible in the control.
  19. The text can contain the following HTML-style tags:
  20. <B> - Turns on BOLD
  21. </B> - Turns off BOLD
  22. <U> - Turns on UNDERLINE
  23. </U> - Turns off UNDERLINE
  24. <UL> - Indents text
  25. </UL> - Unindents text
  26. <HR> - A separator line
  27. <A name=bookmark> - Specifies a bookmark (see WMX_GOTO)
  28. <BR> - Adds a line break
  29. Line-feed and carriage-return characters are interpreted as <BR>.
  30. WMX_GOTO
  31. wParam: Unused
  32. lParam: Specifies a pointer to the string specifing the bookmark
  33. to jump to.
  34. Return Value: Always zero.
  35. Author:
  36. Jim Schmidt (jimschm) 28-Oct-1997
  37. Revision History:
  38. jimschm 23-Sep-1998 IDC_HAND collision fix (now IDC_OUR_HAND)
  39. --*/
  40. #include "pch.h"
  41. #include "uip.h"
  42. #include <shellapi.h>
  43. UINT g_HangingIndentPixels;
  44. PCTSTR g_HangingIndentString = TEXT(" ");
  45. UINT g_FarEastFudgeFactor;
  46. extern BOOL g_Terminated;
  47. #define COMMAND_BOLD 1
  48. #define COMMAND_BOLD_END 2
  49. #define COMMAND_UNDERLINE 3
  50. #define COMMAND_UNDERLINE_END 4
  51. #define COMMAND_HORZ_RULE 5
  52. #define COMMAND_ANCHOR 6
  53. #define COMMAND_ANCHOR_END 7
  54. #define COMMAND_INDENT 8
  55. #define COMMAND_UNINDENT 9
  56. #define COMMAND_LIST_ITEM 10
  57. #define COMMAND_LIST_ITEM_END 11
  58. #define COMMAND_LINE_BREAK 12
  59. #define COMMAND_WHITESPACE 13
  60. #define COMMAND_PARAGRAPH 14
  61. #define COMMAND_ESCAPED_CHAR 15
  62. typedef struct {
  63. BYTE Command;
  64. PCTSTR Text;
  65. UINT TextLen;
  66. } TAG, *PTAG;
  67. TAG g_TagList[] = {
  68. {COMMAND_BOLD, TEXT("B"), 0},
  69. {COMMAND_BOLD_END, TEXT("/B"), 0},
  70. {COMMAND_UNDERLINE, TEXT("U"), 0},
  71. {COMMAND_UNDERLINE_END, TEXT("/U"), 0},
  72. {COMMAND_HORZ_RULE, TEXT("HR"), 0},
  73. {COMMAND_ANCHOR, TEXT("A"), 0},
  74. {COMMAND_ANCHOR_END, TEXT("/A"), 0},
  75. {COMMAND_INDENT, TEXT("UL"), 0},
  76. {COMMAND_UNINDENT, TEXT("/UL"), 0},
  77. {COMMAND_LIST_ITEM, TEXT("LI"), 0},
  78. {COMMAND_LIST_ITEM_END, TEXT("/LI"), 0},
  79. {COMMAND_LINE_BREAK, TEXT("BR"), 0},
  80. {COMMAND_PARAGRAPH, TEXT("P"), 0},
  81. {0, NULL, 0}
  82. };
  83. #define MAX_URL 4096
  84. #define MAX_BOOKMARK 128
  85. typedef struct {
  86. RECT Rect;
  87. TCHAR Url[MAX_URL];
  88. TCHAR Bookmark[MAX_BOOKMARK];
  89. } HOTLINK, *PHOTLINK;
  90. PCTSTR
  91. pParseHtmlArgs (
  92. IN PCTSTR Start,
  93. IN PCTSTR End
  94. )
  95. {
  96. PTSTR MultiSz;
  97. PCTSTR ArgStart;
  98. PCTSTR ArgEnd;
  99. PTSTR p;
  100. BOOL Quotes;
  101. UINT Size;
  102. CHARTYPE ch;
  103. Size = (End - Start) * 2;
  104. MultiSz = AllocPathString (Size);
  105. if (!MultiSz) {
  106. return NULL;
  107. }
  108. p = MultiSz;
  109. do {
  110. while (*Start && _istspace (_tcsnextc (Start))) {
  111. Start = _tcsinc (Start);
  112. }
  113. if (*Start == 0) {
  114. break;
  115. }
  116. ArgStart = Start;
  117. ArgEnd = ArgStart;
  118. Quotes = FALSE;
  119. while (*ArgEnd) {
  120. if (_tcsnextc (ArgEnd) == TEXT('\"')) {
  121. Quotes = !Quotes;
  122. } else if (!Quotes) {
  123. ch = _tcsnextc (ArgEnd);
  124. if (_istspace (ch) || ch == TEXT('>')) {
  125. break;
  126. }
  127. }
  128. ArgEnd = _tcsinc (ArgEnd);
  129. }
  130. if (ArgEnd > ArgStart) {
  131. StringCopyAB (p, ArgStart, ArgEnd);
  132. p = GetEndOfString (p) + 1;
  133. }
  134. Start = ArgEnd;
  135. } while (*Start && _tcsnextc (Start) != TEXT('>'));
  136. *p = 0;
  137. MYASSERT ((UINT) (p - MultiSz) < Size);
  138. return MultiSz;
  139. }
  140. PCTSTR
  141. pGetNextHtmlToken (
  142. IN PCTSTR Arg,
  143. OUT PTSTR Key,
  144. IN PCTSTR Delimiters OPTIONAL
  145. )
  146. {
  147. BOOL Quotes;
  148. PCTSTR ArgStart, ArgEnd, ArgEndSpaceTrimmed;
  149. PCTSTR ArgStartPlusOne;
  150. PCTSTR Delim;
  151. CHARTYPE ch;
  152. ArgStart = SkipSpace (Arg);
  153. ArgEnd = ArgStart;
  154. ArgEndSpaceTrimmed = ArgEnd;
  155. Quotes = FALSE;
  156. while (*ArgEnd) {
  157. ch = _tcsnextc (ArgEnd);
  158. if (Delimiters) {
  159. Delim = Delimiters;
  160. while (*Delim) {
  161. if (ch == _tcsnextc (Delim)) {
  162. break;
  163. }
  164. Delim = _tcsinc (Delim);
  165. }
  166. if (*Delim) {
  167. ch = 0;
  168. }
  169. }
  170. if (ch == 0) {
  171. break;
  172. }
  173. if (!_istspace (ch)) {
  174. ArgEndSpaceTrimmed = ArgEnd;
  175. }
  176. ArgEnd = _tcsinc (ArgEnd);
  177. }
  178. //
  179. // Copy arg, stripping surrounding quotes
  180. //
  181. ArgEndSpaceTrimmed = _tcsinc (ArgEndSpaceTrimmed);
  182. if (_tcsnextc (Key) != TEXT('\"')) {
  183. StringCopyAB (Key, ArgStart, ArgEndSpaceTrimmed);
  184. } else {
  185. ArgEndSpaceTrimmed = _tcsdec2 (ArgStart, ArgEndSpaceTrimmed);
  186. if (!ArgEndSpaceTrimmed) {
  187. ArgEndSpaceTrimmed = ArgStart;
  188. } else if (_tcsnextc (ArgEndSpaceTrimmed) != TEXT('\"')) {
  189. ArgEndSpaceTrimmed = _tcsinc (ArgEndSpaceTrimmed);
  190. }
  191. ArgStartPlusOne = _tcsinc (ArgStart);
  192. StringCopyAB (Key, ArgStartPlusOne, ArgEndSpaceTrimmed);
  193. }
  194. if (*ArgEnd) {
  195. ArgEnd = _tcsinc (ArgEnd);
  196. }
  197. return ArgEnd;
  198. }
  199. VOID
  200. pGetHtmlKeyAndValue (
  201. IN PCTSTR Arg,
  202. OUT PTSTR Key,
  203. OUT PTSTR Value
  204. )
  205. {
  206. PTSTR p;
  207. Arg = pGetNextHtmlToken (Arg, Key, TEXT("="));
  208. MYASSERT (Arg);
  209. if (_tcsnextc (Arg) == TEXT('\"')) {
  210. //
  211. // De-quote the arg
  212. //
  213. StringCopy (Value, _tcsinc (Arg));
  214. p = _tcsrchr (Value, 0);
  215. p = _tcsdec2 (Value, p);
  216. if (p && _tcsnextc (p) == TEXT('\"')) {
  217. *p = 0;
  218. }
  219. } else {
  220. StringCopy (Value, Arg);
  221. }
  222. }
  223. PCTSTR
  224. pGetHtmlCommandOrChar (
  225. IN PCTSTR String,
  226. OUT CHARTYPE *Command,
  227. OUT CHARTYPE *Char,
  228. OUT PTSTR *CommandArg OPTIONAL
  229. )
  230. /*++
  231. Routine Description:
  232. pGetHtmlCommandOrChar parses HTML text is String, returing the HTML tag
  233. command, printable character, pointer to the next token, HTML tag arguments
  234. and a pointer to the end of the current token.
  235. Arguments:
  236. String - Specifies the string containing HTML
  237. Command - Receives the HTML tag command (COMMAND_* constant)
  238. or zero if the next token is not a tag
  239. Char - Receives the next printable character, or zero if the next token is
  240. not a printable character. Whitespace is both a command and a
  241. printable character. The rest are either one or the other.
  242. CommandArg - Receives a multi-sz of command arguments
  243. Return Value:
  244. A pointer to the first non-whitespace character of the next token, or
  245. NULL if no more tokens exist.
  246. --*/
  247. {
  248. CHARTYPE ch;
  249. PCTSTR p, q;
  250. BOOL Quoted;
  251. CHARTYPE ch2;
  252. INT i;
  253. PCTSTR semicolon;
  254. if (CommandArg) {
  255. *CommandArg = NULL;
  256. }
  257. ch = _tcsnextc (String);
  258. *Command = 0;
  259. if (ch == 0) {
  260. i = 0;
  261. }
  262. if (_istspace (ch)) {
  263. *Command = COMMAND_WHITESPACE;
  264. ch = TEXT(' ');
  265. } else if (ch == TEXT('&')) {
  266. //
  267. // Convert HTML character escapes:
  268. //
  269. // &lt; &gt; &amp; &quot; &apos; &nbsp;
  270. //
  271. semicolon = _tcschr (String + 1, TEXT(';'));
  272. if (semicolon) {
  273. *Char = 0;
  274. if (StringMatchAB (TEXT("lt"), String + 1, semicolon)) {
  275. *Char = TEXT('<');
  276. } else if (StringMatchAB (TEXT("gt"), String + 1, semicolon)) {
  277. *Char = TEXT('>');
  278. } else if (StringMatchAB (TEXT("amp"), String + 1, semicolon)) {
  279. *Char = TEXT('&');
  280. } else if (StringMatchAB (TEXT("quot"), String + 1, semicolon)) {
  281. *Char = TEXT('\"');
  282. } else if (StringMatchAB (TEXT("apos"), String + 1, semicolon)) {
  283. *Char = TEXT('\'');
  284. } else if (StringMatchAB (TEXT("nbsp"), String + 1, semicolon)) {
  285. *Char = TEXT(' ');
  286. }
  287. if (*Char) {
  288. *Command = COMMAND_ESCAPED_CHAR;
  289. return _tcsinc (semicolon);
  290. }
  291. }
  292. } else if (ch == TEXT('<')) {
  293. p = SkipSpace (_tcsinc (String));
  294. ch = 0;
  295. if (*p) {
  296. q = NULL;
  297. for (i = 0 ; g_TagList[i].Text ; i++) {
  298. if (StringIMatchTcharCount (g_TagList[i].Text, p, g_TagList[i].TextLen)) {
  299. q = p + g_TagList[i].TextLen;
  300. ch2 = _tcsnextc (q);
  301. if (!_istspace (ch2) && ch2 != TEXT('>')) {
  302. continue;
  303. }
  304. Quoted = FALSE;
  305. while (*q) {
  306. ch2 = _tcsnextc (q);
  307. if (ch2 == TEXT('\"')) {
  308. Quoted = !Quoted;
  309. } else if (!Quoted) {
  310. if (ch2 == TEXT('>')) {
  311. break;
  312. }
  313. }
  314. q = _tcsinc (q);
  315. }
  316. if (*q) {
  317. p += g_TagList[i].TextLen;
  318. p = SkipSpace (p);
  319. break;
  320. }
  321. }
  322. }
  323. if (!g_TagList[i].Text) {
  324. //
  325. // Ignore unsupported tag
  326. //
  327. p = _tcschr (String, TEXT('>'));
  328. if (!p) {
  329. p = GetEndOfString (String);
  330. }
  331. } else {
  332. //
  333. // Found a tag
  334. //
  335. String = p;
  336. *Command = g_TagList[i].Command;
  337. p = q;
  338. MYASSERT (p);
  339. if (CommandArg && String < p) {
  340. *CommandArg = (PTSTR) pParseHtmlArgs (String, p);
  341. }
  342. }
  343. }
  344. String = p;
  345. }
  346. *Char = ch;
  347. //
  348. // Skip block of spaces
  349. //
  350. if (_istspace (ch)) {
  351. do {
  352. String = _tcsinc (String);
  353. } while (_istspace (_tcsnextc (String)));
  354. return *String ? String : NULL;
  355. } else if (*String) {
  356. return _tcsinc (String);
  357. }
  358. return NULL;
  359. }
  360. PCTSTR
  361. pStripFormatting (
  362. PCTSTR String
  363. )
  364. {
  365. PTSTR NewString;
  366. PCTSTR Start, p, q;
  367. PTSTR d;
  368. CHARTYPE Char, Command;
  369. UINT tchars;
  370. //
  371. // Duplicate String, removing the <B> and <U> commands
  372. //
  373. NewString = AllocPathString (SizeOfString (String));
  374. Start = String;
  375. d = NewString;
  376. p = Start;
  377. do {
  378. q = pGetHtmlCommandOrChar (p, &Command, &Char, NULL);
  379. if (!Char || Command == COMMAND_WHITESPACE || Command == COMMAND_ESCAPED_CHAR) {
  380. if (Start < p) {
  381. tchars = p - Start;
  382. CopyMemory (d, Start, tchars * sizeof (TCHAR));
  383. d += tchars;
  384. }
  385. if (Char) {
  386. *d++ = (TCHAR) Char;
  387. }
  388. Start = q;
  389. }
  390. p = q;
  391. } while (p);
  392. *d = 0;
  393. return NewString;
  394. }
  395. VOID
  396. pCreateFontsIfNecessary (
  397. HDC hdc,
  398. HFONT BaseFont,
  399. PTEXTMETRIC ptm,
  400. HFONT *hFontNormal,
  401. HFONT *hFontBold,
  402. HFONT *hFontUnderlined
  403. )
  404. {
  405. LOGFONT lf;
  406. TCHAR FaceName[LF_FACESIZE];
  407. SIZE Extent;
  408. if (!BaseFont) {
  409. BaseFont = GetStockObject (DEFAULT_GUI_FONT);
  410. }
  411. if (BaseFont) {
  412. SelectObject (hdc, BaseFont);
  413. }
  414. GetTextMetrics (hdc, ptm);
  415. if (!(*hFontNormal)) {
  416. ZeroMemory (&lf, sizeof (lf));
  417. GetTextFace (hdc, LF_FACESIZE, FaceName);
  418. GetObject (BaseFont, sizeof (lf), &lf);
  419. lf.lfHeight = ptm->tmHeight;
  420. lf.lfWeight = FW_NORMAL;
  421. StringCopy (lf.lfFaceName, FaceName);
  422. *hFontNormal = CreateFontIndirect (&lf);
  423. lf.lfWeight = FW_BOLD;
  424. *hFontBold = CreateFontIndirect (&lf);
  425. lf.lfWeight = FW_NORMAL;
  426. lf.lfUnderline = 1;
  427. *hFontUnderlined = CreateFontIndirect (&lf);
  428. SelectObject (hdc, *hFontNormal);
  429. GetTextExtentPoint32 (hdc, g_HangingIndentString, TcharCount (g_HangingIndentString), &Extent);
  430. g_HangingIndentPixels = Extent.cx;
  431. if (GetACP() == 932) {
  432. g_FarEastFudgeFactor = ptm->tmAveCharWidth;
  433. } else {
  434. g_FarEastFudgeFactor = 0;
  435. }
  436. }
  437. }
  438. INT
  439. pComputeCharBytes (
  440. IN CHARTYPE Char
  441. )
  442. {
  443. if ((WORD) Char > 255) {
  444. return 2;
  445. }
  446. return 1;
  447. }
  448. typedef struct {
  449. BOOL InListItem;
  450. INT InUnorderedList;
  451. BOOL NextLineBlank;
  452. BOOL LastWasBlankLine;
  453. } PARSESTATE, *PPARSESTATE;
  454. PCTSTR
  455. pFindFirstCharThatDoesNotFit (
  456. IN INT Margin,
  457. IN PGROWLIST ListPtr,
  458. IN PGROWBUFFER AttribsList,
  459. IN HASHTABLE BookmarkTable,
  460. IN HWND hwnd,
  461. IN HFONT hFontNormal,
  462. IN HFONT hFontBold,
  463. IN HFONT hFontUnderlined,
  464. IN PCTSTR p,
  465. IN INT x,
  466. OUT PTEXTMETRIC ptm,
  467. OUT PRECT prect,
  468. IN OUT PLINEATTRIBS LineAttribs,
  469. IN OUT PPARSESTATE ParseState
  470. )
  471. {
  472. HDC hdc;
  473. PCTSTR endOfLine;
  474. PCTSTR wordWrapBreakPos;
  475. PCTSTR q;
  476. PCTSTR Arg;
  477. CHARTYPE Char, Command;
  478. SIZE Extent;
  479. UINT Size;
  480. PLINEATTRIBS PrevLineAttribs;
  481. SCROLLINFO si;
  482. PCTSTR FirstChar;
  483. PTSTR CommandBuf;
  484. PTSTR AnchorKey;
  485. PTSTR AnchorVal;
  486. UINT Count;
  487. INT ScrollBarPixels;
  488. BOOL PrevCharMb = FALSE;
  489. BOOL printableFound = FALSE;
  490. BOOL newLine;
  491. BOOL lastWasBlankLine;
  492. TCHAR oneChar;
  493. UINT prevCommand = 0;
  494. if (ParseState->NextLineBlank) {
  495. ParseState->LastWasBlankLine = TRUE;
  496. ParseState->NextLineBlank = FALSE;
  497. return p;
  498. }
  499. lastWasBlankLine = ParseState->LastWasBlankLine;
  500. ParseState->LastWasBlankLine = FALSE;
  501. //
  502. // Get display DC and initialize metrics
  503. //
  504. hdc = CreateDC (TEXT("display"), NULL, NULL, NULL);
  505. ParseState->NextLineBlank = FALSE;
  506. //
  507. // Select font of previous line
  508. //
  509. Size = GrowListGetSize (ListPtr);
  510. if (!Size) {
  511. if (hdc) {
  512. SelectObject (hdc, hFontNormal);
  513. }
  514. LineAttribs->LastCharAttribs = ATTRIB_NORMAL;
  515. } else {
  516. PrevLineAttribs = (PLINEATTRIBS) AttribsList->Buf + (Size - 1);
  517. LineAttribs->LastCharAttribs = PrevLineAttribs->LastCharAttribs;
  518. if (hdc) {
  519. if (PrevLineAttribs->LastCharAttribs == ATTRIB_BOLD) {
  520. SelectObject (hdc, hFontBold);
  521. } else if (PrevLineAttribs->LastCharAttribs == ATTRIB_UNDERLINED) {
  522. SelectObject (hdc, hFontUnderlined);
  523. } else {
  524. SelectObject (hdc, hFontNormal);
  525. }
  526. }
  527. }
  528. //
  529. // Get metrics
  530. //
  531. ZeroMemory (&si, sizeof (si));
  532. si.cbSize = sizeof (si);
  533. si.fMask = SIF_ALL;
  534. GetScrollInfo (hwnd, SB_VERT, &si);
  535. GetTextMetrics (hdc, ptm);
  536. GetClientRect (hwnd, prect);
  537. //
  538. // Account for scroll bar and right margin if scroll bar is
  539. // not yet visible
  540. //
  541. if (si.nMax < (INT) si.nPage || !si.nPage) {
  542. ScrollBarPixels = GetSystemMetrics (SM_CXVSCROLL);
  543. if (prect->right - prect->left > ScrollBarPixels * 2) {
  544. prect->right -= ScrollBarPixels;
  545. }
  546. }
  547. if (prect->right - prect->left > Margin * 2) {
  548. prect->right -= Margin;
  549. }
  550. //
  551. // Adjust if this is a far east system, because GetTextExtent doesn't
  552. // return the correct number of pixels.
  553. //
  554. prect->right -= g_FarEastFudgeFactor;
  555. //
  556. // Count characters until line is completely processed
  557. //
  558. MYASSERT (!_istspace (_tcsnextc (p)));
  559. FirstChar = p;
  560. endOfLine = p;
  561. wordWrapBreakPos = NULL;
  562. while (p && (INT) x < prect->right) {
  563. q = pGetHtmlCommandOrChar (
  564. p, // current string position
  565. &Command,
  566. &Char,
  567. &CommandBuf
  568. );
  569. if (prevCommand == COMMAND_UNINDENT &&
  570. Command != COMMAND_UNINDENT &&
  571. Command != COMMAND_INDENT &&
  572. (Command != COMMAND_WHITESPACE || printableFound) &&
  573. Command != COMMAND_LINE_BREAK &&
  574. Command != COMMAND_PARAGRAPH &&
  575. !ParseState->InUnorderedList &&
  576. !lastWasBlankLine
  577. ) {
  578. Command = COMMAND_LINE_BREAK;
  579. q = p;
  580. Char = 0;
  581. }
  582. if (!Char) {
  583. if (q != p) {
  584. if (prevCommand == COMMAND_UNINDENT &&
  585. Command == COMMAND_INDENT &&
  586. !ParseState->InUnorderedList &&
  587. !lastWasBlankLine
  588. ) {
  589. Command = COMMAND_LINE_BREAK;
  590. q = p;
  591. } else if (ParseState->InListItem) {
  592. MYASSERT (ParseState->InUnorderedList);
  593. switch (Command) {
  594. case COMMAND_PARAGRAPH:
  595. case COMMAND_LINE_BREAK:
  596. case COMMAND_UNINDENT:
  597. case COMMAND_LIST_ITEM:
  598. //
  599. // Terminate the list item
  600. //
  601. Command = COMMAND_LIST_ITEM_END;
  602. q = p;
  603. break;
  604. }
  605. } else if (Command == COMMAND_INDENT) {
  606. //
  607. // Before indenting, start a new line
  608. //
  609. if (printableFound) {
  610. Command = COMMAND_LINE_BREAK;
  611. q = p;
  612. }
  613. } else if (Command == COMMAND_UNINDENT) {
  614. //
  615. // Before unindenting, complete the current line
  616. //
  617. if (printableFound) {
  618. Command = COMMAND_LINE_BREAK;
  619. q = p;
  620. }
  621. }
  622. }
  623. //
  624. // Process tag
  625. //
  626. newLine = FALSE;
  627. switch (Command) {
  628. case 0:
  629. break;
  630. case COMMAND_BOLD:
  631. if (hdc) {
  632. SelectObject (hdc, hFontBold);
  633. }
  634. LineAttribs->LastCharAttribs = ATTRIB_BOLD;
  635. break;
  636. case COMMAND_UNDERLINE:
  637. if (hdc) {
  638. SelectObject (hdc, hFontUnderlined);
  639. }
  640. LineAttribs->LastCharAttribs = ATTRIB_UNDERLINED;
  641. break;
  642. case COMMAND_PARAGRAPH:
  643. ParseState->NextLineBlank = TRUE;
  644. newLine = TRUE;
  645. break;
  646. case COMMAND_LINE_BREAK:
  647. case COMMAND_HORZ_RULE:
  648. newLine = TRUE;
  649. break;
  650. case COMMAND_INDENT:
  651. ParseState->InUnorderedList += 1;
  652. if ((INT) (LineAttribs->Indent) < ptm->tmHeight * 20) {
  653. LineAttribs->Indent += ptm->tmHeight * 2;
  654. x += ptm->tmHeight * 2;
  655. }
  656. break;
  657. case COMMAND_LIST_ITEM:
  658. if (ParseState->InUnorderedList) {
  659. LineAttribs->HangingIndent += g_HangingIndentPixels;
  660. ParseState->InListItem = TRUE;
  661. }
  662. break;
  663. case COMMAND_LIST_ITEM_END:
  664. if (ParseState->InUnorderedList) {
  665. LineAttribs->HangingIndent -= g_HangingIndentPixels;
  666. ParseState->InListItem = FALSE;
  667. newLine = TRUE;
  668. }
  669. break;
  670. case COMMAND_UNINDENT:
  671. if (ParseState->InUnorderedList) {
  672. MYASSERT (!(ParseState->InListItem));
  673. ParseState->InUnorderedList -= 1;
  674. if ((INT) (LineAttribs->Indent) > ptm->tmHeight * 2) {
  675. LineAttribs->Indent -= ptm->tmHeight * 2;
  676. x -= ptm->tmHeight * 2;
  677. }
  678. //if (!lastWasBlankLine || printableFound) {
  679. // newLine = TRUE;
  680. //}
  681. }
  682. break;
  683. case COMMAND_ANCHOR:
  684. if (CommandBuf) {
  685. Arg = CommandBuf;
  686. while (*Arg) {
  687. //
  688. // Search for a NAME arg
  689. //
  690. Count = TcharCount (Arg);
  691. AnchorKey = AllocText (Count);
  692. AnchorVal = AllocText (Count);
  693. if (!AnchorKey || !AnchorVal) {
  694. FreeText (AnchorKey);
  695. FreeText (AnchorVal);
  696. break;
  697. }
  698. pGetHtmlKeyAndValue (Arg, AnchorKey, AnchorVal);
  699. if (StringIMatch (TEXT("NAME"), AnchorKey)) {
  700. HtAddStringAndData (BookmarkTable, AnchorVal, &Size);
  701. } else if (StringIMatch (TEXT("HREF"), AnchorKey)) {
  702. LineAttribs->AnchorWrap = TRUE;
  703. }
  704. FreeText (AnchorKey);
  705. FreeText (AnchorVal);
  706. Arg += Count + 1;
  707. }
  708. }
  709. break;
  710. case COMMAND_ANCHOR_END:
  711. LineAttribs->AnchorWrap = FALSE;
  712. break;
  713. case COMMAND_BOLD_END:
  714. case COMMAND_UNDERLINE_END:
  715. if (hdc) {
  716. SelectObject (hdc, hFontNormal);
  717. }
  718. LineAttribs->LastCharAttribs = ATTRIB_NORMAL;
  719. break;
  720. }
  721. FreePathString (CommandBuf);
  722. prevCommand = Command;
  723. if (newLine) {
  724. if (q) {
  725. endOfLine = q;
  726. } else {
  727. endOfLine = GetEndOfString (p);
  728. }
  729. wordWrapBreakPos = NULL;
  730. break;
  731. }
  732. } else {
  733. //
  734. // Char is non-zero, so display it
  735. //
  736. if (printableFound || Command != COMMAND_WHITESPACE) {
  737. printableFound = TRUE;
  738. prevCommand = Command;
  739. if (Command) {
  740. oneChar = (TCHAR) Char;
  741. GetTextExtentPoint32 (hdc, &oneChar, 1, &Extent);
  742. } else {
  743. GetTextExtentPoint32 (hdc, p, pComputeCharBytes (Char), &Extent);
  744. }
  745. x += Extent.cx;
  746. if (_istspace (Char)) {
  747. wordWrapBreakPos = q;
  748. } else if (IsLeadByte (*q)) {
  749. if (PrevCharMb && !IsPunct (_tcsnextc (q))) {
  750. wordWrapBreakPos = q;
  751. }
  752. PrevCharMb = TRUE;
  753. } else {
  754. PrevCharMb = FALSE;
  755. }
  756. }
  757. }
  758. if (q) {
  759. endOfLine = q;
  760. } else {
  761. endOfLine = GetEndOfString (endOfLine);
  762. wordWrapBreakPos = NULL;
  763. }
  764. p = q;
  765. }
  766. DeleteDC (hdc);
  767. if (wordWrapBreakPos) {
  768. return wordWrapBreakPos;
  769. }
  770. return endOfLine;
  771. }
  772. VOID
  773. pRegisterHotLink (
  774. IN PGROWBUFFER HotLinkArray,
  775. IN PRECT HotRect,
  776. IN PCTSTR UrlLink,
  777. IN PCTSTR BookmarkLink
  778. )
  779. {
  780. PHOTLINK HotLink;
  781. HotLink = (PHOTLINK) GrowBuffer (HotLinkArray, sizeof (HOTLINK));
  782. if (!HotLink) {
  783. return;
  784. }
  785. HotLink->Rect = *HotRect;
  786. if (UrlLink) {
  787. StringCopy (HotLink->Url, UrlLink);
  788. } else {
  789. HotLink->Url[0] = 0;
  790. }
  791. if (BookmarkLink) {
  792. StringCopy (HotLink->Bookmark, BookmarkLink);
  793. } else {
  794. HotLink->Bookmark[0] = 0;
  795. }
  796. }
  797. PHOTLINK
  798. pFindHotLink (
  799. IN PGROWBUFFER HotLinkArray,
  800. IN UINT x,
  801. IN UINT y
  802. )
  803. {
  804. PHOTLINK HotLink;
  805. UINT u;
  806. HotLink = (PHOTLINK) HotLinkArray->Buf;
  807. for (u = 0 ; u < HotLinkArray->End ; u += sizeof (HOTLINK)) {
  808. if (x >= (UINT) HotLink->Rect.left && x < (UINT) HotLink->Rect.right &&
  809. y >= (UINT) HotLink->Rect.top && y < (UINT) HotLink->Rect.bottom
  810. ) {
  811. break;
  812. }
  813. HotLink++;
  814. }
  815. if (u >= HotLinkArray->End) {
  816. HotLink = NULL;
  817. }
  818. return HotLink;
  819. }
  820. BOOL
  821. pLaunchedAddRemovePrograms (
  822. IN PCTSTR CmdLine
  823. )
  824. {
  825. return CmdLine && _tcsstr (CmdLine, TEXT("appwiz.cpl"));
  826. }
  827. typedef struct {
  828. HASHTABLE BookmarkTable;
  829. UINT LineHeight;
  830. HFONT hFont;
  831. HFONT hFontNormal;
  832. HFONT hFontBold;
  833. HFONT hFontUnderlined;
  834. GROWLIST List;
  835. GROWBUFFER AttribsList;
  836. GROWBUFFER HotLinkArray;
  837. BOOL UrlEnabled;
  838. INT Margin;
  839. PARSESTATE ParseState;
  840. } TEXTVIEW_STATE, *PTEXTVIEW_STATE;
  841. LRESULT
  842. CALLBACK
  843. TextViewProc (
  844. HWND hwnd,
  845. UINT uMsg,
  846. WPARAM wParam,
  847. LPARAM lParam
  848. )
  849. {
  850. PAINTSTRUCT ps;
  851. HDC hdc;
  852. TEXTMETRIC tm;
  853. SCROLLINFO si;
  854. RECT rect;
  855. RECT FillRect;
  856. UINT Pos;
  857. UINT End;
  858. INT x, y;
  859. INT i;
  860. PCTSTR TrueStart, Start, Last;
  861. PCTSTR p, q;
  862. UINT Tchars;
  863. CHARTYPE Char, Command;
  864. UINT PrevHangingIndent;
  865. SIZE Extent;
  866. PLINEATTRIBS LineAttribs;
  867. PLINEATTRIBS PrevLineAttribs;
  868. HPEN OldPen;
  869. HBRUSH FillBrush;
  870. HPEN ShadowPen;
  871. HPEN HighlightPen;
  872. PTEXTVIEW_STATE s;
  873. UINT LineHeight;
  874. PHOTLINK HotLink;
  875. BOOL Hot;
  876. RECT HotRect;
  877. PCTSTR UrlLink;
  878. PCTSTR BookmarkLink;
  879. PTSTR CommandBuf;
  880. PCTSTR Arg;
  881. TCHAR Href[MAX_URL];
  882. PTSTR AnchorKey;
  883. PTSTR AnchorVal;
  884. UINT Count;
  885. HKEY TempKey;
  886. DWORD GrowListSize;
  887. PCTSTR ShellArgs;
  888. LONG l;
  889. BOOL b;
  890. PTSTR text;
  891. UINT textSize;
  892. BOOL printableFound;
  893. TCHAR oneChar;
  894. s = (PTEXTVIEW_STATE) GetWindowLong (hwnd, GWL_USERDATA);
  895. if (s) {
  896. LineHeight = s->LineHeight;
  897. } else {
  898. LineHeight = 0;
  899. }
  900. switch (uMsg) {
  901. case WM_CREATE:
  902. for (i = 0 ; g_TagList[i].Text ; i++) {
  903. g_TagList[i].TextLen = TcharCount (g_TagList[i].Text);
  904. }
  905. s = (PTEXTVIEW_STATE) MemAlloc (g_hHeap, HEAP_ZERO_MEMORY, sizeof (TEXTVIEW_STATE));
  906. SetWindowLong (hwnd, GWL_USERDATA, (LONG) s);
  907. TempKey = OpenRegKeyStr (TEXT("HKCR\\.URL"));
  908. if (TempKey) {
  909. CloseRegKey (TempKey);
  910. }
  911. s->UrlEnabled = (TempKey != NULL);
  912. ZeroMemory (&si, sizeof (si));
  913. si.fMask = SIF_RANGE;
  914. s->BookmarkTable = HtAllocWithData (sizeof (DWORD));
  915. if (!s->BookmarkTable) {
  916. return -1;
  917. }
  918. // WM_SETFONT does a bunch of work, including populating the text
  919. SendMessage (
  920. hwnd,
  921. WM_SETFONT,
  922. SendMessage (GetParent (hwnd), WM_GETFONT, 0, 0),
  923. 0
  924. );
  925. return 0;
  926. case WM_GETDLGCODE:
  927. return DLGC_WANTARROWS;
  928. case WMX_GOTO:
  929. //
  930. // Determine if text is in bookmark table
  931. //
  932. if (!lParam) {
  933. return 0;
  934. }
  935. if (HtFindStringAndData (s->BookmarkTable, (PCTSTR) lParam, &Pos)) {
  936. PostMessage (
  937. hwnd,
  938. WM_VSCROLL,
  939. MAKELPARAM (SB_THUMBPOSITION, (WORD) Pos),
  940. (LPARAM) hwnd
  941. );
  942. }
  943. return 0;
  944. case WMX_ADDLINE:
  945. //
  946. // Init
  947. //
  948. l = GetWindowLong (hwnd, GWL_STYLE) & WS_BORDER;
  949. if (!l) {
  950. l = GetWindowLong (hwnd, GWL_EXSTYLE) & (WS_EX_DLGMODALFRAME|WS_EX_WINDOWEDGE|WS_EX_CLIENTEDGE|WS_EX_STATICEDGE);
  951. }
  952. if (!s->Margin && l) {
  953. s->Margin = s->LineHeight / 2;
  954. }
  955. Start = (PCTSTR) lParam;
  956. // ignore leading space
  957. TrueStart = Start;
  958. while (_istspace (_tcsnextc (Start))) {
  959. Start = _tcsinc (Start);
  960. }
  961. PrevLineAttribs = NULL;
  962. if (s->AttribsList.End) {
  963. MYASSERT (s->AttribsList.End >= sizeof (LINEATTRIBS));
  964. PrevLineAttribs = (PLINEATTRIBS) (s->AttribsList.Buf +
  965. s->AttribsList.End -
  966. sizeof (LINEATTRIBS)
  967. );
  968. }
  969. //
  970. // Copy line attributes (optional) to our Attribs list; ignore errors.
  971. //
  972. LineAttribs = (PLINEATTRIBS) GrowBuffer (&s->AttribsList, sizeof (LINEATTRIBS));
  973. if (!LineAttribs) {
  974. return 0;
  975. }
  976. //
  977. // Copy previous line's attributes
  978. //
  979. ZeroMemory (LineAttribs, sizeof (LINEATTRIBS));
  980. if (PrevLineAttribs) {
  981. LineAttribs->AnchorWrap = PrevLineAttribs->AnchorWrap;
  982. LineAttribs->Indent = PrevLineAttribs->Indent;
  983. LineAttribs->HangingIndent = PrevLineAttribs->HangingIndent;
  984. PrevHangingIndent = PrevLineAttribs->HangingIndent;
  985. } else {
  986. LineAttribs->Indent = s->Margin;
  987. LineAttribs->HangingIndent = 0;
  988. PrevHangingIndent = 0;
  989. }
  990. x = LineAttribs->Indent + PrevHangingIndent;
  991. //
  992. // Find the first character that does not fit on the line
  993. //
  994. Last = pFindFirstCharThatDoesNotFit (
  995. s->Margin,
  996. &s->List,
  997. &s->AttribsList,
  998. s->BookmarkTable,
  999. hwnd,
  1000. s->hFontNormal,
  1001. s->hFontBold,
  1002. s->hFontUnderlined,
  1003. Start,
  1004. (INT) x,
  1005. &tm,
  1006. &rect,
  1007. LineAttribs,
  1008. &s->ParseState
  1009. );
  1010. //
  1011. // Update the vertical scroll bar
  1012. //
  1013. MYASSERT (LineHeight);
  1014. ZeroMemory (&si, sizeof (si));
  1015. si.cbSize = sizeof (si);
  1016. si.fMask = SIF_RANGE|SIF_PAGE;
  1017. si.nMin = 0;
  1018. si.nPage = rect.bottom / LineHeight;
  1019. si.nMax = GrowListGetSize (&s->List);
  1020. SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
  1021. //
  1022. // Copy the string (or as much that is visible) to our
  1023. // grow list
  1024. //
  1025. GrowListAppendStringAB (&s->List, Start, Last);
  1026. //
  1027. // Return the number of bytes copied
  1028. //
  1029. return Last - TrueStart;
  1030. case WMX_ALL_LINES_PAINTED:
  1031. //
  1032. // Scan all the lines, returning 0 if at least one has Painted == FALSE
  1033. //
  1034. LineAttribs = (PLINEATTRIBS) s->AttribsList.Buf;
  1035. if (!LineAttribs) {
  1036. return 1;
  1037. }
  1038. for (Pos = 0 ; Pos < s->AttribsList.End ; Pos += sizeof (LINEATTRIBS)) {
  1039. LineAttribs = (PLINEATTRIBS) (s->AttribsList.Buf + Pos);
  1040. if (!LineAttribs->Painted) {
  1041. return 0;
  1042. }
  1043. }
  1044. return 1;
  1045. case WM_ERASEBKGND:
  1046. return 0;
  1047. case WM_KEYDOWN:
  1048. switch (wParam) {
  1049. case VK_DOWN:
  1050. PostMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
  1051. return 0;
  1052. case VK_UP:
  1053. PostMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0);
  1054. return 0;
  1055. case VK_NEXT:
  1056. PostMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
  1057. return 0;
  1058. case VK_PRIOR:
  1059. PostMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
  1060. return 0;
  1061. case VK_HOME:
  1062. PostMessage (hwnd, WM_VSCROLL, SB_TOP, 0);
  1063. return 0;
  1064. case VK_END:
  1065. PostMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0);
  1066. return 0;
  1067. }
  1068. break;
  1069. case WM_SETFONT:
  1070. s->hFont = (HFONT) wParam;
  1071. if (s->hFontNormal) {
  1072. DeleteObject (s->hFontNormal);
  1073. s->hFontNormal = NULL;
  1074. }
  1075. if (s->hFontBold) {
  1076. DeleteObject (s->hFontBold);
  1077. s->hFontBold = NULL;
  1078. }
  1079. if (s->hFontUnderlined) {
  1080. DeleteObject (s->hFontUnderlined);
  1081. s->hFontUnderlined = NULL;
  1082. }
  1083. hdc = CreateDC (TEXT("display"), NULL, NULL, NULL);
  1084. if (hdc) {
  1085. pCreateFontsIfNecessary (hdc, s->hFont, &tm, &s->hFontNormal, &s->hFontBold, &s->hFontUnderlined);
  1086. s->LineHeight = tm.tmHeight;
  1087. DeleteDC (hdc);
  1088. } else {
  1089. s->LineHeight = 0;
  1090. }
  1091. if (lParam) {
  1092. InvalidateRect (hwnd, NULL, FALSE);
  1093. }
  1094. if (s->AttribsList.End == 0) {
  1095. textSize = GetWindowTextLength (hwnd);
  1096. text = (PTSTR) MemAllocUninit ((textSize + 1) * sizeof (TCHAR));
  1097. GetWindowText (hwnd, text, textSize + 1);
  1098. AddStringToTextView (hwnd, text);
  1099. FreeMem (text);
  1100. }
  1101. return 0;
  1102. case WM_MOUSEMOVE:
  1103. //
  1104. // Search array of hit test rectangles
  1105. //
  1106. x = LOWORD(lParam);
  1107. y = HIWORD(lParam);
  1108. HotLink = pFindHotLink (&s->HotLinkArray, x, y);
  1109. if (HotLink) {
  1110. SetCursor (LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_OUR_HAND)));
  1111. } else {
  1112. SetCursor (LoadCursor (NULL, MAKEINTRESOURCE (IDC_ARROW)));
  1113. }
  1114. break;
  1115. case WM_LBUTTONDOWN:
  1116. //
  1117. // Search array of hit test rectangles
  1118. //
  1119. x = LOWORD(lParam);
  1120. y = HIWORD(lParam);
  1121. HotLink = pFindHotLink (&s->HotLinkArray, x, y);
  1122. if (HotLink) {
  1123. if (HotLink->Url[0]) {
  1124. if (!StringIMatchCharCount (TEXT("file:"), HotLink->Url, 5)) {
  1125. ShellExecute (hwnd, TEXT("open"), HotLink->Url, NULL, NULL, 0);
  1126. } else {
  1127. ShellArgs = &HotLink->Url[5];
  1128. if (*ShellArgs == TEXT('/')) {
  1129. ShellArgs++;
  1130. }
  1131. if (*ShellArgs == TEXT('/')) {
  1132. ShellArgs++;
  1133. }
  1134. ShellArgs = ExtractArgZero (ShellArgs, Href);
  1135. //
  1136. // if we're launching the Add/Remove programs applet,
  1137. // warn users they'll have to restart setup (RAID # 293357)
  1138. //
  1139. b = TRUE;
  1140. if (!UNATTENDED() &&
  1141. !REPORTONLY() &&
  1142. !g_UIQuitSetup &&
  1143. pLaunchedAddRemovePrograms (ShellArgs)
  1144. ) {
  1145. if (IDYES == ResourceMessageBox (
  1146. hwnd,
  1147. MSG_RESTART_IF_CONTINUE_APPWIZCPL,
  1148. MB_YESNO | MB_ICONQUESTION,
  1149. NULL
  1150. )) {
  1151. LOG ((LOG_INFORMATION, "User launched Add/Remove Programs applet; setup will terminate"));
  1152. PostMessage (GetParent (hwnd), WMX_RESTART_SETUP, FALSE, TRUE);
  1153. } else {
  1154. b = FALSE;
  1155. }
  1156. }
  1157. if (b) {
  1158. ShellExecute (hwnd, TEXT("open"), Href, ShellArgs, NULL, 0);
  1159. }
  1160. }
  1161. } else if (HotLink->Bookmark) {
  1162. SendMessage (hwnd, WMX_GOTO, 0, (LPARAM) HotLink->Bookmark);
  1163. }
  1164. }
  1165. break;
  1166. case WM_SETTEXT:
  1167. SendMessage (hwnd, WMX_CLEANUP, 0, 0);
  1168. DefWindowProc (hwnd, uMsg, wParam, lParam);
  1169. SendMessage (hwnd, WM_CREATE, 0, 0);
  1170. return 0;
  1171. case WM_PAINT:
  1172. FillBrush = CreateSolidBrush (GetSysColor (COLOR_BTNFACE));
  1173. ShadowPen = CreatePen (PS_SOLID, 1, GetSysColor (COLOR_3DSHADOW));
  1174. HighlightPen = CreatePen (PS_SOLID, 1, GetSysColor (COLOR_3DHILIGHT));
  1175. hdc = BeginPaint (hwnd, &ps);
  1176. pCreateFontsIfNecessary (hdc, s->hFont, &tm, &s->hFontNormal, &s->hFontBold, &s->hFontUnderlined);
  1177. s->LineHeight = LineHeight = tm.tmHeight;
  1178. GetClientRect (hwnd, &rect);
  1179. //
  1180. // Select colors
  1181. //
  1182. SetBkColor (hdc, GetSysColor (COLOR_BTNFACE));
  1183. SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT));
  1184. if (FillBrush) {
  1185. SelectObject (hdc, FillBrush);
  1186. }
  1187. SelectObject (hdc, GetStockObject (NULL_PEN));
  1188. //
  1189. // Get scroll position
  1190. //
  1191. ZeroMemory (&si, sizeof (si));
  1192. si.cbSize = sizeof (si);
  1193. si.fMask = SIF_PAGE|SIF_POS;
  1194. GetScrollInfo (hwnd, SB_VERT, &si);
  1195. End = (UINT) si.nPos + si.nPage + 1;
  1196. GrowListSize = GrowListGetSize (&s->List);
  1197. End = min (End, GrowListSize);
  1198. y = 0;
  1199. //
  1200. // Select font of previous line. Move up if anchor wrap is on.
  1201. //
  1202. if (si.nPos) {
  1203. do {
  1204. LineAttribs = (PLINEATTRIBS) s->AttribsList.Buf + (si.nPos - 1);
  1205. if (LineAttribs->AnchorWrap) {
  1206. si.nPos--;
  1207. y -= LineHeight;
  1208. } else {
  1209. break;
  1210. }
  1211. } while (si.nPos > 0);
  1212. }
  1213. if (si.nPos) {
  1214. LineAttribs = (PLINEATTRIBS) s->AttribsList.Buf + (si.nPos - 1);
  1215. if (LineAttribs->LastCharAttribs == ATTRIB_BOLD) {
  1216. SelectObject (hdc, s->hFontBold);
  1217. } else if (LineAttribs->LastCharAttribs == ATTRIB_UNDERLINED) {
  1218. SelectObject (hdc, s->hFontUnderlined);
  1219. } else {
  1220. SelectObject (hdc, s->hFontNormal);
  1221. }
  1222. } else {
  1223. SelectObject (hdc, s->hFontNormal);
  1224. }
  1225. //
  1226. // Paint!
  1227. //
  1228. Hot = FALSE;
  1229. s->HotLinkArray.End = 0;
  1230. BookmarkLink = UrlLink = NULL;
  1231. for (Pos = (UINT) si.nPos ; Pos < End ; Pos++) {
  1232. p = GrowListGetString (&s->List, Pos);
  1233. printableFound = FALSE;
  1234. LineAttribs = (PLINEATTRIBS) s->AttribsList.Buf + Pos;
  1235. //
  1236. // Compute hanging indent using previous line
  1237. //
  1238. if (Pos > 0) {
  1239. PrevLineAttribs = (PLINEATTRIBS) ((PLINEATTRIBS) s->AttribsList.Buf + Pos - 1);
  1240. PrevHangingIndent = PrevLineAttribs->HangingIndent;
  1241. } else {
  1242. PrevHangingIndent = 0;
  1243. }
  1244. //
  1245. // Compute starting index
  1246. //
  1247. if (LineAttribs) {
  1248. x = LineAttribs->Indent + PrevHangingIndent;
  1249. LineAttribs->Painted = TRUE;
  1250. } else {
  1251. x = s->Margin;
  1252. }
  1253. //
  1254. // Compute blank area
  1255. //
  1256. if (x > 0) {
  1257. FillRect.left = 0;
  1258. FillRect.right = x;
  1259. FillRect.top = y;
  1260. FillRect.bottom = y + LineHeight;
  1261. Rectangle (hdc, FillRect.left, FillRect.top, FillRect.right + 1, FillRect.bottom + 1);
  1262. }
  1263. //
  1264. // Multiline hotlink
  1265. //
  1266. if (Hot) {
  1267. HotRect.left = x;
  1268. HotRect.top = y;
  1269. }
  1270. //
  1271. // Compute text block
  1272. //
  1273. Start = p;
  1274. while (p) {
  1275. q = pGetHtmlCommandOrChar (p, &Command, &Char, &CommandBuf);
  1276. if (!Char || Command == COMMAND_WHITESPACE || Command == COMMAND_ESCAPED_CHAR) {
  1277. //
  1278. // If this is the end of of a block of text, paint it.
  1279. //
  1280. MYASSERT (Start);
  1281. Tchars = p - Start;
  1282. if (Tchars) {
  1283. TextOut (hdc, x, y, Start, Tchars);
  1284. GetTextExtentPoint32 (hdc, Start, Tchars, &Extent);
  1285. x += Extent.cx;
  1286. if (x > rect.right) {
  1287. break;
  1288. }
  1289. }
  1290. Start = q;
  1291. oneChar = 0;
  1292. if (printableFound && Command == COMMAND_WHITESPACE) {
  1293. oneChar = TEXT(' ');
  1294. } else if (Command == COMMAND_ESCAPED_CHAR) {
  1295. oneChar = (TCHAR) Char;
  1296. }
  1297. if (oneChar) {
  1298. TextOut (hdc, x, y, &oneChar, 1);
  1299. GetTextExtentPoint32 (hdc, &oneChar, 1, &Extent);
  1300. x += Extent.cx;
  1301. if (x > rect.right) {
  1302. break;
  1303. }
  1304. }
  1305. } else {
  1306. printableFound = TRUE;
  1307. }
  1308. switch (Command) {
  1309. case 0:
  1310. case COMMAND_WHITESPACE:
  1311. case COMMAND_ESCAPED_CHAR:
  1312. break;
  1313. case COMMAND_ANCHOR:
  1314. //
  1315. // Does this anchor have an HREF?
  1316. //
  1317. if (CommandBuf) {
  1318. Arg = CommandBuf;
  1319. Href[0] = 0;
  1320. while (Href[0] == 0 && *Arg) {
  1321. //
  1322. // Search for a HREF arg
  1323. //
  1324. Count = TcharCount (Arg);
  1325. AnchorKey = AllocText (Count);
  1326. AnchorVal = AllocText (Count);
  1327. if (!AnchorKey || !AnchorVal) {
  1328. FreeText (AnchorKey);
  1329. FreeText (AnchorVal);
  1330. break;
  1331. }
  1332. pGetHtmlKeyAndValue (Arg, AnchorKey, AnchorVal);
  1333. if (StringIMatch (TEXT("HREF"), AnchorKey)) {
  1334. _tcssafecpy (Href, AnchorVal, MAX_URL);
  1335. }
  1336. FreeText (AnchorKey);
  1337. FreeText (AnchorVal);
  1338. Arg += Count + 1;
  1339. }
  1340. if (Href[0]) {
  1341. //
  1342. // Does HREF point to a bookmark?
  1343. //
  1344. BookmarkLink = UrlLink = NULL;
  1345. if (_tcsnextc (Href) == TEXT('#')) {
  1346. BookmarkLink = SkipSpace (_tcsinc (Href));
  1347. } else {
  1348. if (s->UrlEnabled) {
  1349. UrlLink = Href;
  1350. }
  1351. }
  1352. //
  1353. // If either BookmarkLink or Url is non-NULL, then turn on
  1354. // link font and color
  1355. //
  1356. if (BookmarkLink || UrlLink) {
  1357. HotRect.left = x;
  1358. HotRect.top = y;
  1359. Hot = TRUE;
  1360. SelectObject (hdc, s->hFontUnderlined);
  1361. SetTextColor (hdc, GetSysColor (COLOR_HIGHLIGHT));
  1362. }
  1363. }
  1364. }
  1365. break;
  1366. case COMMAND_BOLD:
  1367. SelectObject (hdc, s->hFontBold);
  1368. break;
  1369. case COMMAND_UNDERLINE:
  1370. SelectObject (hdc, s->hFontUnderlined);
  1371. break;
  1372. case COMMAND_HORZ_RULE:
  1373. FillRect.left = rect.left;
  1374. FillRect.right = rect.right;
  1375. FillRect.top = y;
  1376. FillRect.bottom = y + LineHeight;
  1377. Rectangle (hdc, FillRect.left, FillRect.top, FillRect.right + 1, FillRect.bottom + 1);
  1378. OldPen = (HPEN) SelectObject (hdc, ShadowPen);
  1379. MoveToEx (hdc, rect.left + 3, y + LineHeight / 2, NULL);
  1380. LineTo (hdc, rect.right - 3, y + LineHeight / 2);
  1381. SelectObject (hdc, HighlightPen);
  1382. MoveToEx (hdc, rect.left + 3, y + LineHeight / 2 + 1, NULL);
  1383. LineTo (hdc, rect.right - 3, y + LineHeight / 2 + 1);
  1384. SelectObject (hdc, OldPen);
  1385. x = rect.right;
  1386. break;
  1387. case COMMAND_ANCHOR_END:
  1388. SelectObject (hdc, s->hFontNormal);
  1389. SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT));
  1390. if (Hot) {
  1391. Hot = FALSE;
  1392. HotRect.right = x;
  1393. HotRect.bottom = y + LineHeight;
  1394. pRegisterHotLink (&s->HotLinkArray, &HotRect, UrlLink, BookmarkLink);
  1395. }
  1396. break;
  1397. case COMMAND_BOLD_END:
  1398. case COMMAND_UNDERLINE_END:
  1399. SelectObject (hdc, s->hFontNormal);
  1400. break;
  1401. }
  1402. FreePathString (CommandBuf);
  1403. p = q;
  1404. }
  1405. //
  1406. // Hot link that extends to multiple lines
  1407. //
  1408. if (Hot) {
  1409. HotRect.right = x;
  1410. HotRect.bottom = y + LineHeight;
  1411. pRegisterHotLink (&s->HotLinkArray, &HotRect, UrlLink, BookmarkLink);
  1412. }
  1413. //
  1414. // Fill blank area to end of line
  1415. //
  1416. if (x < rect.right) {
  1417. FillRect.left = x;
  1418. FillRect.right = rect.right;
  1419. FillRect.top = y;
  1420. FillRect.bottom = y + LineHeight;
  1421. Rectangle (hdc, FillRect.left, FillRect.top, FillRect.right + 1, FillRect.bottom + 1);
  1422. }
  1423. y += LineHeight;
  1424. }
  1425. //
  1426. // Fill blank area to bottom of window
  1427. //
  1428. if (y < rect.bottom) {
  1429. FillRect.left = 0;
  1430. FillRect.right = rect.right;
  1431. FillRect.top = y;
  1432. FillRect.bottom = rect.bottom;
  1433. Rectangle (hdc, FillRect.left, FillRect.top, FillRect.right + 1, FillRect.bottom + 1);
  1434. }
  1435. //
  1436. // Cleanup
  1437. //
  1438. if (FillBrush) {
  1439. DeleteObject (FillBrush);
  1440. }
  1441. if (ShadowPen) {
  1442. DeleteObject (ShadowPen);
  1443. ShadowPen = NULL;
  1444. }
  1445. if (HighlightPen) {
  1446. DeleteObject (HighlightPen);
  1447. HighlightPen = NULL;
  1448. }
  1449. EndPaint (hwnd, &ps);
  1450. return 0;
  1451. case WM_VSCROLL:
  1452. Pos = HIWORD (wParam);
  1453. ZeroMemory (&si, sizeof (si));
  1454. si.cbSize = sizeof (si);
  1455. si.fMask = SIF_ALL;
  1456. GetScrollInfo (hwnd, SB_VERT, &si);
  1457. i = si.nMax - (INT) si.nPage + 1;
  1458. si.fMask = 0;
  1459. switch (LOWORD (wParam)) {
  1460. case SB_PAGEDOWN:
  1461. if (si.nPos + (INT) si.nPage < i) {
  1462. si.nPos += si.nPage;
  1463. ScrollWindow (hwnd, 0, -((INT) LineHeight * (INT) si.nPage), NULL, NULL);
  1464. si.fMask = SIF_POS;
  1465. break;
  1466. }
  1467. // fall through!
  1468. case SB_BOTTOM:
  1469. if (si.nPos < i) {
  1470. InvalidateRect (hwnd, NULL, FALSE);
  1471. si.nPos = i;
  1472. si.fMask = SIF_POS;
  1473. }
  1474. break;
  1475. case SB_LINEDOWN:
  1476. if (si.nPos < i) {
  1477. si.nPos += 1;
  1478. ScrollWindow (hwnd, 0, -((INT) LineHeight), NULL, NULL);
  1479. si.fMask = SIF_POS;
  1480. }
  1481. break;
  1482. case SB_LINEUP:
  1483. if (si.nPos > si.nMin) {
  1484. si.nPos -= 1;
  1485. ScrollWindow (hwnd, 0, (INT) LineHeight, NULL, NULL);
  1486. si.fMask = SIF_POS;
  1487. }
  1488. break;
  1489. case SB_THUMBTRACK:
  1490. case SB_THUMBPOSITION:
  1491. if ((INT) Pos != si.nPos) {
  1492. InvalidateRect (hwnd, NULL, FALSE);
  1493. si.nPos = (INT) Pos;
  1494. si.fMask = SIF_POS;
  1495. }
  1496. break;
  1497. case SB_PAGEUP:
  1498. if (si.nPos >= si.nMin + (INT) si.nPage) {
  1499. si.nPos -= si.nPage;
  1500. ScrollWindow (hwnd, 0, (INT) LineHeight * si.nPage, NULL, NULL);
  1501. si.fMask = SIF_POS;
  1502. break;
  1503. }
  1504. // fall through
  1505. case SB_TOP:
  1506. if (si.nPos > si.nMin) {
  1507. ScrollWindow (hwnd, 0, (INT) LineHeight * si.nPos, NULL, NULL);
  1508. si.nPos = si.nMin;
  1509. si.fMask = SIF_POS;
  1510. }
  1511. break;
  1512. }
  1513. if (si.fMask) {
  1514. SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
  1515. }
  1516. break;
  1517. case WM_DESTROY:
  1518. case WMX_CLEANUP:
  1519. if (!g_Terminated) {
  1520. if (s) {
  1521. if (s->hFontNormal) {
  1522. DeleteObject (s->hFontNormal);
  1523. s->hFontNormal = NULL;
  1524. }
  1525. if (s->hFontBold) {
  1526. DeleteObject (s->hFontBold);
  1527. s->hFontBold = NULL;
  1528. }
  1529. if (s->hFontUnderlined) {
  1530. DeleteObject (s->hFontUnderlined);
  1531. s->hFontUnderlined = NULL;
  1532. }
  1533. FreeGrowBuffer (&s->AttribsList);
  1534. FreeGrowBuffer (&s->HotLinkArray);
  1535. FreeGrowList (&s->List);
  1536. if (s->BookmarkTable) {
  1537. HtFree (s->BookmarkTable);
  1538. s->BookmarkTable = NULL;
  1539. }
  1540. MemFree (g_hHeap, 0, s);
  1541. SetWindowLong (hwnd, GWL_USERDATA, 0);
  1542. }
  1543. }
  1544. break;
  1545. }
  1546. return DefWindowProc (hwnd, uMsg, wParam, lParam);
  1547. }
  1548. VOID
  1549. RegisterTextViewer (
  1550. VOID
  1551. )
  1552. {
  1553. static WNDCLASS wc;
  1554. if (!wc.lpfnWndProc) {
  1555. wc.lpfnWndProc = TextViewProc;
  1556. wc.hInstance = g_hInst;
  1557. wc.hCursor = NULL;
  1558. wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
  1559. wc.lpszClassName = S_TEXTVIEW_CLASS;
  1560. RegisterClass (&wc);
  1561. //
  1562. // We don't ever clean this up... and that's OK.
  1563. //
  1564. }
  1565. }
  1566. PCTSTR
  1567. AddLineToTextView (
  1568. HWND hwnd,
  1569. PCTSTR Text
  1570. )
  1571. {
  1572. PCTSTR TextEnd;
  1573. TextEnd = Text + SendMessage (hwnd, WMX_ADDLINE, 0, (LPARAM) Text);
  1574. if (*TextEnd == 0) {
  1575. return NULL;
  1576. }
  1577. return TextEnd;
  1578. }
  1579. VOID
  1580. AddStringToTextView (
  1581. IN HWND hwnd,
  1582. IN PCTSTR String
  1583. )
  1584. {
  1585. PCTSTR p;
  1586. if (!hwnd) {
  1587. return;
  1588. }
  1589. if (String && *String) {
  1590. for (p = String ; p ; p = AddLineToTextView (hwnd, p)) {
  1591. // empty
  1592. }
  1593. }
  1594. }