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.

827 lines
21 KiB

  1. /*** winclip.c - windows clipboard editor extension
  2. *
  3. * Copyright <C> 1988, Microsoft Corporation
  4. *
  5. * Purpose:
  6. * Contains the tglcase function.
  7. *
  8. * Revision History:
  9. *
  10. * 28-Jun-1988 LN Created
  11. * 12-Sep-1988 mz Made WhenLoaded match declaration
  12. *
  13. *************************************************************************/
  14. #include <windows.h>
  15. #include <stdlib.h> /* min macro definition */
  16. #include <string.h> /* prototypes for string fcns */
  17. #undef pascal
  18. #include "zext.h"
  19. #define M_FALSE ((flagType)0)
  20. #define M_TRUE ((flagType)(-1))
  21. #define BUFLEN_MAX (BUFLEN-1)
  22. /*
  23. ** Internal function prototypes
  24. */
  25. #ifdef DEBUG
  26. #define DPRINT(p) DoMessage(p)
  27. #else
  28. #define DPRINT(p)
  29. #endif
  30. HWND ghwndClip;
  31. HINSTANCE ghmod;
  32. int gfmtArgType;
  33. void DeleteArg( PFILE pFile, int argType, COL xStart, LINE yStart,
  34. COL xEnd, COL yEnd );
  35. void InsertText( PFILE pFile, LPSTR pszText, DWORD dwInsMode,
  36. COL xStart, LINE yStart );
  37. flagType pascal EXTERNAL WinCutCopy (ARG *pArg, flagType fCut, flagType fClip);
  38. LPSTR EndOfLine( LPSTR psz );
  39. LPSTR EndOfBreak( LPSTR psz );
  40. int ExtendLine( LPSTR psz, int cchSZ, char ch, int cchNew );
  41. /*************************************************************************
  42. **
  43. ** wincopy
  44. ** Toggle the case of alphabetics contaied within the selected argument:
  45. **
  46. ** NOARG - Toggle case of entire current line
  47. ** NULLARG - Toggle case of current line, from cursor to end of line
  48. ** LINEARG - Toggle case of range of lines
  49. ** BOXARG - Toggle case of characters with the selected box
  50. ** NUMARG - Converted to LINEARG before extension is called.
  51. ** MARKARG - Converted to Appropriate ARG form above before extension is
  52. ** called.
  53. **
  54. ** STREAMARG - Not Allowed. Treated as BOXARG
  55. ** TEXTARG - Not Allowed
  56. **
  57. */
  58. flagType
  59. pascal
  60. EXTERNAL
  61. wincopy (
  62. unsigned int argData, /* keystroke invoked with */
  63. ARG *pArg, /* argument data */
  64. flagType fMeta /* indicates preceded by meta */
  65. )
  66. {
  67. return WinCutCopy( pArg, M_FALSE, M_FALSE );
  68. }
  69. flagType
  70. pascal
  71. EXTERNAL
  72. wincut (
  73. unsigned int argData, /* keystroke invoked with */
  74. ARG *pArg, /* argument data */
  75. flagType fMeta /* indicates preceded by meta */
  76. )
  77. {
  78. return WinCutCopy( pArg, M_TRUE, fMeta );
  79. }
  80. flagType
  81. pascal
  82. EXTERNAL
  83. WinCutCopy (
  84. ARG *pArg,
  85. flagType fCut,
  86. flagType fNoClip
  87. )
  88. {
  89. PFILE pFile; /* file handle of current file */
  90. COL xStart, xEnd;
  91. LINE yStart, yEnd;
  92. char achLine[BUFLEN];
  93. HANDLE hText;
  94. LPSTR pszText;
  95. int iLine, cchLine;
  96. flagType fRet = M_TRUE;
  97. int argSave, argType;
  98. pFile = FileNameToHandle ("", "");
  99. argSave = argType = pArg->argType;
  100. switch ( argType ) {
  101. case BOXARG: /* case switch box */
  102. xStart = pArg->arg.boxarg.xLeft;
  103. xEnd = pArg->arg.boxarg.xRight + 1;
  104. yStart = pArg->arg.boxarg.yTop;
  105. yEnd = pArg->arg.boxarg.yBottom + 1;
  106. /* At this point...
  107. * [xy]Start is Inclusive, [xy]End is EXCLUSIVE of the box arg
  108. */
  109. #ifdef DEBUG
  110. wsprintf( achLine, " BoxDims : %d %d %d %d ", (int)xStart, (int)yStart, (int)xEnd, (int)yEnd);
  111. DoMessage( achLine );
  112. #endif
  113. break;
  114. case NOARG:
  115. /* convert NOARG to a STREAMARG on whole current line */
  116. argType = STREAMARG;
  117. argSave = LINEARG;
  118. xStart = 0;
  119. yStart = pArg->arg.noarg.y;
  120. xEnd = 0;
  121. yEnd = yStart + 1;
  122. break;
  123. case TEXTARG:
  124. /*
  125. * Text args are only for real text. NumArgs and MarkArgs are
  126. * converted to stream or box args by the editor since we say
  127. * we accept NUMARG and MARKARG during initialization.
  128. */
  129. argType = STREAMARG;
  130. argSave = STREAMARG;
  131. xStart = pArg->arg.textarg.x;
  132. xEnd = lstrlen(pArg->arg.textarg.pText) + xStart;
  133. yStart = yEnd = pArg->arg.textarg.y;
  134. break;
  135. case LINEARG: /* case switch line range */
  136. /* convert LINEARG to a STREAMARG so we don't get lots of white space*/
  137. argType = STREAMARG;
  138. xStart = 0;
  139. xEnd = 0;
  140. yStart = pArg->arg.linearg.yStart;
  141. yEnd = pArg->arg.linearg.yEnd + 1;
  142. #ifdef DEBUG
  143. wsprintf( achLine, " LineDims : %d %d %d %d ", (int)xStart, (int)yStart, (int)xEnd, (int)yEnd);
  144. DoMessage( achLine );
  145. #endif
  146. /* At this point...
  147. * [xy]Start is Inclusive, [xy]End is EXCLUSIVE of the line arg
  148. */
  149. break;
  150. case STREAMARG:
  151. /*
  152. * Set Start == first char pos in stream, End == first char pos
  153. * AFTER stream.
  154. */
  155. xStart = pArg->arg.streamarg.xStart;
  156. xEnd = pArg->arg.streamarg.xEnd;
  157. yStart = pArg->arg.streamarg.yStart;
  158. yEnd = pArg->arg.streamarg.yEnd;
  159. #ifdef DEBUG
  160. wsprintf( achLine, " StreamDims : %d %d %d %d ", (int)xStart, (int)yStart, (int)xEnd, (int)yEnd);
  161. DoMessage( achLine );
  162. #endif
  163. break;
  164. default:
  165. #ifdef DEBUG
  166. wsprintf( achLine, " Unknown Arg: 0x%04x", argType );
  167. DoMessage( achLine );
  168. return M_TRUE;
  169. #endif
  170. return M_FALSE;
  171. }
  172. if (!fNoClip) {
  173. if (argType == STREAMARG) {
  174. int cch = 0;
  175. int iChar;
  176. for ( iLine = yStart; iLine <= yEnd; iLine++ )
  177. cch += GetLine (iLine, achLine, pFile) + 3;
  178. hText = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cch);
  179. if (hText == NULL) {
  180. DoMessage( " winclip: Out of Memory" );
  181. return M_FALSE;
  182. }
  183. pszText = GlobalLock(hText);
  184. iChar = xStart;
  185. for ( iLine = yStart; iLine < yEnd; iLine++ ) {
  186. cchLine = GetLine (iLine, achLine, pFile);
  187. /* Incase we start after the end of the line */
  188. if (cchLine < iChar)
  189. cch = 0;
  190. else
  191. cch = cchLine - iChar;
  192. CopyMemory(pszText, &achLine[iChar], cch);
  193. pszText += cch;
  194. strcpy( pszText, "\r\n" );
  195. pszText += 2;
  196. iChar = 0;
  197. }
  198. /* Get partial last line */
  199. if (xEnd != 0) {
  200. cchLine = GetLine (iLine, achLine, pFile);
  201. /* if line is short, then pad it out */
  202. cchLine = ExtendLine( achLine, cchLine, ' ', xEnd );
  203. if (cchLine < iChar)
  204. cchLine = 0;
  205. else
  206. cchLine = xEnd - iChar;
  207. CopyMemory(pszText, &achLine[iChar], cchLine);
  208. pszText += cchLine;
  209. }
  210. } else {
  211. LINE iLine;
  212. int cchBox = xEnd - xStart;
  213. hText = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
  214. (yEnd - yStart) * (cchBox + 3));
  215. if (hText == NULL) {
  216. DoMessage( " winclip: Out of Memory" );
  217. return M_FALSE;
  218. }
  219. pszText = GlobalLock(hText);
  220. for ( iLine = yStart; iLine < yEnd; iLine++ ) {
  221. cchLine = GetLine (iLine, achLine, pFile);
  222. if (argType == BOXARG)
  223. cchLine = ExtendLine( achLine, cchLine, ' ', xEnd );
  224. if (cchLine < xStart )
  225. cchLine = 0;
  226. else
  227. cchLine -= xStart;
  228. cchLine = min(cchLine, cchBox);
  229. CopyMemory(pszText, &achLine[xStart], cchLine);
  230. pszText += cchLine;
  231. strcpy( pszText, "\r\n" );
  232. pszText += 2;
  233. }
  234. }
  235. *pszText = '\0';
  236. GlobalUnlock(hText);
  237. if (OpenClipboard(ghwndClip)) {
  238. EmptyClipboard();
  239. /*
  240. * Set the text into the clipboard
  241. */
  242. if (SetClipboardData(CF_TEXT, hText) == hText) {
  243. /*
  244. * Remember the Arg type for pasting back
  245. */
  246. if (gfmtArgType != 0) {
  247. DWORD *pdw;
  248. HANDLE hArgType = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
  249. sizeof(DWORD));
  250. if (hArgType != NULL && (pdw = GlobalLock(hArgType)) != NULL) {
  251. *pdw = (DWORD)(argSave);
  252. GlobalUnlock(hArgType);
  253. SetClipboardData(gfmtArgType, hArgType);
  254. }
  255. }
  256. } else {
  257. /* An error occured writing text to clipboard */
  258. wsprintf(achLine, " winclip: Error (%ld) setting data",
  259. GetLastError());
  260. DoMessage( achLine );
  261. fRet = M_FALSE;
  262. }
  263. CloseClipboard();
  264. }
  265. }
  266. /*
  267. * No need to free the handle, USER32 will do it (yes it keeps
  268. * track of the client side handle) when we set the next clipboard
  269. * data. (Love that Win3.1 compatibility!)
  270. */
  271. if (fRet && fCut)
  272. DeleteArg( pFile, argType, xStart, yStart, xEnd, yEnd );
  273. return fRet;
  274. }
  275. /*************************************************************************
  276. **
  277. ** winpaste
  278. ** Toggle the case of alphabetics contaied within the selected argument:
  279. **
  280. ** NOARG - Toggle case of entire current line
  281. ** NULLARG - Toggle case of current line, from cursor to end of line
  282. ** LINEARG - Toggle case of range of lines
  283. ** BOXARG - Toggle case of characters with the selected box
  284. ** NUMARG - Converted to LINEARG before extension is called.
  285. ** MARKARG - Converted to Appropriate ARG form above before extension is
  286. ** called.
  287. **
  288. ** STREAMARG - Not Allowed. Treated as BOXARG
  289. ** TEXTARG - Not Allowed
  290. **
  291. */
  292. flagType
  293. pascal
  294. EXTERNAL
  295. winpaste (
  296. unsigned int argData, /* keystroke invoked with */
  297. ARG *pArg, /* argument data */
  298. flagType fMeta /* indicates preceded by meta */
  299. )
  300. {
  301. PFILE pFile; /* file handle of current file */
  302. COL xStart, xEnd;
  303. LINE yStart, yEnd;
  304. int argType;
  305. UINT fmtData = CF_TEXT;
  306. DWORD dwInsMode = STREAMARG;
  307. HANDLE hText;
  308. LPSTR pszText;
  309. /*
  310. * Get the clipboard text and insertion type
  311. */
  312. if (pArg->argType == TEXTARG) {
  313. int i, j;
  314. char achLine[3 + 1 + 3 + 1 + 1 + BUFLEN + 1 + 1 + 5 + 1];
  315. char *p;
  316. /*
  317. * Quick hack to make text arg pastes work like the do in Z
  318. */
  319. j = pArg->arg.textarg.cArg;
  320. if (j > 2)
  321. j = 2;
  322. achLine[0] = '\0';
  323. for ( i = 0; i < j; i++ )
  324. lstrcat(achLine, "arg ");
  325. p = achLine + lstrlen(achLine);
  326. wsprintf( p, "\"%s\" paste", pArg->arg.textarg.pText );
  327. return fExecute( achLine );
  328. }
  329. /* if no text then return FALSE */
  330. if (!IsClipboardFormatAvailable(fmtData)) {
  331. /* No text, try display text */
  332. fmtData = CF_DSPTEXT;
  333. if (!IsClipboardFormatAvailable(fmtData)) {
  334. /* bummer! no text at all, return FALSE */
  335. DoMessage( " winclip: invalid clipboard format" );
  336. return M_FALSE;
  337. }
  338. }
  339. if (!OpenClipboard(ghwndClip))
  340. return M_FALSE;
  341. hText = GetClipboardData(fmtData);
  342. if (hText == NULL || (pszText = GlobalLock(hText)) == NULL) {
  343. CloseClipboard();
  344. return M_FALSE;
  345. }
  346. /* Get insert mode */
  347. if (IsClipboardFormatAvailable(gfmtArgType)) {
  348. DWORD *pdw;
  349. HANDLE hInsMode;
  350. hInsMode = GetClipboardData(gfmtArgType);
  351. if (hInsMode != NULL && (pdw = GlobalLock(hInsMode)) != NULL) {
  352. dwInsMode = *pdw;
  353. GlobalUnlock(hInsMode);
  354. }
  355. }
  356. pFile = FileNameToHandle ("", "");
  357. argType = pArg->argType;
  358. switch ( argType ) {
  359. case BOXARG: /* case switch box */
  360. /*
  361. * Set [xy]Start inclusive of box arg,
  362. * [xy]End exclusive of box arg.
  363. */
  364. xStart = pArg->arg.boxarg.xLeft;
  365. xEnd = pArg->arg.boxarg.xRight + 1;
  366. yStart = pArg->arg.boxarg.yTop;
  367. yEnd = pArg->arg.boxarg.yBottom + 1;
  368. break;
  369. case LINEARG: /* case switch line range */
  370. /*
  371. * Set [xy]Start inclusive of line arg,
  372. * [xy]End exclusive of line arg.
  373. */
  374. xStart = 0;
  375. xEnd = BUFLEN + 1;
  376. yStart = pArg->arg.linearg.yStart;
  377. yEnd = pArg->arg.linearg.yEnd + 1;
  378. break;
  379. case STREAMARG:
  380. /*
  381. * Set [xy]Start inclusive of stream
  382. * xEnd is EXCLUSIVE of stream
  383. * yEnd is INCLUSIVE of stream
  384. */
  385. xStart = pArg->arg.streamarg.xStart;
  386. xEnd = pArg->arg.streamarg.xEnd;
  387. yStart = pArg->arg.streamarg.yStart;
  388. yEnd = pArg->arg.streamarg.yEnd;
  389. break;
  390. case NOARG:
  391. xStart = pArg->arg.noarg.x;
  392. xEnd = xStart + 1;
  393. yStart = pArg->arg.noarg.y;
  394. yEnd = yStart + 1;
  395. break;
  396. default:
  397. GlobalUnlock(hText);
  398. CloseClipboard();
  399. return M_FALSE;
  400. }
  401. /*
  402. * Delete any selection
  403. */
  404. DeleteArg( pFile, argType, xStart, yStart, xEnd, yEnd );
  405. /*
  406. * Insert new text with correct mode
  407. */
  408. InsertText( pFile, pszText, dwInsMode, xStart, yStart );
  409. GlobalUnlock(hText);
  410. CloseClipboard();
  411. return M_TRUE;
  412. }
  413. /*************************************************************************
  414. **
  415. ** windel
  416. **
  417. **
  418. */
  419. flagType
  420. pascal
  421. EXTERNAL
  422. windel (
  423. unsigned int argData, /* keystroke invoked with */
  424. ARG *pArg, /* argument data */
  425. flagType fMeta /* indicates preceded by meta */
  426. )
  427. {
  428. int argType = pArg->argType;
  429. if (argType == NOARG)
  430. return fExecute("delete");
  431. if (argType == NULLARG) {
  432. int c, x, y;
  433. c = pArg->arg.nullarg.cArg;
  434. x = pArg->arg.nullarg.x;
  435. y = pArg->arg.nullarg.y;
  436. pArg->argType = STREAMARG;
  437. pArg->arg.streamarg.xStart = x;
  438. pArg->arg.streamarg.xEnd = 0;
  439. pArg->arg.streamarg.yStart = y;
  440. pArg->arg.streamarg.yEnd = y + 1;
  441. pArg->arg.streamarg.cArg = c;
  442. }
  443. return WinCutCopy (pArg, M_TRUE, fMeta);
  444. }
  445. /*************************************************************************
  446. **
  447. ** WhenLoaded
  448. ** Executed when extension gets loaded. Identify self & assign default
  449. ** keystroke.
  450. **
  451. ** Entry:
  452. ** none
  453. */
  454. void
  455. winclipWhenLoaded ()
  456. {
  457. #if 0
  458. WNDCLASS wc;
  459. ghmod = GetModuleHandle(NULL);
  460. wc.style = 0;
  461. wc.lpfnWndProc = (WNDPROC)DefWindowProc;
  462. wc.cbClsExtra = 0;
  463. wc.cbWndExtra = 0;
  464. wc.hInstance = ghmod;
  465. wc.hIcon = NULL;
  466. wc.hCursor = NULL;
  467. wc.hbrBackground = NULL;
  468. wc.lpszMenuName = NULL; /* Name of menu resource in .RC file. */
  469. wc.lpszClassName = "WinClipWClass"; /* Name used in call to CreateWindow. */
  470. if (RegisterClass(&wc) && (ghwndClip = CreateWindow( "WinClipWClass",
  471. "ClipWindow", WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL,
  472. ghmod, NULL)) == NULL ) {
  473. DoMessage( " winclip: Initialization failed!" );
  474. }
  475. #else
  476. ghwndClip = NULL; //assign clipboard to this thread instead
  477. #endif
  478. gfmtArgType = RegisterClipboardFormat( "Z Arg Type" );
  479. }
  480. void
  481. DeleteArg(
  482. PFILE pFile,
  483. int argType,
  484. COL xStart,
  485. LINE yStart,
  486. COL xEnd,
  487. COL yEnd
  488. )
  489. {
  490. switch ( argType ) {
  491. case STREAMARG:
  492. DelStream(pFile, xStart, yStart, xEnd, yEnd);
  493. break;
  494. case LINEARG:
  495. DelStream(pFile, 0, yStart, 0, yEnd);
  496. break;
  497. case BOXARG: {
  498. LINE iLine;
  499. for ( iLine = yStart; iLine < yEnd; iLine++ ) {
  500. DelStream( pFile, xStart, iLine, xEnd, iLine );
  501. }
  502. break;
  503. }
  504. default:
  505. break;
  506. }
  507. }
  508. void
  509. InsertText(
  510. PFILE pFile,
  511. LPSTR pszText,
  512. DWORD dwInsMode,
  513. COL xStart,
  514. LINE yStart
  515. )
  516. {
  517. char ch;
  518. int cchLine, cchText, cchCopy;
  519. LPSTR pszNL;
  520. char achLine[BUFLEN];
  521. char achEnd[BUFLEN];
  522. switch ( dwInsMode ) {
  523. case STREAMARG:
  524. /*
  525. * Split current line,
  526. * tack first line from buffer to end of new line
  527. * put the new lines in file
  528. * shove the last line to the beggining of the 2nd half of the line
  529. */
  530. DPRINT( " Stream Paste" );
  531. if ( *pszText == '\0' )
  532. break;
  533. pszNL = EndOfLine(pszText);
  534. cchLine = GetLine( yStart, achLine, pFile );
  535. if (cchLine < xStart)
  536. cchLine = ExtendLine( achLine, cchLine, ' ', xStart );
  537. cchText = (int)(pszNL - pszText);
  538. if (xStart + cchText >= BUFLEN_MAX) {
  539. cchText = BUFLEN_MAX - xStart;
  540. pszNL = pszText + cchText;
  541. }
  542. strcpy( achEnd, &achLine[xStart] );
  543. cchLine -= xStart;
  544. CopyMemory( &achLine[xStart], pszText, cchText );
  545. cchText += xStart;
  546. achLine[cchText] = '\0';
  547. while ( *pszNL ) {
  548. PutLine( yStart++, achLine, pFile );
  549. CopyLine( NULL, pFile, 0, 0, yStart );
  550. pszText = EndOfBreak(pszNL);
  551. pszNL = EndOfLine(pszText);
  552. cchText = (int)(pszNL - pszText);
  553. CopyMemory( achLine, pszText, cchText );
  554. achLine[cchText] = '\0';
  555. }
  556. cchCopy = 0;
  557. if (cchLine + cchText > BUFLEN_MAX) {
  558. cchCopy = (cchLine + cchText) - BUFLEN_MAX;
  559. cchLine = cchLine - cchCopy;
  560. }
  561. CopyMemory( &achLine[cchText], achEnd, cchLine );
  562. achLine[cchLine+cchText] = '\0';
  563. PutLine( yStart++, achLine, pFile );
  564. if (cchCopy != 0) {
  565. CopyLine( NULL, pFile, 0, 0, yStart );
  566. CopyMemory( achLine, &achEnd[cchLine], cchCopy );
  567. achLine[cchCopy] = '\0';
  568. PutLine( yStart++, achLine, pFile);
  569. }
  570. break;
  571. case BOXARG:
  572. /*
  573. * Insert the text as a block into the middle of each line.
  574. * This could be tricky since we need to pad all short lines
  575. * out with spaces to match the lenght of the longest line
  576. * in the text.
  577. */
  578. DPRINT( " Box Paste" );
  579. while ( *pszText ) {
  580. pszNL = EndOfLine(pszText);
  581. cchLine = GetLine( yStart, achLine, pFile );
  582. if (cchLine < xStart)
  583. cchLine = ExtendLine( achLine, cchLine, ' ', xStart );
  584. cchText = (int)(pszNL - pszText);
  585. if (cchLine + cchText > BUFLEN_MAX)
  586. cchText = BUFLEN_MAX - cchLine;
  587. /* insert text in middle of line */
  588. strcpy( achEnd, &achLine[xStart] );
  589. CopyMemory( &achLine[xStart], pszText, cchText );
  590. strcpy( &achLine[xStart + cchText], achEnd );
  591. /* put line in file */
  592. PutLine( yStart++, achLine, pFile );
  593. pszText = EndOfBreak(pszNL);
  594. }
  595. break;
  596. case LINEARG:
  597. /*
  598. * shove the lines in the buffer before the current line
  599. */
  600. DPRINT( " Line Paste" );
  601. while ( *pszText ) {
  602. pszNL = EndOfLine(pszText);
  603. ch = *pszNL;
  604. *pszNL = '\0';
  605. CopyLine( NULL, pFile, 0, 0, yStart );
  606. PutLine( yStart++, pszText, pFile);
  607. *pszNL = ch;
  608. pszText = EndOfBreak(pszNL);
  609. }
  610. break;
  611. default:
  612. break;
  613. }
  614. }
  615. LPSTR
  616. EndOfLine(
  617. LPSTR psz
  618. )
  619. {
  620. int c;
  621. c = 0;
  622. while ( *psz && *psz != '\r' && *psz != '\n' && c++ < BUFLEN_MAX )
  623. psz++;
  624. return psz;
  625. }
  626. LPSTR
  627. EndOfBreak(
  628. LPSTR psz
  629. )
  630. {
  631. char chSkip;
  632. switch ( *psz ) {
  633. case '\r':
  634. chSkip = '\n';
  635. break;
  636. case '\n':
  637. chSkip = '\r';
  638. break;
  639. default:
  640. return psz;
  641. }
  642. if (*(++psz) == chSkip)
  643. psz++;
  644. return psz;
  645. }
  646. int
  647. ExtendLine(
  648. LPSTR psz,
  649. int cchLine,
  650. char ch,
  651. int cchTotal
  652. )
  653. {
  654. if ( cchLine >= cchTotal )
  655. return cchLine;
  656. if (cchTotal > BUFLEN_MAX)
  657. cchTotal = BUFLEN_MAX;
  658. psz = &psz[cchLine];
  659. while ( cchLine++ < cchTotal )
  660. *psz++ = ch;
  661. *psz = '\0';
  662. return cchLine;
  663. }