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.

722 lines
16 KiB

  1. /************************************************************/
  2. /* Windows Write, Copyright 1985-1992 Microsoft Corporation */
  3. /************************************************************/
  4. /* MainLoop.c -- WRITE's main message loop */
  5. #define NOGDICAPMASKS
  6. //#define NOCTLMGR
  7. #define NOVIRTUALKEYCODES
  8. #define NOWINSTYLES
  9. #define NOKEYSTATE
  10. #define NOSYSCOMMANDS
  11. #define NOCREATESTRUCT
  12. #define NODRAWTEXT
  13. #define NOMB
  14. #define NOOPENFILE
  15. #define NOPEN
  16. #define NOSOUND
  17. #define NOWH
  18. #define NOWINOFFSETS
  19. #define NOWNDCLASS
  20. #define NOCOMM
  21. #define NOFONT
  22. #define NOGDI
  23. #define NOBRUSH
  24. #define NOATOM
  25. #define NOSCROLL
  26. #define NOCOLOR
  27. #include <windows.h>
  28. #define NOUAC
  29. #include "mw.h"
  30. #include "cmddefs.h"
  31. #include "ch.h"
  32. #include "docdefs.h"
  33. #include "fmtdefs.h"
  34. #include "dispdefs.h"
  35. #include "printdef.h"
  36. #include "wwdefs.h"
  37. #include "propdefs.h"
  38. #include "filedefs.h"
  39. #define NOSTRUNDO
  40. #define NOSTRERRORS
  41. #include "str.h"
  42. #include "preload.h"
  43. extern CHAR (*rgbp)[cbSector];
  44. extern CHAR *rgibpHash;
  45. extern struct BPS *mpibpbps;
  46. extern int ibpMax;
  47. extern int iibpHashMax;
  48. extern struct DOD (**hpdocdod)[];
  49. extern int docCur;
  50. extern int visedCache;
  51. extern typeCP cpMinDocument;
  52. extern struct WWD rgwwd[];
  53. extern int wwCur;
  54. extern struct FLI vfli;
  55. extern struct WWD *pwwdCur;
  56. extern int docMode;
  57. extern CHAR stMode[];
  58. extern int isedMode;
  59. extern int vdocPageCache;
  60. extern typeCP vcpMinPageCache;
  61. extern typeCP vcpMacPageCache;
  62. extern int vipgd;
  63. extern int vfInsLast;
  64. extern struct SEP vsepAbs;
  65. extern struct DOD (**hpdocdod)[];
  66. extern int vfSelHidden;
  67. extern struct SEL selCur;
  68. extern int vfAwfulNoise;
  69. extern HWND vhWndPageInfo;
  70. extern int vfSeeSel;
  71. extern int vipgd;
  72. extern int vfInsEnd; /* Is insert point at end-of-line? */
  73. extern int vfModeIsFootnote; /* true when szMode contains string "Footnote" */
  74. /* used by ShowMode */
  75. extern int docMode;
  76. static int isedMode = iNil;
  77. static int ipgdMode = iNil;
  78. extern CHAR szMode[];
  79. extern HCURSOR vhcIBeam;
  80. #ifdef DBCS
  81. extern int donteat; /* disp.c : if TRUE vmsgLast has msg */
  82. #endif
  83. static int vfSizeMode = false;
  84. int vcCount = 1; /* count to be decremented until 0 before trying to grow rgbp */
  85. NEAR FNeedToGrowRgbp(void);
  86. MainLoop()
  87. {
  88. extern int vfIconic;
  89. extern int vfDead;
  90. extern int vfDeactByOtherApp;
  91. extern MSG vmsgLast;
  92. extern int vfDiskFull;
  93. extern int ferror;
  94. extern HWND hParentWw;
  95. extern HANDLE vhAccel; /* handle to accelerator table */
  96. extern HWND vhDlgFind, vhDlgRunningHead, vhDlgChange;
  97. while (TRUE)
  98. {
  99. if (!vfDeactByOtherApp && !vfIconic && !vfDead &&
  100. !FImportantMsgPresent())
  101. { /* Neither an icon nor a dying ember -- perform background
  102. tasks like screen update, showing selection, etc. */
  103. Idle();
  104. }
  105. /* We are done Idling or there's a message waiting for us */
  106. #ifdef DBCS
  107. if ( donteat ) {
  108. /* We have already get message */
  109. donteat = FALSE;
  110. }
  111. else {
  112. if (!GetMessage( (LPMSG)&vmsgLast, NULL, 0, 0 ))
  113. {
  114. /* Terminating the app; return from WinMain */
  115. LTerm:
  116. break;
  117. }
  118. }
  119. #else
  120. if (!GetMessage( (LPMSG)&vmsgLast, NULL, 0, 0 ))
  121. {
  122. /* Terminating the app; return from WinMain */
  123. LTerm:
  124. break;
  125. }
  126. #endif
  127. /* Reset disk full error flag */
  128. vfDiskFull = false;
  129. ferror = false;
  130. #if WINVER >= 0x300
  131. if (hParentWw == NULL)
  132. /* Odd shut-down condition where we've hParentWw has been
  133. invalidated without our genuine knowledge and thus RIP's */
  134. goto LTerm;
  135. #endif
  136. /* Handle modeless dialog box messages thru IsDialogMessage. */
  137. if (
  138. !(vhDlgFind != NULL && IsDialogMessage(vhDlgFind, &vmsgLast))
  139. && !(vhDlgChange != NULL && IsDialogMessage(vhDlgChange, &vmsgLast))
  140. && !(vhDlgRunningHead != NULL && IsDialogMessage(vhDlgRunningHead,&vmsgLast))
  141. && !(TranslateAccelerator(hParentWw, vhAccel, &vmsgLast))
  142. )
  143. {
  144. int kc;
  145. /* Even if we process the toggle key, still want to translate it */
  146. if (FCheckToggleKeyMessage( &vmsgLast ))
  147. {
  148. goto Translate;
  149. }
  150. if ( ((kc = KcAlphaKeyMessage( &vmsgLast )) != kcNil) &&
  151. (kc != kcAlphaVirtual) )
  152. {
  153. #ifdef CYCLESTOBURN
  154. PreloadCodeTsk( tskInsert );
  155. #endif
  156. AlphaMode( kc );
  157. }
  158. else if (!FNonAlphaKeyMessage( &vmsgLast, TRUE ))
  159. {
  160. Translate:
  161. TranslateMessage( (LPMSG)&vmsgLast);
  162. DispatchMessage((LPMSG)&vmsgLast);
  163. }
  164. }
  165. } /* end while (TRUE) */
  166. }
  167. /* I D L E */
  168. #ifdef DEBUG
  169. int vfValidateCode;
  170. #endif
  171. Idle()
  172. { /* Idle routine -- do background processing things */
  173. extern int vfOutOfMemory;
  174. extern int ibpMaxFloat;
  175. extern int vfLargeSys;
  176. extern int vfDeactByOtherApp;
  177. typeCP cpEdge;
  178. int cdr;
  179. #ifdef DEBUG
  180. extern int fIbpCheck;
  181. extern int fPctbCheck;
  182. int fIbpT=fIbpCheck;
  183. int fPctbT=fPctbCheck;
  184. fIbpT = fIbpCheck;
  185. fPctbT = fPctbCheck;
  186. fPctbCheck = fIbpCheck = TRUE;
  187. CheckIbp();
  188. CheckPctb();
  189. fIbpCheck = fIbpT;
  190. fPctbCheck = fPctbT;
  191. #endif
  192. vfAwfulNoise = false; /* Re-enable beep */
  193. /* Here is where we attempt to recognize that we have
  194. regained memory and are no longer in an error state */
  195. if (vfOutOfMemory)
  196. {
  197. extern int vfMemMsgReported;
  198. if (FStillOutOfMemory())
  199. {
  200. return;
  201. }
  202. else
  203. {
  204. /* Hooray! We recovered from out-of-memory */
  205. vfOutOfMemory = vfMemMsgReported = FALSE;
  206. }
  207. if (FImportantMsgPresent())
  208. return;
  209. }
  210. /* Make sure we repaint what Windows considers to be invalid */
  211. UpdateInvalid();
  212. UpdateDisplay(true);
  213. if (wwdCurrentDoc.fDirty)
  214. /* Update was interrupted */
  215. return;
  216. Assert( wwCur >= 0 );
  217. {
  218. extern int vfSeeEdgeSel;
  219. int dlMac = pwwdCur->dlMac;
  220. struct EDL *pedl = &(**(pwwdCur->hdndl))[dlMac - 1];
  221. cpEdge = CpEdge();
  222. if ( vfSeeSel &&
  223. (vfSeeEdgeSel || (selCur.cpFirst == selCur.cpLim) ||
  224. (selCur.cpLim <= pwwdCur->cpFirst) ||
  225. (selCur.cpFirst >= pedl->cpMin + pedl->dcpMac)) )
  226. {
  227. extern int vfInsEnd;
  228. if (vfInsEnd)
  229. /* Adjust for insert point at end of line */
  230. cpEdge--;
  231. cpEdge = max(0, cpEdge); /* make sure cpEdge is at least 0 */
  232. if (selCur.cpFirst == selCur.cpLim)
  233. ClearInsertLine();
  234. PutCpInWwHz(cpEdge);
  235. if (FImportantMsgPresent())
  236. return;
  237. }
  238. vfSeeSel = vfInsLast = vfSeeEdgeSel = false;
  239. #ifdef DEBUG
  240. if (vfValidateCode)
  241. ValidateCodeSegments(); /* Special kernel call to test checksums */
  242. #endif
  243. if (vfSelHidden && !vfDeactByOtherApp)
  244. { /* Turn on selection highlight */
  245. vfInsEnd = selCur.fEndOfLine;
  246. vfSelHidden = false;
  247. ToggleSel(selCur.cpFirst, selCur.cpLim, true);
  248. if (FImportantMsgPresent())
  249. return;
  250. }
  251. if (!vfSizeMode)
  252. {
  253. CheckMode();
  254. if (FImportantMsgPresent())
  255. return;
  256. }
  257. }
  258. #define cbpIncr 5
  259. if (--vcCount == 0)
  260. {
  261. #ifdef DEBUG
  262. dummy(); /* So Chi-Chuen can set a breakpoint here */
  263. #endif
  264. UnlockData(0);
  265. if ( GlobalCompact((DWORD)0) >= (DWORD)LCBAVAIL )
  266. {
  267. vfLargeSys = TRUE;
  268. ibpMaxFloat = 255; /* about 32K for rgbp */
  269. }
  270. else
  271. {
  272. vfLargeSys = FALSE;
  273. ibpMaxFloat = 75; /* about 10K for rgbp */
  274. }
  275. LockData(0);
  276. /* after adjustment, ibpMaxFloat may be smaller than current ibpMax
  277. but we will not grow rgbp anymore and rgbp will be reduced eventually
  278. when we need more heap space */
  279. if ( ibpMax < ibpMaxFloat && FNeedToGrowRgbp() )
  280. if (!FGrowRgbp(cbpIncr))
  281. FGrowRgbp(1);
  282. if (FImportantMsgPresent())
  283. return;
  284. }
  285. CloseEveryRfn( FALSE ); /* Close files on removable media */
  286. #ifdef CYCLESTOBURN
  287. if (vfLargeSys)
  288. { /* Large system, preload code for as much as possible */
  289. int tsk;
  290. for ( tsk = tskMin; tsk < tskMax; tsk++ )
  291. PreloadCodeTsk( tsk );
  292. }
  293. else
  294. /* Small system, preload code for insert only */
  295. PreloadCodeTsk( tskInsert );
  296. #endif
  297. EndLongOp(vhcIBeam);
  298. }
  299. #ifdef DEBUG
  300. dummy()
  301. {
  302. }
  303. #endif
  304. UpdateInvalid()
  305. { /* Find out what Windows considers to be the invalid range of
  306. the current window. Mark it invalid in WRITE's data structures &
  307. blank the area on the screen */
  308. extern HWND hParentWw;
  309. extern long ropErase;
  310. extern int vfDead;
  311. RECT rc;
  312. if ( (pwwdCur->wwptr != NULL) &&
  313. /* Getting the update rect for the parent is essentially the same as
  314. processing any WM_ERASEBKGND messages that might be out there for the
  315. parent. */
  316. (GetUpdateRect( hParentWw, (LPRECT) &rc, TRUE ),
  317. GetUpdateRect( pwwdCur->wwptr, (LPRECT) &rc, TRUE )) &&
  318. /* Check for vfDead is so we don't repaint after we have
  319. officially closed. Check is AFTER GetUpdateRect call so
  320. we DO clear the background and validate the border */
  321. !vfDead )
  322. {
  323. int ypTop = rc.top;
  324. if (ypTop < pwwdCur->ypMin)
  325. { /* Repaint area includes stripe above ypMin -- validate it,
  326. since erasure is the only repaint necessary */
  327. ypTop = pwwdCur->ypMin; /* Only invalidate below ypMin */
  328. /* The above is NOT ensuring that the upper 4 pixel rows
  329. in the text window get cleared, so we use brute force ..pault */
  330. PatBlt(GetDC(pwwdCur->wwptr), 0, 0, pwwdCur->xpMac, pwwdCur->ypMin,
  331. ropErase);
  332. }
  333. if (ypTop < rc.bottom)
  334. {
  335. InvalBand( pwwdCur, ypTop, rc.bottom );
  336. }
  337. /* Since we have found out the invalid rect, and marked it invalid
  338. in our structures, we don't want to hear about it again,
  339. so we tell windows that we have made everything valid */
  340. ValidateRect( pwwdCur->wwptr, (LPRECT) NULL );
  341. }
  342. }
  343. /* C H E C K M O D E */
  344. CheckMode()
  345. {
  346. typeCP cp;
  347. int pgn;
  348. struct PGTB **hpgtb;
  349. CHAR st[30];
  350. CHAR *pch;
  351. #ifdef BOGUS
  352. /* The mode is driven off of the first cp in the window. */
  353. cp = pwwdCur->cpFirst;
  354. #else /* not BOGUS */
  355. /* The mode is driven off of the last cp of the first line in the window. */
  356. {
  357. register struct EDL *pedl = &(**pwwdCur->hdndl)[0];
  358. cp = CpMax(pedl->cpMin + pedl->dcpMac - 1, cp0);
  359. }
  360. #endif /* not BOGUS */
  361. #ifdef CASHMERE
  362. if (cp > CpMacText(docCur)) /* in footnote and running head */
  363. {
  364. SetModeToFootnote();
  365. return;
  366. }
  367. #endif /* CASHMERE */
  368. CacheSect(docCur, cp);
  369. /* If the doc has changed since the last time we entered, or the current cp
  370. is not in the last page that was cached, then cache the current page. */
  371. if (!(vdocPageCache == docCur && cp >= vcpMinPageCache && cp <
  372. vcpMacPageCache))
  373. {
  374. CachePage(docCur, cp);
  375. }
  376. /* If the current doc, ised, and ipgd have not changed then the page number
  377. is the same, so return. */
  378. if (docMode == docCur && isedMode == visedCache && ipgdMode == vipgd)
  379. {
  380. return;
  381. }
  382. /* szMode is going to be set to "Page nnn" or "Pnnn Dnnn". */
  383. vfModeIsFootnote = false;
  384. /* Record the current doc, ised and ipgd. */
  385. docMode = docCur;
  386. isedMode = visedCache;
  387. ipgdMode = vipgd;
  388. /* Retrieve the current page number. */
  389. hpgtb = (**hpdocdod)[docMode].hpgtb;
  390. pgn = (vipgd == iNil) ? ((vsepAbs.pgnStart == pgnNil) ? 1 : vsepAbs.pgnStart)
  391. : (**hpgtb).rgpgd[vipgd].pgn;
  392. #ifdef CASHMERE
  393. /* If the document has multiple sections and we had to set szMode to "Pnnn
  394. Dnnn", then return. */
  395. if ((isedMode != iNil) && (FSetModeForSection(pgn)))
  396. {
  397. return;
  398. }
  399. #endif /* CASHMERE */
  400. /* Place "Page nnn" in szMode and output to mode field of window. */
  401. #if defined(KOREA)
  402. pch = &szMode[0];
  403. *pch++ = chSpace;
  404. ncvtu(pgn, &pch);
  405. *pch++ = chSpace;
  406. FillStId(st, IDSTRChPage, sizeof(st));
  407. bltbyte(&st[1], pch, st[0]+1);
  408. //*pch = '\0';
  409. #else
  410. FillStId(st, IDSTRChPage, sizeof(st));
  411. st[1] = ChUpper(st[1]);
  412. bltbyte(&st[1], szMode, st[0]);
  413. pch = &szMode[st[0]];
  414. *pch++ = chSpace;
  415. ncvtu(pgn, &pch);
  416. *pch = '\0';
  417. #endif
  418. DrawMode();
  419. } /* end CheckMode */
  420. NEAR FNeedToGrowRgbp()
  421. { /* return true iif page buffers are all used up */
  422. register struct BPS *pbps;
  423. struct BPS *pbpsMax = &mpibpbps[ibpMax];
  424. extern int ibpMaxFloat;
  425. vcCount = 512;
  426. if (ibpMax + 1 > ibpMaxFloat)
  427. return(FALSE); /* don't even try if adding one more page will exceed limit */
  428. for (pbps = &mpibpbps[0]; pbps < pbpsMax; pbps++)
  429. {
  430. /* any unused page? */
  431. if (pbps->fn == fnNil)
  432. {
  433. return(FALSE);
  434. }
  435. }
  436. return(TRUE);
  437. }
  438. CachePage(doc,cp)
  439. int doc;
  440. typeCP cp;
  441. {
  442. struct PGTB **hpgtb;
  443. int cpgd;
  444. typeCP cpMacPage;
  445. vdocPageCache = doc;
  446. hpgtb = (**hpdocdod)[doc].hpgtb;
  447. if (hpgtb == 0 || (**hpgtb).cpgd == 0)
  448. {
  449. vcpMinPageCache = cp0;
  450. vcpMacPageCache = cpMax;
  451. vipgd = -1;
  452. return;
  453. }
  454. /* Get index to beginning of NEXT page */
  455. cpgd = (**hpgtb).cpgd;
  456. vipgd = IcpSearch(cp+1, &((**hpgtb).rgpgd[0]), sizeof(struct PGD),
  457. bcpPGD, cpgd);
  458. cpMacPage = (**hpgtb).rgpgd[vipgd].cpMin;
  459. if (cp >= cpMacPage)
  460. { /* Last page */
  461. vcpMinPageCache = cpMacPage;
  462. vcpMacPageCache = (**hpdocdod)[doc].cpMac + 1;
  463. }
  464. else
  465. {
  466. vcpMinPageCache = (vipgd == 0) ? cpMinDocument : (**hpgtb).rgpgd[vipgd - 1].cpMin;
  467. vcpMacPageCache = cpMacPage;
  468. vipgd -= 1; /* so that ShowMode can get correct pgn */
  469. }
  470. }
  471. #ifdef CASHMERE
  472. /* A D D V I S I S P A C E S */
  473. AddVisiSpaces(ww, pedl, dypBaseline, dypFontSize)
  474. int ww;
  475. struct EDL *pedl; /* Do no heap movement in this subroutine */
  476. int dypBaseline, dypFontSize;
  477. {
  478. /* Put a centered dot in each space character, and show all tabs */
  479. int ich;
  480. struct WWD *pwwd = &rgwwd[ww];
  481. int xpPos = vfli.xpLeft + xpSelBar - pwwd->xpMin;
  482. int ypPos;
  483. WORDPTR bitsDest = pwwd->wwptr + (long)STRUCIDX(portBits);
  484. RECT rcDest;
  485. int xpRightReal = vfli.xpRight - pwwd->xpMin;
  486. extern BITPAT patVisiTab;
  487. BITMAP bmap;
  488. ypPos = pedl->yp - dypBaseline - dypFontSize / 4;
  489. rcDest.bottom = ypPos + 4;
  490. rcDest.top = rcDest.bottom - 8;
  491. SetRect(&bmap.bounds, 8, 0, 16, 8);
  492. bmap.rowBytes = 2;
  493. bmap.baseAddr = MPLP(&patVisiTab);
  494. PenSize(1, 1);
  495. PenMode(patXor);
  496. for (ich = 0; ich < vfli.ichMac; ++ich)
  497. {
  498. switch(vfli.rgch[ich])
  499. {
  500. case chSpace:
  501. MoveTo(xpPos + vfli.rgdxp[ich] / 2, ypPos);
  502. Line(0, 0);
  503. break;
  504. case chTab:
  505. rcDest.left = xpPos - 1;
  506. rcDest.right = rcDest.left + 8;
  507. CopyBits(MPLP(&bmap), bitsDest, &(bmap.bounds),
  508. &rcDest, srcXor, 0l);
  509. }
  510. xpPos += vfli.rgdxp[ich];
  511. }
  512. }
  513. #endif /* CASHMERE */
  514. #ifdef ENABLE
  515. /* F S E T M O D E F O R S E C T I O N */
  516. FSetModeForSection(pgn)
  517. int pgn; /* pgn is the current page number */
  518. {
  519. struct SETB *psetb;
  520. struct SED *psed;
  521. int cch, sectn;
  522. CHAR *pch;
  523. #ifdef DEBUG
  524. Assert(HsetbGet(docMode) != 0);
  525. #endif /* DEBUG*/
  526. psetb = *HsetbGet(docMode);
  527. psed = psetb->rgsed;
  528. /* Decide if a mode string of the form "Pnnn Dnnn" needs to be */
  529. /* displayed. If no, just return. If yes, derive the section # */
  530. if(psed->cp == CpMacText(docMode))
  531. return(FALSE);
  532. else
  533. {
  534. if (psetb->csed <= 1)
  535. return(FALSE);
  536. sectn = isedMode + 1;
  537. }
  538. /* Place "Pnnn Dnnn" in stMode and output to window */
  539. pch = &stMode[1];
  540. *pch++ = chPnMode;
  541. ncvtu(pgn,&pch);
  542. *pch++ = chSpace;
  543. *pch++ = chDivMode;
  544. ncvtu(sectn,&pch);
  545. stMode[0] = pch - stMode - 1;
  546. DrawMode();
  547. return(TRUE);
  548. }
  549. #endif /* ENABLE */
  550. #ifdef CASHMERE
  551. Visify(pch, pcch)
  552. CHAR *pch;
  553. int *pcch;
  554. { /* Transform chars to "Visible font" */
  555. CHAR *pchT = pch;
  556. int cch = *pcch;
  557. while (cch--)
  558. {
  559. if ((*pchT = ChVisible(*pch++)) != 0)
  560. pchT++;
  561. else
  562. --(*pcch);
  563. }
  564. }
  565. #endif /* CASHMERE */
  566. #ifdef CASHMERE
  567. int ChVisible(ch)
  568. int ch;
  569. { /* Return "visible font" for ch */
  570. switch (ch)
  571. {
  572. #ifdef CRLF
  573. case chReturn:
  574. return 0; /* chNil won't fit into a byte */
  575. #endif
  576. case chNRHFile: return chHyphen;
  577. case chNewLine: return chVisNewLine;
  578. case chEol: return chVisEol;
  579. case chTab: return chVisTab;
  580. case chSect: return chVisSect;
  581. default:
  582. return ch;
  583. }
  584. }
  585. #endif /* CASHMERE */
  586. #ifdef CYCLESTOBURN
  587. void PreloadCodeTsk( tsk )
  588. int tsk;
  589. {
  590. switch (tsk) {
  591. case tskInsert:
  592. LoadF( IbpMakeValid ); /* FILE.C */
  593. LoadF( MoveLeftRight ); /* CURSKEYS.C */
  594. LoadF( CtrBackDypCtr ); /* SCROLLVT.C */ /* Sometimes */
  595. LoadF( PutCpInWwHz ); /* SCROLLHZ.C */
  596. LoadF( ValidateTextBlt ); /* INSERT2.C */
  597. LoadF( InsertEolInsert ); /* INSERTCO.C */
  598. LoadF( Replace ); /* EDIT.C */
  599. LoadF( AlphaMode ); /* INSERT.C */
  600. break;
  601. case tskFormat:
  602. LoadF( DoPrm ); /* DOPRM.C */
  603. LoadF( AddSprmCps ); /* ADDPRM.C */
  604. LoadF( SetUndo ); /* EDIT.C */
  605. LoadF( FInitFontEnum ); /* FONTS.C */
  606. LoadF( SetAppMenu ); /* MENU.C */
  607. break;
  608. case tskScrap:
  609. LoadWindowsF( SetClipboardData ); /* USER!WINCLIP */
  610. LoadF( Replace ); /* EDIT.C */
  611. LoadF( fnCutEdit ); /* CLIPBORD.C */
  612. LoadF( SetAppMenu ); /* MENU.C */
  613. break;
  614. }
  615. }
  616. #endif
  617.