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.

4349 lines
145 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module RANGE.C - Implement the CTxtRange Class |
  5. *
  6. * This module implements the internal CTxtRange methods. See range2.c
  7. * for the ITextRange methods
  8. *
  9. * Authors: <nl>
  10. * Original RichEdit code: David R. Fulmer <nl>
  11. * Christian Fortini <nl>
  12. * Murray Sargent <nl>
  13. *
  14. * Revisions: <nl>
  15. * AlexGo: update to runptr text ptr; floating ranges, multilevel undo
  16. *
  17. * Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
  18. */
  19. #include "_common.h"
  20. #include "_range.h"
  21. #include "_edit.h"
  22. #include "_text.h"
  23. #include "_rtext.h"
  24. #include "_m_undo.h"
  25. #include "_antievt.h"
  26. #include "_disp.h"
  27. #include "_uspi.h"
  28. #include "_rtfconv.h"
  29. #include "_txtbrk.h"
  30. #include "_font.h"
  31. #ifdef LINESERVICES
  32. #include "_ols.h"
  33. #endif
  34. ASSERTDATA
  35. TCHAR szEmbedding[] = {WCH_EMBEDDING, 0};
  36. // =========================== Invariant stuff ======================================================
  37. #define DEBUG_CLASSNAME CTxtRange
  38. #include "_invar.h"
  39. #ifdef DEBUG
  40. BOOL
  41. CTxtRange::Invariant( void ) const
  42. {
  43. LONG cpMin, cpMost;
  44. GetRange(cpMin, cpMost);
  45. Assert ( cpMin >= 0 );
  46. Assert ( cpMin <= cpMost );
  47. Assert ( cpMost <= GetTextLength() );
  48. Assert ( cpMin != cpMost || cpMost <= GetAdjustedTextLength());
  49. static LONG numTests = 0;
  50. numTests++; // how many times we've been called.
  51. // make sure the selections are in range.
  52. return CRchTxtPtr::Invariant();
  53. }
  54. #endif
  55. void CTxtRange::RangeValidateCp(LONG cp, LONG cch)
  56. {
  57. LONG cchText = GetAdjustedTextLength();
  58. LONG cpOther = cp - cch; // Calculate cpOther with entry cp
  59. _wFlags = FALSE; // This range isn't a selection
  60. _iFormat = -1; // Set up the default format, which
  61. // doesn't get AddRefFormat'd
  62. ValidateCp(cpOther); // Validate requested other end
  63. cp = GetCp(); // Validated cp
  64. if(cp == cpOther && cp > cchText) // IP cannot follow undeletable
  65. cp = cpOther = SetCp(cchText); // EOP at end of story
  66. _cch = cp - cpOther; // Store valid length
  67. }
  68. CTxtRange::CTxtRange(CTxtEdit *ped, LONG cp, LONG cch) :
  69. CRchTxtPtr(ped, cp)
  70. {
  71. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CTxtRange");
  72. RangeValidateCp(cp, cch);
  73. Update_iFormat(-1); // Choose _iFormat
  74. CNotifyMgr *pnm = ped->GetNotifyMgr();
  75. if(pnm)
  76. pnm->Add( (ITxNotify *)this );
  77. }
  78. CTxtRange::CTxtRange(CRchTxtPtr& rtp, LONG cch) :
  79. CRchTxtPtr(rtp)
  80. {
  81. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CTxtRange");
  82. RangeValidateCp(GetCp(), cch);
  83. Update_iFormat(-1); // Choose _iFormat
  84. CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
  85. if(pnm)
  86. pnm->Add( (ITxNotify *)this );
  87. }
  88. CTxtRange::CTxtRange(const CTxtRange &rg) :
  89. CRchTxtPtr((CRchTxtPtr)rg)
  90. {
  91. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CTxtRange");
  92. _cch = rg._cch;
  93. _wFlags = FALSE; // This range isn't a selection
  94. _iFormat = -1; // Set up the default format, which
  95. // doesn't get AddRefFormat'd
  96. Set_iCF(rg._iFormat);
  97. CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
  98. if(pnm)
  99. pnm->Add((ITxNotify *)this);
  100. }
  101. CTxtRange::~CTxtRange()
  102. {
  103. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::~CTxtRange");
  104. if(!IsZombie())
  105. {
  106. CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
  107. if(pnm )
  108. pnm->Remove((ITxNotify *)this);
  109. }
  110. ReleaseFormats(_iFormat, -1);
  111. }
  112. CRchTxtPtr& CTxtRange::operator =(const CRchTxtPtr &rtp)
  113. {
  114. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::operator =");
  115. _TEST_INVARIANT_ON(rtp)
  116. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  117. CRchTxtPtr::operator =(rtp);
  118. CheckChange(cpSave);
  119. return *this;
  120. }
  121. CTxtRange& CTxtRange::operator =(const CTxtRange &rg)
  122. {
  123. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::operator =");
  124. _TEST_INVARIANT_ON( rg );
  125. LONG cchSave = _cch; // Save entry _cp, _cch for change check
  126. LONG cpSave = GetCp();
  127. CRchTxtPtr::operator =(rg);
  128. _cch = rg._cch; // Can't use CheckChange(), since don't
  129. // use _fExtend
  130. Update_iFormat(-1);
  131. _TEST_INVARIANT_
  132. if( _fSel && (cpSave != GetCp() || cchSave != _cch) )
  133. GetPed()->GetCallMgr()->SetSelectionChanged();
  134. return *this;
  135. }
  136. /*
  137. * CTxtRange::OnPreReplaceRange (cp, cchDel, cchNew, cpFormatMin,
  138. * cpFormatMax)
  139. *
  140. * @mfunc
  141. * called when the backing store changes
  142. *
  143. * @devnote
  144. * 1) if this range is before the changes, do nothing
  145. *
  146. * 2) if the changes are before this range, simply
  147. * add the delta change to GetCp()
  148. *
  149. * 3) if the changes overlap one end of the range, collapse
  150. * that end to the edge of the modifications
  151. *
  152. * 4) if the changes are completely internal to the range,
  153. * adjust _cch and/or GetCp() to reflect the new size. Note
  154. * that two overlapping insertion points will be viewed as
  155. * a 'completely internal' change.
  156. *
  157. * 5) if the changes overlap *both* ends of the range, collapse
  158. * the range to cp
  159. *
  160. * Note that there is an ambiguous cp case; namely the changes
  161. * occur *exactly* at a boundary. In this case, the type of
  162. * range matters. If a range is normal, then the changes
  163. * are assumed to fall within the range. If the range is
  164. * is protected (either in reality or via DragDrop), then
  165. * the changes are assumed to be *outside* of the range.
  166. */
  167. void CTxtRange::OnPreReplaceRange (
  168. LONG cp, //@parm cp at start of change
  169. LONG cchDel, //@parm Count of chars deleted
  170. LONG cchNew, //@parm Count of chars inserted
  171. LONG cpFormatMin, //@parm the min cp of a format change
  172. LONG cpFormatMax) //@parm the max cp of a format change
  173. {
  174. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::OnPreReplaceRange");
  175. if(CONVERT_TO_PLAIN == cp)
  176. {
  177. // We need to dump our formatting because it is gone
  178. _rpCF.SetToNull();
  179. _rpPF.SetToNull();
  180. if(_fSel)
  181. GetPed()->_fUpdateSelection = TRUE;
  182. Update_iFormat(-1);
  183. return;
  184. }
  185. }
  186. /*
  187. * CTxtRange::OnPostReplaceRange (cp, cchDel, cchNew, cpFormatMin,
  188. * cpFormatMax)
  189. *
  190. * @mfunc
  191. * called when the backing store changes
  192. *
  193. * @devnote
  194. * 1) if this range is before the changes, do nothing
  195. *
  196. * 2) if the changes are before this range, simply
  197. * add the delta change to GetCp()
  198. *
  199. * 3) if the changes overlap one end of the range, collapse
  200. * that end to the edge of the modifications
  201. *
  202. * 4) if the changes are completely internal to the range,
  203. * adjust _cch and/or GetCp() to reflect the new size. Note
  204. * that two overlapping insertion points will be viewed as
  205. * a 'completely internal' change.
  206. *
  207. * 5) if the changes overlap *both* ends of the range, collapse
  208. * the range to cp
  209. *
  210. * Note that there is an ambiguous cp case; namely the changes
  211. * occur *exactly* at a boundary. In this case, the type of
  212. * range matters. If a range is normal, then the changes
  213. * are assumed to fall within the range. If the range is
  214. * is protected (either in reality or via DragDrop), then
  215. * the changes are assumed to be *outside* of the range.
  216. */
  217. void CTxtRange::OnPostReplaceRange (
  218. LONG cp, //@parm cp at start of change
  219. LONG cchDel, //@parm Count of chars deleted
  220. LONG cchNew, //@parm Count of chars inserted
  221. LONG cpFormatMin, //@parm Min cp of format change
  222. LONG cpFormatMax) //@parm Max cp of format change
  223. {
  224. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::OnPostReplaceRange");
  225. // NB!! We can't do invariant testing here, because we could
  226. // be severely out of date!
  227. LONG cchtemp;
  228. LONG cpMin, cpMost;
  229. LONG cchAdjTextLen;
  230. LONG delta = cchNew - cchDel;
  231. Assert (CONVERT_TO_PLAIN != cp);
  232. GetRange(cpMin, cpMost);
  233. // This range is before the changes. Note: an insertion pt at cp
  234. // shouldn't be changed
  235. if( cp >= cpMost )
  236. {
  237. // Double check to see if we need to fix up our format
  238. // run pointers. If so, all we need to do is rebind
  239. // our inherited rich text pointer
  240. if(cpFormatMin <= cpMost || cpFormatMin == CP_INFINITE)
  241. InitRunPtrs();
  242. else
  243. {
  244. // It's possible that the format runs changed anyway,
  245. // e.g., they became allocated, deallocated, or otherwise
  246. // changed. Normally, BindToCp takes care of this
  247. // situation, but we don't want to pay that cost all
  248. // the time.
  249. //
  250. // Note that starting up the rich text subsystem will
  251. // generate a notification with cpFormatMin == CP_INFINITE
  252. //
  253. // So here, call CheckFormatRuns. This makes sure that
  254. // the runs are in sync with what CTxtStory has
  255. // (doing an InitRunPtrs() _only_ if absolutely necessary).
  256. CheckFormatRuns();
  257. }
  258. return;
  259. }
  260. // Anywhere in the following that we want to increment the current cp by a
  261. // delta, we are counting on the following invariant.
  262. Assert(GetCp() >= 0);
  263. // Changes are entirely before this range. Specifically,
  264. // that's determined by looking at the incoming cp *plus* the number
  265. // of characters deleted
  266. if(cp + cchDel < cpMin || _fDragProtection && cp + cchDel <= cpMin)
  267. {
  268. cchtemp = _cch;
  269. BindToCp(GetCp() + delta);
  270. _cch = cchtemp;
  271. }
  272. // The changes are internal to the range or start within the
  273. // range and go beyond.
  274. else if( cp >= cpMin && cp <= cpMost )
  275. {
  276. // Nobody should be modifying a drag-protected range. Unfortunately,
  277. // Ren re-enters us with a SetText call during drag drop, so we need
  278. // to handle this case 'gracefully'.
  279. if( _fDragProtection )
  280. {
  281. TRACEWARNSZ("REENTERED during a DRAG DROP!! Trying to recover!");
  282. }
  283. if( cp + cchDel <= cpMost )
  284. {
  285. // Changes are purely internal, so
  286. // be sure to preserve the active end. Basically, if
  287. // GetCp() *is* cpMin, then we only need to update _cch.
  288. // Otherwise, GetCp() needs to be moved as well
  289. if( _cch >= 0 )
  290. {
  291. Assert(GetCp() == cpMost);
  292. cchtemp = _cch;
  293. BindToCp(GetCp() + delta);
  294. _cch = cchtemp + delta;
  295. }
  296. else
  297. {
  298. BindToCp(GetCp());
  299. _cch -= delta;
  300. }
  301. // Special case: the range is left with only the final EOP
  302. // selected. This means all the characters in the range were
  303. // deleted so we want to move the range back to an insertion
  304. // point at the end of the text.
  305. cchAdjTextLen = GetAdjustedTextLength();
  306. if(GetCpMin() >= cchAdjTextLen && !GetPed()->IsStreaming())
  307. {
  308. // Reduce the range to an insertion point
  309. _cch = 0;
  310. _fExtend = FALSE;
  311. // Set the cp to the end of the document.
  312. SetCp(cchAdjTextLen);
  313. }
  314. }
  315. else
  316. {
  317. // Changes extended beyond cpMost. In this case,
  318. // we want to truncate cpMost to the *beginning* of
  319. // the changes (i.e. cp)
  320. if( _cch > 0 )
  321. {
  322. BindToCp(cp);
  323. _cch = cp - cpMin;
  324. }
  325. else
  326. {
  327. BindToCp(cpMin);
  328. _cch = cpMin - cp;
  329. }
  330. }
  331. }
  332. else if( cp + cchDel >= cpMost )
  333. {
  334. // Nobody should be modifying a drag-protected range. Unfortunately,
  335. // Ren re-enters us with a SetText call during drag drop, so we need
  336. // to handle this case 'gracefully'.
  337. if( _fDragProtection )
  338. {
  339. TRACEWARNSZ("REENTERED during a DRAG DROP!! Trying to recover!");
  340. }
  341. // Entire range was deleted, so collapse to an insertion point at cp
  342. BindToCp(cp);
  343. _cch = 0;
  344. }
  345. else
  346. {
  347. // Nobody should be modifying a drag-protected range. Unfortunately,
  348. // Ren re-enters us with a SetText call during drag drop, so we need
  349. // to handle this case 'gracefully'.
  350. if( _fDragProtection )
  351. {
  352. TRACEWARNSZ("REENTERED during a DRAG DROP!! Trying to recover!");
  353. }
  354. // The change crossed over just cpMin. In this case move cpMin
  355. // forward to the unchanged part
  356. LONG cchdiff = (cp + cchDel) - cpMin;
  357. Assert( cp + cchDel < cpMost );
  358. Assert( cp + cchDel >= cpMin );
  359. Assert( cp < cpMin );
  360. cchtemp = _cch;
  361. if( _cch > 0 )
  362. {
  363. BindToCp(GetCp() + delta);
  364. _cch = cchtemp - cchdiff;
  365. }
  366. else
  367. {
  368. BindToCp(cp + cchNew);
  369. _cch = cchtemp + cchdiff;
  370. }
  371. }
  372. if( _fSel )
  373. {
  374. GetPed()->_fUpdateSelection = TRUE;
  375. GetPed()->GetCallMgr()->SetSelectionChanged();
  376. }
  377. Update_iFormat(-1); // Make sure _iFormat is up to date
  378. _TEST_INVARIANT_
  379. }
  380. /*
  381. * CTxtRange::Zombie ()
  382. *
  383. * @mfunc
  384. * Turn this range into a zombie (_cp = _cch = 0, NULL ped, ptrs to
  385. * backing store arrays. CTxtRange methods like GetRange(),
  386. * GetCpMost(), GetCpMin(), and GetTextLength() all work in zombie mode,
  387. * returning zero values.
  388. */
  389. void CTxtRange::Zombie()
  390. {
  391. CRchTxtPtr::Zombie();
  392. _cch = 0;
  393. }
  394. /*
  395. * CTxtRange::CheckChange(cpSave, cchSave)
  396. *
  397. * @mfunc
  398. * Set _cch according to _fExtend and set selection-changed flag if
  399. * this range is a CTxtSelection and the new _cp or _cch differ from
  400. * cp and cch, respectively.
  401. *
  402. * @devnote
  403. * We can count on GetCp() and cpSave both being <= GetTextLength(),
  404. * but we can't leave GetCp() equal to GetTextLength() unless _cch ends
  405. * up > 0.
  406. */
  407. LONG CTxtRange::CheckChange(
  408. LONG cpSave) //@parm Original _cp for this range
  409. {
  410. LONG cchAdj = GetAdjustedTextLength();
  411. LONG cchSave = _cch;
  412. if(_fExtend) // Wants to be nondegenerate
  413. { // and maybe it is
  414. LONG cp = GetCp();
  415. _cch = cp - (cpSave - cchSave);
  416. CheckIfSelHasEOP(cpSave, cchSave);
  417. }
  418. else
  419. {
  420. _cch = 0; // Insertion point
  421. _fSelHasEOP = FALSE; // Selection doesn't contain
  422. _fSelHasCell = FALSE; // any char, let alone a CR
  423. } // or table cell
  424. if(!_cch && GetCp() > cchAdj) // If still IP and active end
  425. CRchTxtPtr::SetCp(cchAdj); // follows nondeletable EOP,
  426. // backspace over that EOP
  427. LONG cch = GetCp() - cpSave;
  428. _fMoveBack = cch < 0;
  429. if(cch || cchSave != _cch)
  430. {
  431. Update_iFormat(-1);
  432. if(_fSel)
  433. GetPed()->GetCallMgr()->SetSelectionChanged();
  434. _TEST_INVARIANT_
  435. }
  436. return cch;
  437. }
  438. /*
  439. * CTxtRange::CheckIfSelHasEOP(cpSave, cchSave)
  440. *
  441. * @mfunc
  442. * Maintains _fSelHasEOP = TRUE iff selection contains one or more EOPs.
  443. * When cpSave = -1, calculates _fSelHasEOP unconditionally and cchSave
  444. * is ignored (it's only used for conditional execution). Else _fSelHasEOP
  445. * is only calculated for cases that may change it, i.e., it's assumed
  446. * be up to date before the change.
  447. *
  448. * @rdesc
  449. * TRUE iff _fSel and _cch
  450. *
  451. * @devnote
  452. * Call after updating range _cch
  453. */
  454. BOOL CTxtRange::CheckIfSelHasEOP(
  455. LONG cpSave, //@parm Previous active end cp or -1
  456. LONG cchSave) //@parm Previous signed length if cpSave != -1
  457. {
  458. // _fSelHasEOP only maintained for the selection
  459. if(!_fSel)
  460. return FALSE;
  461. if(!_cch)
  462. {
  463. _fSelHasEOP = FALSE; // Selection doesn't contain
  464. _fSelHasCell = FALSE; // any char, let alone CR
  465. return FALSE;
  466. }
  467. LONG cpMin, cpMost;
  468. GetRange(cpMin, cpMost);
  469. if(cpSave != -1) // Selection may have changed
  470. { // Set up to bypass text scan if
  471. LONG cpMinPrev, cpMostPrev; // selection grew and _fSelHasEOP
  472. // is already TRUE or got smaller
  473. cpMinPrev = cpMostPrev = cpSave;// and _fSelHasEOP is FALSE.
  474. if(cchSave > 0) // Calculate previous cpMin
  475. cpMinPrev -= cchSave; // and cpMost
  476. else
  477. cpMostPrev -= cchSave;
  478. // Note: _fSelHasCell shouldn't change while in a table, since
  479. // Update() should always expand to a cell once _fSelHasCell has
  480. // been deteted.
  481. if (!_fSelHasEOP && cpMin >= cpMinPrev && cpMost <= cpMostPrev ||
  482. _fSelHasEOP && cpMin <= cpMinPrev && cpMost >= cpMostPrev)
  483. {
  484. return TRUE; // _fSelHasEOP can't change
  485. }
  486. }
  487. LONG FEOP_Results;
  488. CTxtPtr tp(_rpTX); // Scan range for an EOP
  489. tp.SetCp(cpMin);
  490. tp.FindEOP(cpMost - cpMin, &FEOP_Results);
  491. _fSelHasCell = (FEOP_Results & FEOP_CELL) != 0;
  492. _fSelHasEOP = (FEOP_Results & FEOP_EOP) != 0;
  493. return TRUE;
  494. }
  495. /*
  496. * CTxtRange::GetRange(&cpMin, &cpMost)
  497. *
  498. * @mfunc
  499. * set cpMin = this range cpMin
  500. * set cpMost = this range cpMost
  501. * return cpMost - cpMin, i.e. abs(_cch)
  502. *
  503. * @rdesc
  504. * abs(_cch)
  505. */
  506. LONG CTxtRange::GetRange (
  507. LONG& cpMin, //@parm Pass-by-ref cpMin
  508. LONG& cpMost) const //@parm Pass-by-ref cpMost
  509. {
  510. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetRange");
  511. LONG cch = _cch;
  512. if(cch >= 0)
  513. {
  514. cpMost = GetCp();
  515. cpMin = cpMost - cch;
  516. }
  517. else
  518. {
  519. cch = -cch;
  520. cpMin = GetCp();
  521. cpMost = cpMin + cch;
  522. }
  523. return cch;
  524. }
  525. /*
  526. * CTxtRange::GetCpMin()
  527. *
  528. * @mfunc
  529. * return this range's cpMin
  530. *
  531. * @rdesc
  532. * cpMin
  533. *
  534. * @devnote
  535. * If you need cpMost and/or cpMost - cpMin, GetRange() is faster
  536. */
  537. LONG CTxtRange::GetCpMin() const
  538. {
  539. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetCpMin");
  540. LONG cp = GetCp();
  541. return _cch <= 0 ? cp : cp - _cch;
  542. }
  543. /*
  544. * CTxtRange::GetCpMost()
  545. *
  546. * @mfunc
  547. * return this range's cpMost
  548. *
  549. * @rdesc
  550. * cpMost
  551. *
  552. * @devnote
  553. * If you need cpMin and/or cpMost - cpMin, GetRange() is faster
  554. */
  555. LONG CTxtRange::GetCpMost() const
  556. {
  557. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetCpMost");
  558. LONG cp = GetCp();
  559. return _cch >= 0 ? cp : cp - _cch;
  560. }
  561. /*
  562. * CTxtRange::Update(fScrollIntoView)
  563. *
  564. * @mfunc
  565. * Virtual stub routine overruled by CTxtSelection::Update() when this
  566. * text range is a text selection. The purpose is to update the screen
  567. * display of the caret or selection to correspond to changed cp's.
  568. *
  569. * @rdesc
  570. * TRUE
  571. */
  572. BOOL CTxtRange::Update (
  573. BOOL fScrollIntoView) //@parm TRUE if should scroll caret into view
  574. {
  575. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Update");
  576. return TRUE; // Simple range has no selection colors or
  577. } // caret, so just return TRUE
  578. /*
  579. * CTxtRange::SetCp(cp)
  580. *
  581. * @mfunc
  582. * Set active end of this range to cp. Leave other end where it is or
  583. * collapse range depending on _fExtend (see CheckChange()).
  584. *
  585. * @rdesc
  586. * cp at new active end (may differ from cp, since cp may be invalid).
  587. */
  588. LONG CTxtRange::SetCp(
  589. LONG cp) //@parm new cp for active end of this range
  590. {
  591. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtRange::SetCp");
  592. LONG cpSave = GetCp();
  593. CRchTxtPtr::SetCp(cp);
  594. CheckChange(cpSave); // NB: this changes _cp if after
  595. return GetCp(); // final CR and _cch = 0
  596. }
  597. /*
  598. * CTxtRange::Set (cp, cch)
  599. *
  600. * @mfunc
  601. * Set this range's active-end cp and signed cch
  602. */
  603. BOOL CTxtRange::Set (
  604. LONG cp, //@parm Desired active end cp
  605. LONG cch) //@parm Desired signed count of chars
  606. {
  607. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Set");
  608. BOOL bRet = FALSE;
  609. LONG cchSave = _cch; // Save entry _cp, _cch for change check
  610. LONG cchText = GetAdjustedTextLength();
  611. LONG cpSave = GetCp();
  612. LONG cpOther = cp - cch; // Desired "other" end
  613. ValidateCp(cp); // Be absolutely sure to validate
  614. ValidateCp(cpOther); // both ends
  615. if(cp == cpOther && cp > cchText) // IP cannot follow undeletable
  616. cp = cpOther = cchText; // EOP at end of story
  617. CRchTxtPtr::Advance(cp - GetCp());
  618. AssertSz(cp == GetCp(),
  619. "CTxtRange::Set: inconsistent cp");
  620. if(GetPed()->fUseCRLF())
  621. {
  622. cch = _rpTX.AdjustCpCRLF();
  623. if(cch)
  624. {
  625. _rpCF.AdvanceCp(cch); // Keep all 3 runptrs in sync
  626. _rpPF.AdvanceCp(cch);
  627. cp = GetCp();
  628. }
  629. if(cpOther != cp)
  630. {
  631. CTxtPtr tp(_rpTX);
  632. tp.AdvanceCp(cpOther - cp);
  633. cpOther += tp.AdjustCpCRLF();
  634. }
  635. }
  636. _cch = cp - cpOther; // Validated _cch value
  637. CheckIfSelHasEOP(cpSave, cchSave); // Maintain _fSelHasEOP in
  638. // outline mode
  639. _fMoveBack = GetCp() < cpSave;
  640. if(cpSave != GetCp() || cchSave != _cch)
  641. {
  642. if(_fSel)
  643. GetPed()->GetCallMgr()->SetSelectionChanged();
  644. Update_iFormat(-1);
  645. bRet = TRUE;
  646. }
  647. _TEST_INVARIANT_
  648. return bRet;
  649. }
  650. /*
  651. * CTxtRange::Advance(cch)
  652. *
  653. * @mfunc
  654. * Advance active end of range by cch.
  655. * Other end stays put iff _fExtend
  656. *
  657. * @rdesc
  658. * cch actually moved
  659. */
  660. LONG CTxtRange::Advance (
  661. LONG cch) //@parm Signed char count to move active end
  662. {
  663. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Advance");
  664. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  665. CRchTxtPtr::Advance(cch);
  666. return CheckChange(cpSave);
  667. }
  668. /*
  669. * CTxtRange::AdvanceCRLF()
  670. *
  671. * @mfunc
  672. * Advance active end of range one char, treating CRLF as a single char.
  673. * Other end stays put iff _fExtend is nonzero.
  674. *
  675. * @rdesc
  676. * cch actually moved
  677. */
  678. LONG CTxtRange::AdvanceCRLF()
  679. {
  680. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::AdvanceCRLF");
  681. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  682. CRchTxtPtr::AdvanceCRLF();
  683. return CheckChange(cpSave);
  684. }
  685. /*
  686. * CTxtRange::SnapToCluster(INT iDirection)
  687. *
  688. * @rdesc
  689. * cch actually moved
  690. */
  691. LONG CTxtRange::SnapToCluster(INT iDirection)
  692. {
  693. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::SnapToCluster");
  694. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  695. CRchTxtPtr::SnapToCluster(iDirection);
  696. return CheckChange(cpSave);
  697. }
  698. /*
  699. * CTxtRange::BackupCRLF()
  700. *
  701. * @mfunc
  702. * Backup active end of range one char, treating CRLF as a single char.
  703. * Other end stays put iff _fExtend
  704. *
  705. * @rdesc
  706. * cch actually moved
  707. */
  708. LONG CTxtRange::BackupCRLF(
  709. BOOL fDiacriticCheck)
  710. {
  711. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::BackupCRLF");
  712. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  713. CRchTxtPtr::BackupCRLF(fDiacriticCheck);
  714. return CheckChange(cpSave);
  715. }
  716. /*
  717. * CTxtRange::FindWordBreak(action)
  718. *
  719. * @mfunc
  720. * Move active end as determined by plain-text FindWordBreak().
  721. * Other end stays put iff _fExtend
  722. *
  723. * @rdesc
  724. * cch actually moved
  725. */
  726. LONG CTxtRange::FindWordBreak (
  727. INT action) //@parm action defined by CTxtPtr::FindWordBreak()
  728. {
  729. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtPtr::FindWordBreak");
  730. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  731. CRchTxtPtr::FindWordBreak(action);
  732. return CheckChange(cpSave);
  733. }
  734. /*
  735. * CTxtRange::FlipRange()
  736. *
  737. * @mfunc
  738. * Flip active and non active ends
  739. */
  740. void CTxtRange::FlipRange()
  741. {
  742. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FlipRange");
  743. _TEST_INVARIANT_
  744. CRchTxtPtr::Advance(-_cch);
  745. _cch = -_cch;
  746. }
  747. /*
  748. * CTxtRange::HexToUnicode(publdr)
  749. *
  750. * @mfunc
  751. * Convert hex number ending at cpMost to a Unicode character and
  752. * replace the hex number by that character. Take into account
  753. * Unicode surrogates for hex values from 0x10000 up to 0x10FFFF.
  754. *
  755. * @rdesc
  756. * HRESULT S_OK if conversion successful and hex number replaced by
  757. * corresponding Unicode character
  758. */
  759. HRESULT CTxtRange::HexToUnicode (
  760. IUndoBuilder *publdr) //@parm UndoBuilder to receive antievents
  761. {
  762. LONG ch;
  763. LONG cpMin, cpMost;
  764. LONG cch = GetRange(cpMin, cpMost);
  765. LONG i;
  766. LONG lch = 0;
  767. if(cch)
  768. {
  769. if(cpMost > GetAdjustedTextLength() || cch > 6)
  770. return S_FALSE;
  771. Collapser(tomEnd);
  772. }
  773. else
  774. cch = 6;
  775. SetExtend(TRUE);
  776. for(i = 0; cch--; i += 4)
  777. {
  778. ch = GetPrevChar();
  779. if(ch == '+') // Check for U+xxxx notation
  780. { // If it's there, set up to
  781. Advance(-1); // delete the U+ (or u+)
  782. Advance((GetPrevChar() | 0x20) == 'u' ? -1 : 1);
  783. break; // Else leave the +
  784. }
  785. if(ch > 'f' || !IsXDigit(ch))
  786. break;
  787. Advance(-1);
  788. ch |= 0x20;
  789. ch -= (ch >= 'a') ? 'a' - 10 : '0';
  790. lch += (ch << i);
  791. }
  792. if(!lch || lch > 0x10FFFF)
  793. return S_FALSE;
  794. WCHAR str[2] = {(WCHAR)lch};
  795. cch = 1;
  796. if(lch > 0xFFFF)
  797. {
  798. lch -= 0x10000;
  799. str[0] = 0xD800 + (lch >> 10);
  800. str[1] = 0xDC00 + (lch & 0x3FF);
  801. cch = 2;
  802. }
  803. if(publdr)
  804. publdr->StopGroupTyping();
  805. _rpCF.AdjustBackward(); // Use format of run preceding
  806. Set_iCF(_rpCF.GetFormat()); // hex number
  807. _fUseiFormat = TRUE;
  808. _rpCF.AdjustForward();
  809. CleanseAndReplaceRange(cch, str, FALSE, publdr, NULL);
  810. return S_OK;
  811. }
  812. /*
  813. * CTxtRange::UnicodeToHex(publdr)
  814. *
  815. * @mfunc
  816. * Convert Unicode character(s) preceeding cpMin to a hex number and
  817. * select it. Translate Unicode surrogates into corresponding for hex
  818. * values from 0x10000 up to 0x10FFFF.
  819. *
  820. * @rdesc
  821. * HRESULT S_OK if conversion successful and Unicode character(s) is
  822. * replaced by corresponding hex number.
  823. */
  824. HRESULT CTxtRange::UnicodeToHex (
  825. IUndoBuilder *publdr) //@parm UndoBuilder to receive antievents
  826. {
  827. if(_cch) // If there's a selection,
  828. { // convert 1st char in sel
  829. Collapser(tomStart);
  830. Advance(IN_RANGE(0xD800, CRchTxtPtr::GetChar(), 0xDBFF) ? 2 : 1);
  831. }
  832. LONG cp = GetCp();
  833. if(!cp) // No character to convert
  834. return S_FALSE;
  835. _cch = 1; // Select previous char
  836. LONG n = GetPrevChar(); // Get it
  837. _fExtend = TRUE;
  838. if(publdr)
  839. publdr->StopGroupTyping();
  840. if(IN_RANGE(0xDC00, n, 0xDFFF)) // Unicode surrogate trail word
  841. {
  842. if(cp <= 1) // No lead word
  843. return S_FALSE;
  844. Advance(-2);
  845. LONG ch = CRchTxtPtr::GetChar();
  846. Assert(IN_RANGE(0xD800, ch, 0xDBFF));
  847. n = (n & 0x3FF) + ((ch & 0x3FF) << 10) + 0x10000;
  848. _cch = -2;
  849. }
  850. // Convert ch to str
  851. LONG cch = 0;
  852. LONG quot, rem; // ldiv results
  853. WCHAR str[6];
  854. WCHAR * pch = &str[0];
  855. for(LONG d = 1; d < n; d <<= 4) // d = smallest power of 16 > n
  856. ;
  857. if(n && d > n)
  858. d >>= 4;
  859. while(d)
  860. {
  861. quot = n / d; // Avoid an ldiv
  862. rem = n % d;
  863. n = quot + '0';
  864. if(n > '9')
  865. n += 'A' - '9' - 1;
  866. *pch++ = (WCHAR)n; // Store digit
  867. cch++;
  868. n = rem; // Setup remainder
  869. d >>= 4;
  870. }
  871. CleanseAndReplaceRange(cch, str, FALSE, publdr, NULL);
  872. _cch = cch; // Select number
  873. if(_fSel)
  874. Update(FALSE);
  875. return S_OK;
  876. }
  877. /*
  878. * CTxtRange::IsInputSequenceValid(pch, cchIns, fOver, pfBaseChar)
  879. *
  880. * @mfunc
  881. * Verify the sequence of incoming text. Return FALSE if invalid
  882. * combination is found. The criteria is to allow any combinations
  883. * that are displayable on screen (the simplest approach used by system
  884. * edit control).
  885. *
  886. * @rdesc
  887. * Return FALSE if invalid combination is found; else TRUE.
  888. *
  889. * FUTURE: We may consider to support bad sequence filter or text streaming. The code
  890. * below is easy enough to be extended to do so.
  891. */
  892. BOOL CTxtRange::IsInputSequenceValid(
  893. WCHAR* pwch, // Inserting string
  894. LONG cchIns, // Character count
  895. BOOL fOverType, // Insert or Overwrite mode
  896. BOOL* pfBaseChar) // Is pwch[0] a cluster start (base char)?
  897. {
  898. CTxtEdit* ped = GetPed();
  899. CTxtPtr tp(_rpTX);
  900. HKL hkl = GetKeyboardLayout(0);
  901. BOOL fr = TRUE;
  902. if (ped->fUsePassword() || ped->_fNoInputSequenceChk)
  903. return TRUE; // no check when editing password
  904. if (PRIMARYLANGID(hkl) == LANG_VIETNAMESE)
  905. {
  906. // No concern about overtyping or cluster since we look backward only
  907. // 1 char and dont care characters following the insertion point.
  908. if(_cch > 0)
  909. tp.AdvanceCp(-_cch);
  910. fr = IsVietCdmSequenceValid(tp.GetPrevChar(), *pwch);
  911. }
  912. else if (PRIMARYLANGID(hkl) == LANG_THAI ||
  913. W32->IsIndicLcid(LOWORD(hkl)))
  914. {
  915. // Do complex things for Thai and Indic
  916. WCHAR rgwchText[32];
  917. WCHAR* pwchText = rgwchText;
  918. CUniscribe* pusp = ped->Getusp();
  919. CTxtBreaker* pbrk = ped->_pbrk;
  920. LONG found = 0;
  921. LONG cp, cpSave, cpLimMin, cpLimMax;
  922. LONG cchDel = 0, cchText, ich;
  923. LONG cpEnd = ped->GetAdjustedTextLength();
  924. if (_cch > 0)
  925. tp.AdvanceCp(-_cch);
  926. cp = cpSave = cpLimMin = cpLimMax = tp.GetCp();
  927. if (_cch)
  928. {
  929. cchDel = abs(_cch);
  930. }
  931. else if (fOverType && !tp.IsAtEOP() && cp != cpEnd)
  932. {
  933. // Delete up to the next cluster in overtype mode
  934. cchDel++;
  935. if (pbrk)
  936. while (cp + cchDel < cpEnd && !pbrk->CanBreakCp(BRK_CLUSTER, cp + cchDel))
  937. cchDel++;
  938. }
  939. cpLimMax += cchDel;
  940. // Figure the min/max boundaries
  941. if (pbrk)
  942. {
  943. // Min boundary
  944. cpLimMin += tp.FindEOP(tomBackward, &found);
  945. if (!(found & FEOP_EOP))
  946. cpLimMin = 0;
  947. while (--cp > cpLimMin && !pbrk->CanBreakCp(BRK_CLUSTER, cp));
  948. cpLimMin = max(cp, cpLimMin); // more precise boundary
  949. // Max boundary
  950. cp = cpLimMax;
  951. tp.SetCp(cpLimMax);
  952. cpLimMax += tp.FindEOP(tomForward, &found);
  953. if (!(found & FEOP_EOP))
  954. cpLimMax = ped->GetTextLength();
  955. while (cp < cpLimMax && !pbrk->CanBreakCp(BRK_CLUSTER, cp++));
  956. cpLimMax = min(cp, cpLimMax); // more precise boundary
  957. }
  958. else
  959. {
  960. // No cluster info we statically bound to -1/+1 from selection range
  961. cpLimMin--;
  962. cpLimMin = max(0, cpLimMin);
  963. cpLimMax += cchDel + 1;
  964. cpLimMax = min(cpLimMax, ped->GetTextLength());
  965. }
  966. cp = cpSave + cchDel;
  967. cchText = cpSave - cpLimMin + cchIns + cpLimMax - cp;
  968. tp.SetCp(cpLimMin);
  969. if (cchText > 32)
  970. pwchText = new WCHAR[cchText];
  971. if (pwchText)
  972. {
  973. // prepare text
  974. cchText = tp.GetText (cpSave - cpLimMin, pwchText);
  975. tp.AdvanceCp (cchText + cchDel);
  976. ich = cchText;
  977. wcsncpy (&pwchText[cchText], pwch, cchIns);
  978. cchText += cchIns;
  979. cchText += tp.GetText (cpLimMax - cpSave - cchDel, &pwchText[cchText]);
  980. Assert (cchText == cpLimMax - cpLimMin - cchDel + cchIns);
  981. if (pusp)
  982. {
  983. SCRIPT_STRING_ANALYSIS ssa;
  984. HRESULT hr;
  985. BOOL fDecided = FALSE;
  986. hr = ScriptStringAnalyse(NULL, pwchText, cchText, GLYPH_COUNT(cchText), -1,
  987. SSA_BREAK, -1, NULL, NULL, NULL, NULL, NULL, &ssa);
  988. if (S_OK == hr)
  989. {
  990. if (fOverType)
  991. {
  992. const SCRIPT_LOGATTR* psla = ScriptString_pLogAttr(ssa);
  993. BOOL fBaseChar = !psla || psla[ich].fCharStop;
  994. if (!fBaseChar)
  995. {
  996. // In overtype mode, if the inserted char is not a cluster start.
  997. // We act like insert mode. Recursive call with fOvertype = FALSE.
  998. fr = IsInputSequenceValid(pwch, cchIns, 0, NULL);
  999. fDecided = TRUE;
  1000. }
  1001. if (pfBaseChar)
  1002. *pfBaseChar = fBaseChar;
  1003. }
  1004. if (!fDecided && S_FALSE == ScriptStringValidate(ssa))
  1005. fr = FALSE;
  1006. ScriptStringFree(&ssa);
  1007. }
  1008. }
  1009. if (pwchText != rgwchText)
  1010. delete[] pwchText;
  1011. }
  1012. }
  1013. return fr;
  1014. }
  1015. /*
  1016. * CTxtRange::CleanseAndReplaceRange(cch, *pch, fTestLimit, publdr,
  1017. * pchD, pcchMove, dwFlags)
  1018. * @mfunc
  1019. * Cleanse the string pch (replace CRLFs by CRs, etc.) and substitute
  1020. * the resulting string for the text in this range using the CCharFormat
  1021. * _iFormat and updating other text runs as needed. For single-line
  1022. * controls, truncate on the first EOP and substitute the truncated
  1023. * string. Also truncate if string would overflow the max text length.
  1024. *
  1025. * @rdesc
  1026. * Count of new characters added
  1027. */
  1028. LONG CTxtRange::CleanseAndReplaceRange (
  1029. LONG cchS, //@parm Length of replacement (Source) text
  1030. const WCHAR * pchS, //@parm Replacement (Source) text
  1031. BOOL fTestLimit, //@parm Whether to do limit test
  1032. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1033. WCHAR * pchD, //@parm Destination string (multiline only)
  1034. LONG* pcchMove, //@parm Count of chars moved in 1st replace
  1035. DWORD dwFlags) //@parm ReplaceRange's flags
  1036. {
  1037. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CleanseAndReplaceRange");
  1038. CTxtEdit * ped = GetPed();
  1039. BYTE bDefaultCharset = ped->GetCharFormat(-1)->_bCharSet;
  1040. LONG cchM = 0;
  1041. LONG cchMove = 0;
  1042. LONG cchNew = 0; // Collects total cch inserted
  1043. LONG cch; // Collects cch for cur charset
  1044. DWORD ch, ch1;
  1045. LONG cpFirst = GetCpMin();
  1046. DWORD dw;
  1047. DWORD dwCharFlags = 0;
  1048. DWORD dwCharMask = GetCharSetMask();
  1049. DWORD dwCurrentFontUsed = 0;
  1050. BOOL f10Mode = ped->Get10Mode();
  1051. BOOL fCallerDestination = pchD != 0; // Save if pchD enters as 0
  1052. BOOL fDefFontHasASCII = FALSE;
  1053. CFreezeDisplay fd(ped->_pdp);
  1054. BOOL fMultiLine = ped->_pdp->IsMultiLine();
  1055. BOOL fFEBaseFont = IsFECharSet(bDefaultCharset);
  1056. BOOL fInTable = FALSE;
  1057. bool fUIFont = fUseUIFont();
  1058. BOOL fUseCRLF = ped->fUseCRLF();
  1059. BOOL fDefFontSymbol = dwCharMask == fSYMBOL;
  1060. const WCHAR * pch = pchS;
  1061. CTempWcharBuf twcb; // Buffer needed for multiline if
  1062. // pchD = 0
  1063. CCharFormat CFCurrent; // Current CF used during IME in progress
  1064. const DWORD fALPHA = 0x01;
  1065. BOOL fDeleteChar = !ped->IsRich() && _cch;
  1066. if (ped->_fIMEInProgress)
  1067. {
  1068. // Initialize data to handle alpha/ASCII dual font mode
  1069. // during IME composition
  1070. dwCurrentFontUsed = fFE;
  1071. CFCurrent = *ped->GetCharFormat(GetiFormat());
  1072. }
  1073. // Check if default font supports full ASCII and Symbol
  1074. if (fUIFont)
  1075. {
  1076. DWORD dwMaskDefFont = GetCharSetMask(TRUE);
  1077. fDefFontHasASCII = (dwMaskDefFont & fASCII) == fASCII;
  1078. fDefFontSymbol = dwMaskDefFont == fSYMBOL;
  1079. }
  1080. if(!pchS)
  1081. cchS = 0;
  1082. else if(fMultiLine)
  1083. {
  1084. if(cchS < 0) // Calculate length for
  1085. cchS = wcslen(pchS); // target buffer
  1086. if(cchS && !pchD)
  1087. {
  1088. pchD = twcb.GetBuf(cchS);
  1089. if(!pchD) // Couldn't allocate buffer:
  1090. return 0; // give up with no update
  1091. }
  1092. pch = pchD;
  1093. if(_cch <= 0)
  1094. fInTable = GetPF()->InTable();
  1095. else
  1096. {
  1097. CFormatRunPtr rpPF(_rpPF);
  1098. rpPF.AdvanceCp(-_cch);
  1099. fInTable = (ped->GetParaFormat(rpPF.GetFormat())->InTable());
  1100. }
  1101. }
  1102. else if(cchS < 0) // Calculate string length
  1103. cchS = tomForward; // while looking for EOP
  1104. for(cch = 0; cchS; cchS--, pchS++, cch++)
  1105. {
  1106. ch = *pchS;
  1107. if(!ch && (!fMultiLine || !fCallerDestination))
  1108. break;
  1109. if(IN_RANGE(CELL, ch, CR)) // Handle CR and LF combos
  1110. {
  1111. if(!fMultiLine && ch >= LF) // Truncate at 1st EOP to be
  1112. break; // compatible with user.exe SLE
  1113. // and for consistent behavior
  1114. if(ch == CR && !f10Mode)
  1115. {
  1116. if(cchS > 1)
  1117. {
  1118. ch1 = *(pchS + 1);
  1119. if(cchS > 2 && ch1 == CR && *(pchS+2) == LF)
  1120. {
  1121. if(fUseCRLF)
  1122. {
  1123. *pchD++ = ch;
  1124. *pchD++ = ch1;
  1125. ch = LF;
  1126. cch += 2;
  1127. }
  1128. else
  1129. {
  1130. // Translate CRCRLF to CR or to ' '
  1131. ch = ped->fXltCRCRLFtoCR() ? CR : ' ';
  1132. }
  1133. pchS += 2; // Bypass two chars
  1134. cchS -= 2;
  1135. }
  1136. else if(ch1 == LF)
  1137. {
  1138. if(fUseCRLF && !fInTable)// Copy over whole CRLF
  1139. {
  1140. *pchD++ = ch; // Here we copy CR
  1141. ch = ch1; // Setup to copy LF
  1142. cch++;
  1143. }
  1144. pchS++;
  1145. cchS--;
  1146. }
  1147. }
  1148. if(fInTable && ch == CR) // Our simple tables can't contain
  1149. ch = ' '; // CRs or CELLs
  1150. }
  1151. else if(!fUseCRLF && ch == LF) // Treat lone LFs as EOPs, i.e.,
  1152. ch = CR; // be nice to Unix text files
  1153. else if(ch == CELL && fInTable)
  1154. ch = ' ';
  1155. }
  1156. else if((ch | 1) == PS) // Translate Unicode para/line
  1157. { // separators into CR/VT
  1158. if(!fMultiLine)
  1159. break;
  1160. ch = (ch == PS) ? CR : VT;
  1161. }
  1162. dw = fSYMBOL;
  1163. if(!fDefFontSymbol)
  1164. dw = GetCharFlags(ch, bDefaultCharset); // Check for complex scripts,
  1165. dwCharFlags |= dw; // FE, and charset changes
  1166. dw &= ~0xFF; // Exclude non-fontbind flags
  1167. if(ped->IsAutoFont() && !fDefFontSymbol)
  1168. {
  1169. BOOL fReplacedText = FALSE;
  1170. if (fDeleteChar)
  1171. {
  1172. fDeleteChar = FALSE;
  1173. ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE, NULL, RR_ITMZ_NONE);
  1174. Set_iCF(-1);
  1175. dwCharMask = GetCharSetMask(TRUE);
  1176. }
  1177. if (!ped->_fIMEInProgress)
  1178. {
  1179. // Simp. Chinese uses some of the Latin2 symbols
  1180. if (dw == fLATIN2 || IN_RANGE(0x0250, ch, 0x02FF)
  1181. || IN_RANGE(0xFE50, ch, 0xFE6F))
  1182. {
  1183. WCHAR wch = ch;
  1184. if (VerifyFEString(CP_CHINESE_SIM, &wch, 1, TRUE) == CP_CHINESE_SIM ||
  1185. VerifyFEString(CP_CHINESE_TRAD, &wch, 1, TRUE) == CP_CHINESE_TRAD)
  1186. dw = fCHINESE;
  1187. }
  1188. if (fUIFont && dw == fHILATIN1 && fFEBaseFont)
  1189. {
  1190. // Use Ansi font for HiAnsi
  1191. if (dwCurrentFontUsed != fHILATIN1)
  1192. {
  1193. fReplacedText = TRUE;
  1194. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1195. dw, &cchM, cpFirst, IGNORE_CURRENT_FONT, RR_ITMZ_NONE); // Replace text up to previous char
  1196. }
  1197. dwCurrentFontUsed = fHILATIN1;
  1198. }
  1199. else if (fUIFont && fDefFontHasASCII &&
  1200. (dw & fASCII || IN_RANGE(0x2018, ch, 0x201D)))
  1201. {
  1202. if (dwCurrentFontUsed != fASCII)
  1203. {
  1204. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1205. 0, &cchM, cpFirst, IGNORE_CURRENT_FONT, RR_ITMZ_NONE); // Replace text up to previous char
  1206. // Use the -1 font charset/face/size so the current font effect
  1207. // will still be used.
  1208. CCharFormat CFDefault = *ped->GetCharFormat(-1);
  1209. SetCharFormat(&CFDefault, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1210. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1211. fReplacedText = TRUE;
  1212. }
  1213. dwCurrentFontUsed = fASCII;
  1214. }
  1215. else if (dw && !(dw & dwCharMask) // No match: need charset change
  1216. || dwCurrentFontUsed) // or change in classification
  1217. {
  1218. fReplacedText = TRUE;
  1219. dwCurrentFontUsed = 0;
  1220. if(dw & (fCHINESE | fBIG5) & ~255) // If Han char, check next few
  1221. { // chars for a Hangul or Kana
  1222. Assert(cchS);
  1223. const WCHAR *pchT = pchS+1;
  1224. LONG i = min(10, cchS - 1);
  1225. while(i-- && *pchT)
  1226. dw |= GetCharFlags(*pchT++, bDefaultCharset);
  1227. i = CalcTextLenNotInRange();
  1228. if(cchS < 6 && i) // Get flags around range
  1229. {
  1230. CTxtPtr tp(_rpTX);
  1231. i = min(i, 6);
  1232. if(!_cch) // For insertion point, backup
  1233. tp.AdvanceCp(-i/2); // half way
  1234. else if(_cch < 0) // Active end at cpMin, backup
  1235. tp.AdvanceCp(-i); // whole way
  1236. for(; i--; tp.AdvanceCp(1))
  1237. dw |= GetCharFlags(tp.GetChar(), bDefaultCharset);
  1238. }
  1239. dw &= (fKANA | fHANGUL | fCHINESE | fBIG5);
  1240. }
  1241. else if(dw & (fHILATIN1 | fLATIN2) && dwCharMask & fLATIN)
  1242. {
  1243. LONG i = dwCharMask & fLATIN;
  1244. dw = W32->GetCharFlags125x(ch) & fLATIN;
  1245. if(!(dw & i))
  1246. for(i = 0x100; i < 0x20000 && !(dw & i); i <<= 1)
  1247. ;
  1248. dw &= i;
  1249. }
  1250. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1251. dw, &cchM, cpFirst, MATCH_FONT_SIG, RR_ITMZ_NONE); // Replace text up to previous char
  1252. }
  1253. }
  1254. else
  1255. {
  1256. // IME in progress, only need to check ASCII cases
  1257. BOOL fHandled = FALSE;
  1258. if (ch <= 0x7F)
  1259. {
  1260. if (fUIFont)
  1261. {
  1262. // Use default font
  1263. if (dwCurrentFontUsed != fASCII)
  1264. {
  1265. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1266. 0, &cchM, cpFirst, IGNORE_CURRENT_FONT, RR_ITMZ_NONE); // Replace text up to previous char
  1267. // Use the -1 font charset/face/size so the current font effect
  1268. // will still be used.
  1269. CCharFormat CFDefault = *ped->GetCharFormat(-1);
  1270. SetCharFormat(&CFDefault, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1271. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1272. fReplacedText = TRUE;
  1273. }
  1274. dwCurrentFontUsed = fASCII;
  1275. fHandled = TRUE;
  1276. }
  1277. else if (ped->_fDualFont && IsAlpha(ch))
  1278. {
  1279. // Use English Font
  1280. if (dwCurrentFontUsed != fALPHA)
  1281. {
  1282. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1283. dw, &cchM, cpFirst, IGNORE_CURRENT_FONT, RR_ITMZ_NONE); // Replace text up to previous char
  1284. fReplacedText = TRUE;
  1285. }
  1286. dwCurrentFontUsed = fALPHA;
  1287. fHandled = TRUE;
  1288. }
  1289. }
  1290. // Use current FE font
  1291. if (!fHandled)
  1292. {
  1293. if (dwCurrentFontUsed != fFE)
  1294. {
  1295. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1296. 0, &cchM, cpFirst, IGNORE_CURRENT_FONT, RR_ITMZ_NONE); // Replace text up to previous char
  1297. SetCharFormat(&CFCurrent, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1298. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1299. fReplacedText = TRUE;
  1300. }
  1301. dwCurrentFontUsed = fFE;
  1302. }
  1303. }
  1304. if (fReplacedText)
  1305. {
  1306. dwCharMask = (dw & fSYMBOL) ? fSYMBOL : GetCharSetMask();
  1307. if(cchM)
  1308. cchMove = cchM; // Can only happen on 1st replace
  1309. pch = fMultiLine ? pchD : pchS;
  1310. cch = 0;
  1311. }
  1312. }
  1313. if(fMultiLine) // In multiline controls, collect
  1314. { // possibly translated chars
  1315. if(dw & fSYMBOL) // Convert 0xF000 thru 0xF0FF to
  1316. ch &= 0xFF; // SYMBOL_CHARSET with 0x00 thru
  1317. *pchD++ = ch; // 0xFF. FUTURE: make work for
  1318. } // single line too...
  1319. }
  1320. ped->OrCharFlags(dwCharFlags, publdr);
  1321. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr, 0, &cchM, cpFirst,
  1322. IGNORE_CURRENT_FONT, RR_ITMZ_NONE);
  1323. if(cchM)
  1324. cchMove = cchM; // Can only happen on 1st replace
  1325. if (pcchMove)
  1326. *pcchMove = cchMove;
  1327. if (ped->IsComplexScript())
  1328. {
  1329. if (dwFlags & RR_ITMZ_NONE || ped->IsStreaming())
  1330. ped->_fItemizePending = TRUE;
  1331. else
  1332. ItemizeReplaceRange(cchNew, cchMove, publdr, dwFlags & RR_ITMZ_UNICODEBIDI);
  1333. }
  1334. return cchNew;
  1335. }
  1336. /*
  1337. * CTxtRange::CheckLimitReplaceRange(cchNew, *pch, fTestLimit, publdr,
  1338. * dwFlags, pcchMove, prp, iMatchCurrent, dwFlags)
  1339. * @mfunc
  1340. * Replace the text in this range by pch using CCharFormat _iFormat
  1341. * and updating other text runs as needed.
  1342. *
  1343. * @rdesc
  1344. * Count of new characters added
  1345. *
  1346. * @devnote
  1347. * moves this text pointer to end of replaced text and
  1348. * may move text block and formatting arrays
  1349. */
  1350. LONG CTxtRange::CheckLimitReplaceRange (
  1351. LONG cch, //@parm Length of replacement text
  1352. TCHAR const * pch, //@parm Replacement text
  1353. BOOL fTestLimit, //@parm Whether to do limit test
  1354. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1355. DWORD dwCharFlags, //@parm CharFlags following pch
  1356. LONG * pcchMove, //@parm Count of chars moved in 1st replace
  1357. LONG cpFirst, //@parm Starting cp for font binding
  1358. int iMatchCurrent, //@parm Font matching method
  1359. DWORD dwFlags) //@parm ReplaceRange's flags
  1360. {
  1361. CTxtEdit *ped = GetPed();
  1362. if(cch || _cch)
  1363. {
  1364. if(fTestLimit)
  1365. {
  1366. LONG cchLen = CalcTextLenNotInRange();
  1367. DWORD cchMax = ped->TxGetMaxLength();
  1368. if((DWORD)(cch + cchLen) > cchMax) // New plus old count exceeds
  1369. { // max allowed, so truncate
  1370. cch = cchMax - cchLen; // down to what fits
  1371. cch = max(cch, 0); // Keep it positive
  1372. ped->GetCallMgr()->SetMaxText(); // Report exceeded
  1373. }
  1374. }
  1375. if (cch && ped->IsAutoFont() && !ped->_fIMEInProgress)
  1376. {
  1377. LONG iFormatTemp;
  1378. if (fUseUIFont() && GetAdjustedTextLength() != _cch)
  1379. {
  1380. // Delete the old string first so _iFormat is defined
  1381. ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE, pcchMove, dwFlags);
  1382. iFormatTemp = _iFormat;
  1383. }
  1384. else
  1385. iFormatTemp = GetiFormat();
  1386. BYTE bCharSetCurrent = ped->GetCharFormat(iFormatTemp)->_bCharSet;
  1387. if (IsFECharSet(bCharSetCurrent))
  1388. {
  1389. // Check if current font can handle this string.
  1390. INT cpgCurrent = GetCodePage(bCharSetCurrent);
  1391. INT cpgNew = VerifyFEString(cpgCurrent, pch, cch, FALSE);
  1392. if (cpgCurrent != cpgNew)
  1393. {
  1394. // Setup the new CodePage to handle this string
  1395. CCharFormat CF;
  1396. CCFRunPtr rp(_rpCF, ped);
  1397. rp.AdvanceCp(cpFirst - GetCp());
  1398. CF._bCharSet = GetCharSet(cpgNew);
  1399. if(rp.GetPreferredFontInfo(cpgNew, CF._bCharSet, CF._iFont, CF._yHeight,
  1400. CF._bPitchAndFamily, _iFormat, iMatchCurrent))
  1401. {
  1402. SetCharFormat(&CF, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1403. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1404. }
  1405. }
  1406. }
  1407. }
  1408. cch = ReplaceRange(cch, pch, publdr, SELRR_REMEMBERRANGE, pcchMove, dwFlags);
  1409. }
  1410. if(dwCharFlags)
  1411. {
  1412. CCharFormat CF;
  1413. CCFRunPtr rp(_rpCF, ped);
  1414. rp.AdvanceCp(cpFirst - GetCp());
  1415. // If following string contains Hangul or Kana, use Korean or Japanese
  1416. // font signatures, respectively. Else use incoming dwCharFlags
  1417. dwCharFlags &= ~255;
  1418. if(dwCharFlags & fHANGUL)
  1419. dwCharFlags = fHANGUL;
  1420. else if(dwCharFlags & fKANA)
  1421. dwCharFlags = fKANA;
  1422. else if(dwCharFlags & fBIG5)
  1423. dwCharFlags = fBIG5;
  1424. else
  1425. Assert(!(dwCharFlags & fCHINESE) || dwCharFlags == (fCHINESE & ~255));
  1426. LONG i = W32->ScriptIndexFromFontSig(dwCharFlags >> 8);
  1427. CF._bCharSet = GetCharSet(i, NULL);
  1428. if(rp.GetPreferredFontInfo(i, CF._bCharSet, CF._iFont, CF._yHeight,
  1429. CF._bPitchAndFamily, (_cch ? -1 : _iFormat), iMatchCurrent))
  1430. {
  1431. SetCharFormat(&CF, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1432. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1433. }
  1434. }
  1435. return cch;
  1436. }
  1437. /*
  1438. * CTxtRange::ReplaceRange(cchNew, *pch, publdr. selaemode, pcchMove)
  1439. *
  1440. * @mfunc
  1441. * Replace the text in this range by pch using CCharFormat _iFormat
  1442. * and updating other text runs as needed.
  1443. *
  1444. * @rdesc
  1445. * Count of new characters added
  1446. *
  1447. * @devnote
  1448. * moves this text pointer to end of replaced text and
  1449. * may move text block and formatting arrays
  1450. */
  1451. LONG CTxtRange::ReplaceRange (
  1452. LONG cchNew, //@parm Length of replacement text
  1453. TCHAR const * pch, //@parm Replacement text
  1454. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1455. SELRR selaemode, //@parm Controls how selection antievents are to be generated.
  1456. LONG* pcchMove, //@parm number of chars moved after replace
  1457. DWORD dwFlags) //@parm Special flags
  1458. {
  1459. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::ReplaceRange");
  1460. LONG lRet;
  1461. LONG iFormat = _iFormat;
  1462. BOOL fReleaseFormat = FALSE;
  1463. ICharFormatCache * pcf = GetCharFormatCache();
  1464. _TEST_INVARIANT_
  1465. if(!(cchNew | _cch)) // Nothing to add or delete,
  1466. { // so we're done
  1467. if(pcchMove)
  1468. *pcchMove = 0;
  1469. return 0;
  1470. }
  1471. if(publdr && selaemode != SELRR_IGNORE)
  1472. {
  1473. Assert(selaemode == SELRR_REMEMBERRANGE);
  1474. HandleSelectionAEInfo(GetPed(), publdr, GetCp(), _cch,
  1475. GetCpMin() + cchNew, 0, SELAE_MERGE);
  1476. }
  1477. if(_cch > 0)
  1478. FlipRange();
  1479. // If we are replacing a non-degenerate selection, then the Word95
  1480. // UI specifies that we should use the rightmost formatting at cpMin.
  1481. if(_cch < 0 && _rpCF.IsValid() && !_fDualFontMode && !_fUseiFormat)
  1482. {
  1483. _rpCF.AdjustForward();
  1484. iFormat = _rpCF.GetFormat();
  1485. // This is a bit icky, but the idea is to stabilize the
  1486. // reference count on iFormat. When we get it above, it's
  1487. // not addref'ed, so if we happen to delete the text in the
  1488. // range and the range is the only one with that format,
  1489. // then the format will go away.
  1490. pcf->AddRef(iFormat);
  1491. fReleaseFormat = TRUE;
  1492. }
  1493. _fUseiFormat = FALSE;
  1494. LONG cchForReplace = -_cch;
  1495. _cch = 0;
  1496. lRet = CRchTxtPtr::ReplaceRange(cchForReplace, cchNew, pch, publdr,
  1497. iFormat, pcchMove, dwFlags);
  1498. if(lRet)
  1499. _fMoveBack = FALSE;
  1500. Update_iFormat(fReleaseFormat ? iFormat : -1);
  1501. if(fReleaseFormat)
  1502. {
  1503. Assert(pcf);
  1504. pcf->Release(iFormat);
  1505. }
  1506. return lRet;
  1507. }
  1508. /*
  1509. * CTxtRange::Delete(publdr. selaemode)
  1510. *
  1511. * @mfunc
  1512. * Delete text in this range.
  1513. */
  1514. void CTxtRange::Delete (
  1515. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1516. SELRR selaemode) //@parm Controls generation of selection antievents
  1517. {
  1518. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Delete");
  1519. if(!_cch)
  1520. return; // Nothing to delete
  1521. if(!GetPed()->IsBiDi())
  1522. {
  1523. ReplaceRange(0, NULL, publdr, selaemode, NULL);
  1524. return;
  1525. }
  1526. CFreezeDisplay fd(GetPed()->_pdp);
  1527. ReplaceRange(0, NULL, publdr, selaemode);
  1528. }
  1529. /*
  1530. * CTxtRange::GetCharFormat(pCF, flags)
  1531. *
  1532. * @mfunc
  1533. * Set *pCF = CCharFormat for this range. If cbSize = sizeof(CHARFORMAT)
  1534. * only transfer CHARFORMAT data.
  1535. *
  1536. * @rdesc
  1537. * Mask of unchanged properties over range (for CHARFORMAT::dwMask)
  1538. *
  1539. * @devnote
  1540. * NINCH means No Input No CHange (a Microsoft Word term). Here used for
  1541. * properties that change during the range of cch characters. NINCHed
  1542. * properties in a Word-Font dialog have grayed boxes. They are indicated
  1543. * by zero values in their respective dwMask bit positions. Note that
  1544. * a blank at the end of the range does not participate in the NINCH
  1545. * test, i.e., it can have a different CCharFormat without zeroing the
  1546. * corresponding dwMask bits. This is done to be compatible with Word
  1547. * (see also CTxtSelection::SetCharFormat when _fWordSelMode is TRUE).
  1548. */
  1549. DWORD CTxtRange::GetCharFormat (
  1550. CCharFormat *pCF, //@parm CCharFormat to fill with results
  1551. DWORD flags) const //@parm flags
  1552. {
  1553. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetCharFormat");
  1554. _TEST_INVARIANT_
  1555. CTxtEdit * const ped = GetPed();
  1556. if(!_cch || !_rpCF.IsValid()) // IP or invalid CF
  1557. { // run ptr: use CF at
  1558. *pCF = *ped->GetCharFormat(_iFormat); // this text ptr
  1559. return CFM_ALL2;
  1560. }
  1561. LONG cpMin, cpMost; // Nondegenerate range:
  1562. LONG cch = GetRange(cpMin, cpMost); // need to scan
  1563. LONG cchChunk; // cch in CF run
  1564. DWORD dwMask = CFM_ALL2; // Initially all prop def'd
  1565. LONG iDirection; // Direction of scan
  1566. CFormatRunPtr rp(_rpCF); // Nondegenerate range
  1567. /*
  1568. * The code below reads character formatting the way Word does it,
  1569. * that is, by not including the formatting of the last character in the
  1570. * range if that character is a blank.
  1571. *
  1572. * See also the corresponding code in CTxtSelection::SetCharFormat().
  1573. */
  1574. if(cch > 1 && _fSel && (flags & SCF_USEUIRULES))// If more than one char,
  1575. { // don't include trailing
  1576. CTxtPtr tp(ped, cpMost - 1); // blank in NINCH test
  1577. if(tp.GetChar() == ' ')
  1578. { // Have trailing blank:
  1579. cch--; // one less char to check
  1580. if(_cch > 0) // Will scan backward, so
  1581. rp.AdvanceCp(-1); // backup before blank
  1582. }
  1583. }
  1584. if(_cch < 0) // Setup direction and
  1585. { // initial cchChunk
  1586. iDirection = 1; // Scan forward
  1587. rp.AdjustForward();
  1588. cchChunk = rp.GetCchLeft(); // Chunk size for _rpCF
  1589. }
  1590. else
  1591. {
  1592. iDirection = -1; // Scan backward
  1593. rp.AdjustBackward(); // If at BOR, go to
  1594. cchChunk = rp.GetIch(); // previous EOR
  1595. }
  1596. *pCF = *ped->GetCharFormat(rp.GetFormat()); // Initialize *pCF to
  1597. // starting format
  1598. while(cchChunk < cch) // NINCH properties that
  1599. { // change over the range
  1600. cch -= cchChunk; // given by cch
  1601. if(!rp.ChgRun(iDirection)) // No more runs
  1602. break; // (cch too big)
  1603. cchChunk = rp.GetRun(0)->_cch;
  1604. const CCharFormat *pCFTemp = ped->GetCharFormat(rp.GetFormat());
  1605. dwMask &= ~pCFTemp->Delta(pCF, // NINCH properties that
  1606. flags & CFM2_CHARFORMAT); // changed, i.e., reset
  1607. } // corresponding bits
  1608. return dwMask;
  1609. }
  1610. /*
  1611. * CTxtRange::SetCharFormat(pCF, flags, publdr, dwMask, dwMask2)
  1612. *
  1613. * @mfunc
  1614. * apply CCharFormat *pCF to this range. If range is an insertion point,
  1615. * and (flags & SCF_WORD) != 0, then apply CCharFormat to word surrounding
  1616. * this insertion point
  1617. *
  1618. * @rdesc
  1619. * HRESULT = (successfully set whole range) ? NOERROR : S_FALSE
  1620. *
  1621. * @devnote
  1622. * SetParaFormat() is similar, but simpler, since it doesn't have to
  1623. * special case insertion-point ranges or worry about bullet character
  1624. * formatting, which is given by EOP formatting.
  1625. */
  1626. HRESULT CTxtRange::SetCharFormat (
  1627. const CCharFormat *pCF, //@parm CCharFormat to fill with results
  1628. DWORD flags, //@parm SCF_WORD OR SCF_IGNORESELAE
  1629. IUndoBuilder *publdr, //@parm Undo builder to use
  1630. DWORD dwMask, //@parm CHARFORMAT2 mask
  1631. DWORD dwMask2) //@parm Second mask
  1632. {
  1633. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::SetCharFormat");
  1634. LONG cch = -_cch; // Defaults for _cch <= 0
  1635. LONG cchBack = 0; // cch to back up for formatting
  1636. LONG cchFormat; // cch for formatting
  1637. CCharFormat CF; // Temporary CF
  1638. LONG cp;
  1639. LONG cpMin, cpMost;
  1640. LONG cpStart = 0;
  1641. LONG cpWordMin, cpWordMost;
  1642. BOOL fApplyToEOP = FALSE;
  1643. BOOL fProtected = FALSE;
  1644. HRESULT hr = NOERROR;
  1645. LONG iCF;
  1646. CTxtEdit * const ped = GetPed(); // defined and not style
  1647. ICharFormatCache * pf = GetCharFormatCache();
  1648. CFreezeDisplay fd(ped->_pdp);
  1649. _TEST_INVARIANT_
  1650. if(!Check_rpCF()) // Not rich
  1651. return NOERROR;
  1652. if(_cch > 0) // Active end at range end
  1653. {
  1654. cchBack = -_cch; // Setup to back up to
  1655. cch = _cch; // start of format area
  1656. }
  1657. else if(_cch < 0)
  1658. _rpCF.AdjustForward();
  1659. else if(!cch && (flags & (SCF_WORD | SCF_USEUIRULES)))
  1660. {
  1661. BOOL fCheckEOP = TRUE;
  1662. if(flags & SCF_WORD)
  1663. {
  1664. FindWord(&cpWordMin, &cpWordMost, FW_EXACT);
  1665. // If nearest word is within this range, calculate cchback and cch
  1666. // so that we can apply the given format to the word
  1667. if(cpWordMin < GetCp() && GetCp() < cpWordMost)
  1668. {
  1669. // RichEdit 1.0 made 1 final check: ensure word's format
  1670. // is constant w.r.t. the format passed in
  1671. CTxtRange rg(*this);
  1672. rg.Set(cpWordMin, cpWordMin - cpWordMost);
  1673. fProtected = rg.WriteAccessDenied();
  1674. if(!fProtected && (rg.GetCharFormat(&CF) & dwMask) == dwMask)
  1675. {
  1676. cchBack = cpWordMin - GetCp();
  1677. cch = cpWordMost - cpWordMin;
  1678. }
  1679. fCheckEOP = FALSE;
  1680. }
  1681. }
  1682. if(fCheckEOP && _rpTX.IsAtEOP() && !GetPF()->_wNumbering)
  1683. {
  1684. CTxtPtr tp(_rpTX);
  1685. cch = tp.AdvanceCpCRLF();
  1686. _rpCF.AdjustForward(); // Go onto format EOP
  1687. fApplyToEOP = TRUE;
  1688. // Apply the characterset and face to EOP because EOP can be in any charset
  1689. dwMask2 |= CFM2_NOCHARSETCHECK;
  1690. }
  1691. }
  1692. cchFormat = cch;
  1693. BOOL fApplyStyle = pCF->fSetStyle(dwMask, dwMask2);
  1694. if(!cch) // Set degenerate-range (IP)
  1695. { // CF
  1696. LApplytoIP:
  1697. DWORD dwMsk = dwMask;
  1698. dwMask2 |= CFM2_NOCHARSETCHECK;
  1699. CF = *ped->GetCharFormat(_iFormat); // Copy current CF at IP to CF
  1700. if ((CF._dwEffects & CFE_LINK) && // Don't allow our URL
  1701. ped->GetDetectURL()) // formatting to be changed
  1702. {
  1703. dwMsk &= ~CFM_LINK;
  1704. }
  1705. if(fApplyStyle)
  1706. CF.ApplyDefaultStyle(pCF->_sStyle);
  1707. hr = CF.Apply(pCF, dwMsk, dwMask2); // Apply *pCF
  1708. if(hr != NOERROR) // Cache result if new
  1709. return hr;
  1710. hr = pf->Cache(&CF, &iCF); // In any case, get iCF
  1711. if(hr != NOERROR) // (which AddRef's it)
  1712. return hr;
  1713. #ifdef LINESERVICES
  1714. if (g_pols)
  1715. g_pols->DestroyLine(NULL);
  1716. #endif
  1717. pf->Release(_iFormat);
  1718. _iFormat = iCF;
  1719. if(fProtected) // Signal to beep if UI
  1720. hr = S_FALSE;
  1721. }
  1722. else // Set nondegenerate-range CF
  1723. { // Get start of affected area
  1724. CNotifyMgr *pnm = NULL;
  1725. if (!(flags & SCF_IGNORENOTIFY))
  1726. {
  1727. pnm = ped->GetNotifyMgr(); // Get the notification mgr
  1728. if(pnm)
  1729. {
  1730. cpStart = GetCp() + cchBack; // Bulletting may move
  1731. // affected area back if
  1732. if(GetPF()->_wNumbering) // formatting hits EOP that
  1733. { // affects bullet at BOP
  1734. FindParagraph(&cpMin, &cpMost);
  1735. if(cpMost <= GetCpMost())
  1736. cpStart = cpMin;
  1737. }
  1738. pnm->NotifyPreReplaceRange(this, // Notify interested parties of
  1739. CP_INFINITE, 0, 0, cpStart, // the impending update
  1740. cpStart + cchFormat);
  1741. }
  1742. }
  1743. _rpCF.AdvanceCp(cchBack); // Back up to formatting start
  1744. CFormatRunPtr rp(_rpCF); // Clone _rpCF to walk range
  1745. cp = GetCp() + cchBack;
  1746. if(publdr)
  1747. {
  1748. LONG cchBackup = 0, cchAdvance = 0;
  1749. if (ped->IsBiDi())
  1750. {
  1751. CRchTxtPtr rtp(*this);
  1752. rtp._rpCF.AdvanceCp(-cchBack); // restore previous _rpCF
  1753. rtp.Advance(cchBack);
  1754. cchBackup = rtp.ExpandRangeFormatting(cch, 0, cchAdvance);
  1755. Assert(cchBackup >= 0);
  1756. }
  1757. rp.AdvanceCp(-cchBackup);
  1758. IAntiEvent *pae = gAEDispenser.CreateReplaceFormattingAE(
  1759. ped, rp, cch + cchBackup + cchAdvance, pf, CharFormat);
  1760. rp.AdvanceCp(cchBackup);
  1761. if(pae)
  1762. publdr->AddAntiEvent(pae);
  1763. }
  1764. // Following Word, we translate runs for 8-bit charsets to/from
  1765. // SYMBOL_CHARSET
  1766. LONG cchRun;
  1767. LONG cchTrans;
  1768. UINT CodePage = 0;
  1769. DWORD dwFontSig = 0;
  1770. DWORD dwMaskSave = dwMask;
  1771. DWORD dwMask2Save = dwMask2;
  1772. LONG cchSkip = 0;
  1773. BOOL fSymbolCharSet = IsSymbolOrOEM(pCF->_bCharSet);
  1774. BOOL fBiDiCharSet = IsBiDiCharSet(pCF->_bCharSet);
  1775. BOOL fFECharSet = IsFECharSet(pCF->_bCharSet);
  1776. BOOL fFontCheck = (dwMask2 & CFM2_MATCHFONT);
  1777. BOOL fInRange;
  1778. CTxtPtr tp(_rpTX);
  1779. if(fFontCheck && !fSymbolCharSet)
  1780. {
  1781. GetFontSignatureFromFace(pCF->_iFont, &dwFontSig);
  1782. if(!dwFontSig)
  1783. dwFontSig = GetFontSig(pCF->_bCharSet);
  1784. }
  1785. if (ped->_fIMEInProgress && !(dwMask2 & CFM2_SCRIPT))
  1786. dwMask2 |= CFM2_NOCHARSETCHECK; // Don't check charset or it will display garbage
  1787. while(cch > 0 && rp.IsValid())
  1788. {
  1789. CF = *ped->GetCharFormat(rp.GetFormat());// Copy rp CF to temp CF
  1790. if(fApplyStyle)
  1791. CF.ApplyDefaultStyle(pCF->_sStyle);
  1792. cchRun = cch;
  1793. if (CF._dwEffects & CFE_RUNISDBCS)
  1794. {
  1795. // Don't allow charset/face name change for DBCS run
  1796. // causing these are garbage characters
  1797. dwMask &= ~(CFM_CHARSET | CFM_FACE);
  1798. }
  1799. else if(fFontCheck) // Only apply font if it
  1800. { // supports run's charset
  1801. cchRun = rp.GetCchLeft(); // Translate up to end of
  1802. cchRun = min(cch, cchRun); // current CF run
  1803. dwMask &= ~CFM_CHARSET; // Default no charset change
  1804. if(cchSkip)
  1805. { // Skip cchSkip chars (were
  1806. cchRun = cchSkip; // not translatable with
  1807. cchSkip = 0; // CodePage)
  1808. }
  1809. else if(fSymbolCharSet ^ IsSymbolOrOEM(CF._bCharSet))
  1810. { // SYMBOL to/from nonSYMBOL
  1811. CodePage = GetCodePage(fSymbolCharSet ? CF._bCharSet : pCF->_bCharSet);
  1812. if(!Is8BitCodePage(CodePage))
  1813. goto DoASCII;
  1814. dwMask |= CFM_CHARSET; // Need to change charset
  1815. if(fSymbolCharSet)
  1816. CF._wCodePageSave = CodePage;
  1817. else if(Is8BitCodePage(CF._wCodePageSave))
  1818. {
  1819. CodePage = CF._wCodePageSave;
  1820. CF._bCharSet = GetCharSet(CodePage);
  1821. dwMask &= ~CFM_CHARSET; // Already changed
  1822. }
  1823. tp.SetCp(cp); // Point tp at start of run
  1824. cchTrans = tp.TranslateRange(cchRun, CodePage, fSymbolCharSet,
  1825. publdr /*, cchSkip */);
  1826. if(cchTrans < cchRun) // Ran into char not in
  1827. { // CodePage, so set up to
  1828. cchSkip = 1; // skip the char
  1829. cchRun = cchTrans; // FUTURE: use cchSkip out
  1830. if(!cchRun) // parm from TranslateRange
  1831. continue; // instead of skipping 1 char
  1832. } // at a time
  1833. }
  1834. else if(!fSymbolCharSet)
  1835. {
  1836. DoASCII: tp.SetCp(cp); // Point tp at start of run
  1837. fInRange = tp.GetChar() < 0x80;
  1838. if (!fBiDiCharSet && !IsBiDiCharSet(CF._bCharSet) &&
  1839. fInRange &&
  1840. ((dwFontSig & fASCII >> 8) == fASCII >> 8 || fFECharSet || fSymbolCharSet))
  1841. {
  1842. // ASCII text and new font supports ASCII
  1843. // -FUTURE-
  1844. // We exlude BiDi here. We cannot allow applying BiDi charset to non-BiDi run or vice versa.
  1845. // This because we use charset for BiDi reordering. In the future we should
  1846. // evolve to something more elegant than charset.
  1847. if (!(GetFontSig(CF._bCharSet) & ~(fASCII >> 8) & dwFontSig))
  1848. // New font doesnt support underlying charset,
  1849. // apply new charset to ASCII
  1850. dwMask |= CFM_CHARSET;
  1851. }
  1852. else if (!(GetFontSig(CF._bCharSet) & ~(fASCII >> 8) & dwFontSig))
  1853. // New font doesnt support underlying charset,
  1854. // suppress both new charset and facename
  1855. dwMask &= ~CFM_FACE;
  1856. cchRun -= tp.MoveWhile(cchRun, 0, 0x7F, fInRange);
  1857. }
  1858. }
  1859. hr = CF.Apply(pCF, dwMask, dwMask2);// Apply *pCF
  1860. if(hr != NOERROR)
  1861. return hr;
  1862. dwMask = dwMaskSave; // Restore mask in case
  1863. dwMask2 = dwMask2Save; // changed above
  1864. hr = pf->Cache(&CF, &iCF); // Cache result if new, In any
  1865. if(hr != NOERROR) // cause, use format index iCF
  1866. break;
  1867. #ifdef LINESERVICES
  1868. if (g_pols)
  1869. g_pols->DestroyLine(NULL);
  1870. #endif
  1871. cchRun = rp.SetFormat(iCF, cchRun, pf);// Set format for this run
  1872. // Proper levels will be generated later by BiDi FSM.
  1873. pf->Release(iCF); // Release count from Cache above
  1874. // rp.SetFormat AddRef's as needed
  1875. if(cchRun == CP_INFINITE)
  1876. {
  1877. ped->GetCallMgr()->SetOutOfMemory();
  1878. break;
  1879. }
  1880. cp += cchRun;
  1881. cch -= cchRun;
  1882. }
  1883. _rpCF.AdjustBackward(); // Expand scope for merging
  1884. rp.AdjustForward(); // runs
  1885. rp.MergeRuns(_rpCF._iRun, pf); // Merge adjacent runs that
  1886. // have the same format
  1887. if(cchBack) // Move _rpCF back to where it
  1888. _rpCF.AdvanceCp(-cchBack); // was
  1889. else // Active end at range start:
  1890. _rpCF.AdjustForward(); // don't leave at EOR
  1891. if(pnm)
  1892. {
  1893. pnm->NotifyPostReplaceRange(this, // Notify interested parties
  1894. CP_INFINITE, 0, 0, cpStart, // of the change.
  1895. cpStart + cchFormat - cch);
  1896. }
  1897. if(publdr && !(flags & SCF_IGNORESELAE))
  1898. {
  1899. HandleSelectionAEInfo(ped, publdr, GetCp(), _cch, GetCp(), _cch,
  1900. SELAE_FORCEREPLACE);
  1901. }
  1902. if(!_cch) // In case IP with ApplyToWord
  1903. {
  1904. if(fApplyToEOP) // Formatting EOP only
  1905. goto LApplytoIP;
  1906. Update_iFormat(-1);
  1907. }
  1908. if (ped->IsRich())
  1909. ped->GetCallMgr()->SetChangeEvent(CN_GENERIC);
  1910. }
  1911. if(_fSel && ped->IsRich() && !ped->_f10Mode /*bug fix #5211*/)
  1912. ped->GetCallMgr()->SetSelectionChanged();
  1913. AssertSz(GetCp() == (cp = _rpCF.CalculateCp()),
  1914. "RTR::SetCharFormat(): incorrect format-run ptr");
  1915. if (!(dwMask2 & (CFM2_SCRIPT | CFM2_HOLDITEMIZE)) && cchFormat && hr == NOERROR && !cch)
  1916. {
  1917. // A non-degenerate range not coming from ItemizeRuns
  1918. // It's faster to make a copy pointer since we dont need to worry about fExtend.
  1919. CRchTxtPtr rtp(*this);
  1920. rtp.Advance(cchBack + cchFormat);
  1921. rtp.ItemizeReplaceRange(cchFormat, 0, publdr);
  1922. return hr;
  1923. }
  1924. return (hr == NOERROR && cch) ? S_FALSE : hr;
  1925. }
  1926. /*
  1927. * CTxtRange::GetParaFormat(pPF)
  1928. *
  1929. * @mfunc
  1930. * return CParaFormat for this text range. If no PF runs are allocated,
  1931. * then return default CParaFormat
  1932. *
  1933. * @rdesc
  1934. * Mask of defined properties: 1 bit means corresponding property is
  1935. * defined and constant throughout range. 0 bit means it isn't constant
  1936. * throughout range. Note that PARAFORMAT has fewer relevant bits
  1937. * (PFM_ALL vs PFM_ALL2)
  1938. */
  1939. DWORD CTxtRange::GetParaFormat (
  1940. CParaFormat *pPF, //@parm ptr to CParaFormat to be filled
  1941. DWORD flags) const // be filled with possibly NINCH'd values
  1942. {
  1943. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetParaFormat");
  1944. CTxtEdit * const ped = GetPed();
  1945. _TEST_INVARIANT_
  1946. DWORD dwMask = flags & PFM_PARAFORMAT // Default presence of
  1947. ? PFM_ALL : PFM_ALL2; // all properties
  1948. CFormatRunPtr rp(_rpPF);
  1949. LONG cch = -_cch;
  1950. if(cch < 0) // At end of range:
  1951. { // go to start of range
  1952. rp.AdvanceCp(cch);
  1953. cch = -cch; // Count with cch > 0
  1954. }
  1955. *pPF = *ped->GetParaFormat(rp.GetFormat()); // Initialize *pPF to
  1956. // starting paraformat
  1957. if(!cch || !rp.IsValid()) // No cch or invalid PF
  1958. return dwMask; // run ptr: use PF at
  1959. // this text ptr
  1960. LONG cchChunk = rp.GetCchLeft(); // Chunk size for rp
  1961. while(cchChunk < cch) // NINCH properties that
  1962. { // change over the range
  1963. cch -= cchChunk; // given by cch
  1964. if(!rp.NextRun()) // Go to next run // No more runs
  1965. break; // (cch too big)
  1966. cchChunk = rp.GetCchLeft();
  1967. dwMask &= ~ped->GetParaFormat(rp.GetFormat())// NINCH properties that
  1968. ->Delta(pPF, flags & PFM_PARAFORMAT); // changed, i.e., reset
  1969. } // corresponding bits
  1970. return dwMask;
  1971. }
  1972. /*
  1973. * CTxtRange::SetParaFormat(pPF, publdr, dwMask)
  1974. *
  1975. * @mfunc
  1976. * apply CParaFormat *pPF to this range.
  1977. *
  1978. * @rdesc
  1979. * if successfully set whole range, return NOERROR, otherwise
  1980. * return error code or S_FALSE.
  1981. */
  1982. HRESULT CTxtRange::SetParaFormat (
  1983. const CParaFormat* pPF, //@parm CParaFormat to apply to this range
  1984. IUndoBuilder *publdr, //@parm Undo context for this operation
  1985. DWORD dwMask) //@parm Mask to use
  1986. {
  1987. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::SetParaFormat");
  1988. LONG cch; // Length of text to format
  1989. LONG cchBack; // cch to back up for formatting
  1990. LONG cp;
  1991. LONG cpMin, cpMost; // Limits of text to format
  1992. LONG delta;
  1993. HRESULT hr = NOERROR;
  1994. LONG iPF = 0; // Lndex to a CParaFormat
  1995. CTxtEdit * const ped = GetPed();
  1996. CParaFormat PF; // Temporary CParaFormat
  1997. IParaFormatCache * pf = GetParaFormatCache();// Format cache ptr for Cache,
  1998. // AddRefFormat, ReleaseFormat
  1999. CBiDiLevel* pLevel;
  2000. CBiDiLevel lvRTL = {1, 0};
  2001. CBiDiLevel lvLTR = {0, 0};
  2002. CFreezeDisplay fd(ped->_pdp);
  2003. _TEST_INVARIANT_
  2004. if(!Check_rpPF())
  2005. return E_FAIL;
  2006. FindParagraph(&cpMin, &cpMost); // Get limits of text to
  2007. cch = cpMost - cpMin; // format, namely closest
  2008. CNotifyMgr *pnm = ped->GetNotifyMgr();
  2009. if(pnm)
  2010. {
  2011. pnm->NotifyPreReplaceRange(this, // Notify interested parties of
  2012. CP_INFINITE, 0, 0, cpMin, cpMost); // the impending update
  2013. }
  2014. cchBack = cpMin - GetCp();
  2015. _rpPF.AdvanceCp(cchBack); // Back up to formatting start
  2016. CFormatRunPtr rp(_rpPF); // Clone _rpPF to walk range
  2017. if(publdr)
  2018. {
  2019. IAntiEvent *pae = gAEDispenser.CreateReplaceFormattingAE(ped,
  2020. rp, cch, pf, ParaFormat);
  2021. if(pae)
  2022. publdr->AddAntiEvent(pae);
  2023. }
  2024. const CParaFormat* pPFCurrent; // PF at current runptr
  2025. BOOL fLevelChanged = FALSE;
  2026. BOOL fFullyDefined = FALSE; // Default input PF not fully defined
  2027. if (ped->HandleStyle(&PF, pPF, dwMask) == NOERROR &&
  2028. pf->Cache(&PF, &iPF) == NOERROR)
  2029. {
  2030. fFullyDefined = TRUE;
  2031. }
  2032. do
  2033. {
  2034. WORD wEffectsCurrent;
  2035. pPFCurrent = ped->GetParaFormat(rp.GetFormat());// Get current PF
  2036. wEffectsCurrent = pPFCurrent->_wEffects;// Save current effect so we don't
  2037. // need to use pPFCurrent because it may
  2038. // become invalid after a PF.Apply.
  2039. if(!fFullyDefined) // If pPF doesn't specify
  2040. { // full PF, fill in undefined
  2041. PF = *pPFCurrent;
  2042. hr = PF.Apply(pPF, dwMask); // Apply *pPF
  2043. if(hr != NOERROR) // (Probably E_INVALIDARG)
  2044. break; // Cache result if new; in any
  2045. hr = pf->Cache(&PF, &iPF); // case, get format index iPF
  2046. if(hr != NOERROR) // Can't necessarily return
  2047. break; // error, since may need
  2048. }
  2049. if (!fLevelChanged)
  2050. fLevelChanged = (wEffectsCurrent ^ PF._wEffects) & PFE_RTLPARA;
  2051. pLevel = PF.IsRtlPara() ? &lvRTL : &lvLTR;
  2052. delta = rp.SetFormat(iPF, cch, pf, pLevel); // Set format for this run
  2053. if(!fFullyDefined) // Release count from Cache above
  2054. pf->Release(iPF); // rp.SetFormat AddRefs as needed
  2055. if(delta == CP_INFINITE)
  2056. {
  2057. ped->GetCallMgr()->SetOutOfMemory();
  2058. break;
  2059. }
  2060. cch -= delta;
  2061. } while (cch > 0) ;
  2062. if(fFullyDefined)
  2063. pf->Release(iPF); // Release count from Cache above
  2064. _rpPF.AdjustBackward(); // If at BOR, go to prev EOR
  2065. rp.MergeRuns(_rpPF._iRun, pf); // Merge any adjacent runs
  2066. // that have the same format
  2067. if(cchBack) // Move _rpPF back to where it
  2068. _rpPF.AdvanceCp(-cchBack); // was
  2069. else // Active end at range start:
  2070. _rpPF.AdjustForward(); // don't leave at EOR
  2071. if(pnm)
  2072. {
  2073. pnm->NotifyPostReplaceRange(this, // Notify interested parties of
  2074. CP_INFINITE, 0, 0, cpMin, cpMost); // the update
  2075. }
  2076. if(publdr)
  2077. {
  2078. // Paraformatting works a bit differently, it just remembers the
  2079. // current selection. Cast selection to range to avoid including
  2080. // _select.h; we only need range methods.
  2081. CTxtRange *psel = (CTxtRange *)GetPed()->GetSel();
  2082. if(psel)
  2083. {
  2084. cp = psel->GetCp();
  2085. HandleSelectionAEInfo(ped, publdr, cp, psel->GetCch(),
  2086. cp, psel->GetCch(), SELAE_FORCEREPLACE);
  2087. }
  2088. }
  2089. ped->GetCallMgr()->SetChangeEvent(CN_GENERIC);
  2090. AssertSz(GetCp() == (cp = _rpPF.CalculateCp()),
  2091. "RTR::SetParaFormat(): incorrect format-run ptr");
  2092. if (fLevelChanged && cpMost > cpMin)
  2093. {
  2094. ped->OrCharFlags(fBIDI, publdr);
  2095. // make sure the CF is valid
  2096. Check_rpCF();
  2097. CTxtRange rg(*this);
  2098. if (publdr)
  2099. {
  2100. // create anti-events to keep BiDi level of paragraphs in need
  2101. //
  2102. ICharFormatCache* pcfc = GetCharFormatCache();
  2103. CFormatRunPtr rp(_rpCF);
  2104. rp.AdvanceCp(cpMin - _rpTX.GetCp());
  2105. IAntiEvent *pae = gAEDispenser.CreateReplaceFormattingAE (
  2106. ped, rp, cpMost - cpMin, pcfc, CharFormat);
  2107. if (pae)
  2108. publdr->AddAntiEvent(pae);
  2109. }
  2110. rg.Set(cpMost, cpMost - cpMin);
  2111. rg.ItemizeRuns (publdr);
  2112. }
  2113. return (hr == NOERROR && cch) ? S_FALSE : hr;
  2114. }
  2115. /*
  2116. * CTxtRange::SetParaStyle(pPF, publdr, dwMask)
  2117. *
  2118. * @mfunc
  2119. * apply CParaFormat *pPF using the style pPF->sStyle to this range.
  2120. *
  2121. * @rdesc
  2122. * if successfully set whole range, return NOERROR, otherwise
  2123. * return error code or S_FALSE.
  2124. *
  2125. * @comm
  2126. * If pPF->dwMask & PFM_STYLE is nonzero, this range is expanded to
  2127. * complete paragraphs. If it's zero, this call just passes control
  2128. * to CTxtRange::SetParaStyle().
  2129. */
  2130. HRESULT CTxtRange::SetParaStyle (
  2131. const CParaFormat* pPF, //@parm CParaFormat to apply to this range
  2132. IUndoBuilder *publdr, //@parm Undo context for this operation
  2133. DWORD dwMask) //@parm Mask to use
  2134. {
  2135. LONG cchSave = _cch; // Save range cp and cch in case
  2136. LONG cpSave = GetCp(); // para expand needed
  2137. HRESULT hr;
  2138. if(publdr)
  2139. publdr->StopGroupTyping();
  2140. if(pPF->fSetStyle(dwMask))
  2141. {
  2142. CCharFormat CF; // Need to apply associated CF
  2143. LONG cpMin, cpMost;
  2144. Expander(tomParagraph, TRUE, NULL, &cpMin, &cpMost);
  2145. CF._sStyle = pPF->_sStyle;
  2146. hr = SetCharFormat(&CF, 0, publdr, CFM_STYLE, 0);
  2147. if(hr != NOERROR)
  2148. return hr;
  2149. }
  2150. hr = SetParaFormat(pPF, publdr, dwMask);
  2151. Set(cpSave, cchSave); // Restore this range in case expanded
  2152. return hr;
  2153. }
  2154. /*
  2155. * CTxtRange::Update_iFormat(iFmtDefault)
  2156. *
  2157. * @mfunc
  2158. * update _iFormat to CCharFormat at current active end
  2159. *
  2160. * @devnote
  2161. * _iFormat is only used when the range is degenerate
  2162. *
  2163. * The Word 95 UI specifies that the *previous* format should
  2164. * be used if we're in at an ambiguous cp (i.e. where a formatting
  2165. * change occurs) _unless_ the previous character is an EOP
  2166. * marker _or_ if the previous character is protected.
  2167. */
  2168. void CTxtRange::Update_iFormat (
  2169. LONG iFmtDefault) //@parm Format index to use if _rpCF isn't valid
  2170. {
  2171. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Update_iFormat");
  2172. DWORD dwEffects;
  2173. LONG ifmt, iFormatForward;
  2174. const CCharFormat *pCF, *pCFForward;
  2175. if(_cch)
  2176. return;
  2177. _fSelHasEOP = FALSE; // Empty ranges don't contain
  2178. _fSelHasCell = FALSE; // anything, incl EOPs/cells
  2179. if(_fDontUpdateFmt) // _iFormat is only used
  2180. return; // for degenerate ranges
  2181. if(_rpCF.IsValid() && iFmtDefault == -1)
  2182. {
  2183. // Get forward info before possibly adjusting backward
  2184. _rpCF.AdjustForward();
  2185. ifmt = iFormatForward = _rpCF.GetFormat();
  2186. pCF = pCFForward = GetPed()->GetCharFormat(ifmt);
  2187. dwEffects = pCF->_dwEffects;
  2188. if(!_rpTX.IsAfterEOP())
  2189. {
  2190. _rpCF.AdjustBackward(); // Adjust backward
  2191. ifmt = _rpCF.GetFormat();
  2192. pCF = GetPed()->GetCharFormat(ifmt);
  2193. dwEffects = pCF->_dwEffects;
  2194. }
  2195. if (!(GetPed()->_fIMEInProgress)) // Dont change fomrat during IME
  2196. {
  2197. if(!_rpTX.GetCp() && (pCF->_dwEffects & CFE_RUNISDBCS))
  2198. {
  2199. // If at beginning of document, and text is protected, just use
  2200. // default format.
  2201. ifmt = iFmtDefault;
  2202. }
  2203. else if(dwEffects & (CFE_PROTECTED | CFE_LINK | CFE_HIDDEN | CFE_RUNISDBCS))
  2204. {
  2205. // If range is protected or a hyperlink, pick forward format
  2206. ifmt = iFormatForward;
  2207. }
  2208. else if(ifmt != iFormatForward && _fMoveBack &&
  2209. IsRTLCharSet(pCF->_bCharSet) != IsRTLCharSet(pCFForward->_bCharSet))
  2210. {
  2211. ifmt = iFormatForward;
  2212. }
  2213. }
  2214. iFmtDefault = ifmt;
  2215. }
  2216. // Don't allow _iFormat to include CFE_LINK or CFE_HIDDEN attributes
  2217. // unless they're the default
  2218. if(iFmtDefault != -1)
  2219. {
  2220. pCF = GetPed()->GetCharFormat(iFmtDefault);
  2221. if(pCF->_dwEffects & (CFE_LINK | CFE_HIDDEN))
  2222. {
  2223. CCharFormat CF = *pCF;
  2224. CF._dwEffects &= ~(CFE_LINK | CFE_HIDDEN);
  2225. // This range must be an insertion point!
  2226. Assert(_cch == 0);
  2227. SetCharFormat(&CF, FALSE, NULL, CFM_ALL2, 0);
  2228. return;
  2229. }
  2230. }
  2231. Set_iCF(iFmtDefault);
  2232. }
  2233. /*
  2234. * CTxtRange::GetCharSetMask(fUseDocFormat)
  2235. *
  2236. * @mfunc
  2237. * Get this range's charset mask corresponding to _iFormat.
  2238. * If fUseDocFormat is TRUE, then use -1 instead of _iFormat.
  2239. *
  2240. * @rdesc
  2241. * charset mask for range or default document
  2242. */
  2243. DWORD CTxtRange::GetCharSetMask(
  2244. BOOL fUseDocFormat)
  2245. {
  2246. LONG iFormat = fUseDocFormat ? -1 : GetiFormat();
  2247. DWORD dwMask = GetFontSig((GetPed()->GetCharFormat(iFormat))->_bCharSet) << 8;
  2248. if(dwMask & fSYMBOL)
  2249. return dwMask;
  2250. // For now, Indic fonts match only ASCII digits
  2251. dwMask |= fBELOWX40;
  2252. if (dwMask < fDEVANAGARI)
  2253. dwMask |= fASCII; // fASCIIUPR+fBELOWX40
  2254. if ((dwMask & (fKANA | fHANGUL | fCHINESE | fBIG5))
  2255. && W32->IsFESystem() ) // For FE systems
  2256. dwMask |= fOTHER; // match fOTHER
  2257. else if(dwMask & fLATIN)
  2258. dwMask |= fCOMBINING;
  2259. return dwMask;
  2260. }
  2261. /*
  2262. * CTxtRange::GetiFormat()
  2263. *
  2264. * @mfunc
  2265. * Return (!_cch || _fUseiFormat) ? _iFormat : iFormat at cpMin
  2266. *
  2267. * @rdesc
  2268. * iFormat at cpMin if nondegenerate and !_fUseiFormat; else _iFormat
  2269. *
  2270. * @devnote
  2271. * This routine doesn't AddRef iFormat, so it shouldn't be used if
  2272. * it needs to be valid after character formatting has changed, e.g.,
  2273. * by ReplaceRange or SetCharFormat or SetParaStyle
  2274. */
  2275. LONG CTxtRange::GetiFormat() const
  2276. {
  2277. if(!_cch || _fUseiFormat)
  2278. return _iFormat;
  2279. if(_cch > 0)
  2280. {
  2281. CFormatRunPtr rp(_rpCF);
  2282. rp.AdvanceCp(-_cch);
  2283. return rp.GetFormat();
  2284. }
  2285. return _rpCF.GetFormat();
  2286. }
  2287. /*
  2288. * CTxtRange::Get_iCF()
  2289. *
  2290. * @mfunc
  2291. * Get this range's _iFormat (AddRef'ing, of course)
  2292. *
  2293. * @devnote
  2294. * Get_iCF() is used by the RTF reader
  2295. */
  2296. LONG CTxtRange::Get_iCF ()
  2297. {
  2298. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Get_iCF");
  2299. GetCharFormatCache()->AddRef(_iFormat);
  2300. return _iFormat;
  2301. }
  2302. /*
  2303. * CTxtRange::Set_iCF(iFormat)
  2304. *
  2305. * @mfunc
  2306. * Set range's _iFormat to iFormat, AddRefing and Releasing as required.
  2307. *
  2308. * @rdesc
  2309. * TRUE if _iFormat changed
  2310. */
  2311. BOOL CTxtRange::Set_iCF (
  2312. LONG iFormat) //@parm Index of char format to use
  2313. {
  2314. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Set_iCF");
  2315. if(iFormat == _iFormat)
  2316. return FALSE;
  2317. ICharFormatCache *pCFC = GetCharFormatCache();
  2318. pCFC->AddRef(iFormat);
  2319. pCFC->Release(_iFormat); // Note: _iFormat = -1 doesn't
  2320. _iFormat = iFormat; // get AddRef'd or Release'd
  2321. AssertSz(GetCF(), "CTxtRange::Set_iCF: illegal format");
  2322. return TRUE;
  2323. }
  2324. /*
  2325. * CTxtRange::Get_iPF()
  2326. *
  2327. * @mfunc
  2328. * Get paragraph format at active end
  2329. *
  2330. * @devnote
  2331. * Get_iPF() is used by the RTF reader on encountering a start group
  2332. */
  2333. LONG CTxtRange::Get_iPF ()
  2334. {
  2335. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Get_iPF");
  2336. LONG iPF = _rpPF.GetFormat();
  2337. GetParaFormatCache()->AddRef(iPF);
  2338. return iPF;
  2339. }
  2340. /*
  2341. * CTxtRange::BiDiLevelFromFSM(pFSM)
  2342. *
  2343. * @mfunc
  2344. * Run BiDi FSM to generate proper embedding level for runs
  2345. *
  2346. * @rdesc
  2347. * HRESULT
  2348. */
  2349. HRESULT CTxtRange::BiDiLevelFromFSM (
  2350. const CBiDiFSM* pFSM) // in: ptr to FSM
  2351. {
  2352. AssertSz(pFSM && _rpCF.IsValid(), "Not enough information to run BiDi FSM");
  2353. LONG cpMin, cpMost, cp, cchLeft;
  2354. LONG ich, cRunsStart, cRuns = 0;
  2355. HRESULT hr = S_OK;
  2356. GetRange(cpMin, cpMost);
  2357. AssertSz (cpMost - cpMin > 0, "FSM: Invalid range");
  2358. CRchTxtPtr rtp(*this);
  2359. rtp.Advance(cpMin - rtp.GetCp()); // initiate position to cpMin
  2360. CFormatRunPtr rpPF(rtp._rpPF); // pointer to current paragraph
  2361. cchLeft = cpMost - cpMin;
  2362. cp = cpMin;
  2363. while (cchLeft > 0 && SUCCEEDED(hr))
  2364. {
  2365. // accumulate runs within the same paragraph level
  2366. cRuns = GetRunsPF(&rtp, &rpPF, cchLeft);
  2367. cRunsStart = 0; // assume no start run
  2368. ich = rtp.Advance(-rtp.GetIchRunCF()); // locate preceding run
  2369. rtp._rpCF.AdjustBackward(); // adjusting format backward
  2370. rtp._rpPF.AdjustBackward(); // adjusting format backward
  2371. if(rtp._rpPF.SameLevel(&rpPF))
  2372. {
  2373. // start at the beginning of preceding run
  2374. if (rtp.Advance(-rtp.GetCchRunCF()))
  2375. cRunsStart++;
  2376. }
  2377. else
  2378. {
  2379. // preceding run is not at the same paragraph level, resume position
  2380. rtp.Advance(-ich);
  2381. }
  2382. rtp._rpCF.AdjustForward(); // make sure we have forward run pointers
  2383. rtp._rpPF.AdjustForward();
  2384. // Run FSM for the number of runs in multiple paragraphs with the same level
  2385. hr = pFSM->RunFSM(&rtp, cRuns, cRunsStart, rtp.IsParaRTL() ? 1 : 0);
  2386. cp = cpMost - cchLeft;
  2387. rtp.Advance(cp - rtp.GetCp()); // Advance to next paragraph(s)
  2388. rpPF = rtp._rpPF; // paragraph format at cp
  2389. }
  2390. AssertSz (cp == cpMost , "Running BiDi FSM partially done!");
  2391. _rpCF = rtp._rpCF; // We may have splitted CF runs
  2392. return hr;
  2393. }
  2394. /*
  2395. * CTxtRange::GetRunsPF(prtp, prpPF, cchLeft)
  2396. *
  2397. * @mfunc
  2398. * Get the number of CF runs within the same paragraph's base level.
  2399. * Its scope could cover multiple paragraphs. As long as they are in the
  2400. * same level, we can run them through the FSM in one go.
  2401. *
  2402. */
  2403. LONG CTxtRange::GetRunsPF(
  2404. CRchTxtPtr* prtp, // in: RichText ptr to the first run in range
  2405. CFormatRunPtr* prpPF, // in: Pointer to current paragraph run
  2406. LONG& cchLeft) // in/out: number of char left
  2407. {
  2408. Assert (prtp && prtp->_rpPF.SameLevel(prpPF) && cchLeft > 0);
  2409. LONG cRuns = 0;
  2410. LONG cchRun, cchText = cchLeft;
  2411. ICharFormatCache* pf = GetCharFormatCache();
  2412. // check if the first CF run is PF bound
  2413. //
  2414. prtp->_rpPF.AdjustBackward();
  2415. if (prtp->GetIchRunCF() > 0 && !prtp->_rpPF.SameLevel(prpPF))
  2416. prtp->_rpCF.SplitFormat(pf); // PF breaks inside a CF run, split the run
  2417. prtp->_rpPF.AdjustForward(); // make sure we are all forward.
  2418. prtp->_rpCF.AdjustForward();
  2419. while (cchText > 0)
  2420. {
  2421. cchRun = min(prtp->GetCchLeftRunPF(), prtp->GetCchLeftRunCF());
  2422. cchRun = min(cchText, cchRun); // find out the nearest hop
  2423. cchText -= cchRun;
  2424. prtp->Advance(cchRun); // to the next hop
  2425. if (!prtp->_rpPF.SameLevel(prpPF))
  2426. { // this is a para with different level
  2427. prtp->_rpCF.SplitFormat(pf); // split that CF run
  2428. cRuns++; // count the splitted
  2429. break; // and we're done
  2430. }
  2431. if (!cchText || // this is the last hop -or-
  2432. !prtp->GetIchRunCF() || // we're at the start or the end of a CF run
  2433. !prtp->GetCchLeftRunCF())
  2434. {
  2435. cRuns++; // count this hop
  2436. }
  2437. }
  2438. prtp->Advance(cchText - cchLeft); // resume position
  2439. cchLeft = cchText; // update number of char left
  2440. return cRuns;
  2441. }
  2442. /*
  2443. * CTxtRange::SpanSubstringDir (pusp, prtp, cchString, puInitLevel, pfNumericFound)
  2444. *
  2445. * @mfunc
  2446. * Span the run of text bound by or contains only block separators
  2447. * and share the same charset directionality.
  2448. *
  2449. * @rdesc
  2450. * number of span'ed text characters
  2451. *
  2452. */
  2453. LONG CTxtRange::SpanSubstring(
  2454. CUniscribe* pusp, // in: Uniscribe interface
  2455. CFormatRunPtr* prp, // in: Format run pointer
  2456. WCHAR* pwchString, // in: Input string
  2457. LONG cchString, // in: String character count
  2458. WORD& uSubStrLevel, // in/out: BiDi substring initial level
  2459. DWORD dwInFlags, // in: Input flags
  2460. CCharFlags* pCharflags, // out:Output charflags
  2461. WORD& wBiDiLangId) // out:The primary language of a BiDi run
  2462. {
  2463. Assert (pusp && cchString > 0 && prp && prp->IsValid());
  2464. LONG cch, cchLeft;
  2465. cch = cchLeft = cchString;
  2466. wBiDiLangId = LANG_NEUTRAL; // assume unknown
  2467. if (dwInFlags & SUBSTR_INSPANCHARSET)
  2468. {
  2469. // span runs with same charset's direction
  2470. CTxtEdit* ped = GetPed();
  2471. CFormatRunPtr rp(*prp);
  2472. const CCharFormat* pCF;
  2473. BOOL fNext;
  2474. BYTE bCharSet1, bCharSet2;
  2475. rp.AdjustForward();
  2476. pCF = ped->GetCharFormat(rp.GetFormat());
  2477. bCharSet1 = bCharSet2 = pCF->_bCharSet;
  2478. while (!(bCharSet1 ^ bCharSet2))
  2479. {
  2480. cch = min(rp.GetCchLeft(), cchLeft);
  2481. cchLeft -= cch;
  2482. if (!(fNext = rp.NextRun()) || !cchLeft)
  2483. break;
  2484. bCharSet1 = bCharSet2;
  2485. pCF = ped->GetCharFormat(rp.GetFormat());
  2486. bCharSet2 = pCF->_bCharSet;
  2487. }
  2488. uSubStrLevel = IsBiDiCharSet(bCharSet1) ? 1 : 0;
  2489. if (uSubStrLevel & 1)
  2490. wBiDiLangId = bCharSet1 == ARABIC_CHARSET ? LANG_ARABIC : LANG_HEBREW;
  2491. cchString -= cchLeft;
  2492. cch = cchString;
  2493. dwInFlags |= SUBSTR_INSPANBLOCK;
  2494. }
  2495. if (dwInFlags & SUBSTR_INSPANBLOCK)
  2496. {
  2497. // scan the whole substring to collect information about it
  2498. DWORD dwBS = IsEOP(*pwchString) ? 1 : 0;
  2499. BYTE bCharMask;
  2500. cch = 0;
  2501. if (pCharflags)
  2502. pCharflags->_bFirstStrong = pCharflags->_bContaining = 0;
  2503. while (cch < cchString && !((IsEOP(pwchString[cch]) ? 1 : 0) ^ dwBS))
  2504. {
  2505. if (!dwBS && pCharflags)
  2506. {
  2507. bCharMask = 0;
  2508. switch (MECharClass(pwchString[cch]))
  2509. {
  2510. case CC_ARABIC:
  2511. case CC_HEBREW:
  2512. case CC_RTL:
  2513. bCharMask = SUBSTR_OUTCCRTL;
  2514. break;
  2515. case CC_LTR:
  2516. bCharMask = SUBSTR_OUTCCLTR;
  2517. default:
  2518. break;
  2519. }
  2520. if (bCharMask)
  2521. {
  2522. if (!pCharflags->_bFirstStrong)
  2523. pCharflags->_bFirstStrong |= bCharMask;
  2524. pCharflags->_bContaining |= bCharMask;
  2525. }
  2526. }
  2527. cch++;
  2528. }
  2529. }
  2530. return cch;
  2531. }
  2532. /*
  2533. * CTxtRange::ItemizeRuns(publdr, fUnicodeBidi, iCtxBaseLevel)
  2534. *
  2535. * @mfunc
  2536. * Break text range into smaller run(s) containing
  2537. *
  2538. * 1. Script ID for complex script shaping
  2539. * 2. Charset for run internal direction
  2540. * 3. BiDi embedding level
  2541. *
  2542. * @rdesc
  2543. * TRUE iff one or more items found.
  2544. * The range's active end will be at cpMost upon return.
  2545. *
  2546. * @devnote
  2547. * This routine could handle mixed paragraph runs
  2548. */
  2549. BOOL CTxtRange::ItemizeRuns(
  2550. IUndoBuilder *publdr, //@parm Undo context for this operation
  2551. BOOL fUnicodeBiDi, //@parm TRUE: Caller needs Bidi algorithm
  2552. BOOL fUseCtxLevel) //@parm Itemize using context based level (only valid if fUnicodeBiDi is true)
  2553. {
  2554. LONG cchString;
  2555. int cItems = 0;
  2556. LONG cpMin, cpMost;
  2557. CTxtEdit* ped = GetPed();
  2558. CFreezeDisplay fd(ped->_pdp); // Freeze display
  2559. int i;
  2560. BYTE pbBufIn[MAX_CLIENT_BUF];
  2561. PUSP_CLIENT pc = NULL;
  2562. const CBiDiFSM* pFSM;
  2563. SCRIPT_ITEM* psi;
  2564. CUniscribe* pusp;
  2565. CTxtPtr tp(_rpTX);
  2566. LONG cp, cch, cchSave;
  2567. HRESULT hr = E_FAIL;
  2568. CCharFormat CF;
  2569. BOOL fWhiteChunk; // Chunk contains only whitespaces
  2570. WCHAR* pwchString;
  2571. WORD uSubStrLevel; // Substring initial level
  2572. WORD uParaLevel; // Paragraph initial level
  2573. CNotifyMgr* pnm = NULL; // Notification manager
  2574. BOOL fRunUnicodeBiDi;
  2575. BOOL fStreaming = ped->IsStreaming();
  2576. BOOL fChangeCharSet = FALSE;
  2577. CCharFlags charflags = {0};
  2578. WORD wBiDiLangId;
  2579. #ifdef DEBUG
  2580. LONG cchText = tp.GetTextLength();
  2581. #endif
  2582. // Get range and setup text ptr to the start
  2583. //
  2584. cch = cchString = GetRange(cpMin, cpMost);
  2585. if (!cch)
  2586. return FALSE;
  2587. tp.SetCp(cpMin);
  2588. // prepare Uniscribe
  2589. pusp = ped->Getusp();
  2590. if (!pusp)
  2591. goto Exit;
  2592. // allocate temp buffer for itemization
  2593. pusp->CreateClientStruc(pbBufIn, MAX_CLIENT_BUF, &pc, cchString, cli_Itemize);
  2594. if (!pc)
  2595. goto Exit;
  2596. Assert (tp.GetCp() == cpMin);
  2597. if(pnm = ped->GetNotifyMgr())
  2598. pnm->NotifyPreReplaceRange(this, CP_INFINITE, 0, 0, cpMin, cpMost);
  2599. cp = cpMin; // Set cp starting point at cpMin
  2600. Set(cp, 0); // equals to Collapser(tomStart)
  2601. SetExtend(TRUE);
  2602. Check_rpCF(); // Make sure _rpCF is valid
  2603. Check_rpPF(); // _rpPF too
  2604. // Always run UnicodeBidi for plain text control
  2605. // (2.1 backward compatible)
  2606. //
  2607. if (!ped->IsRich())
  2608. {
  2609. fUnicodeBiDi = TRUE;
  2610. fUseCtxLevel = FALSE;
  2611. }
  2612. uSubStrLevel = uParaLevel = IsParaRTL() ? 1 : 0; // initialize substring level
  2613. pwchString = pc->si->pwchString;
  2614. tp.GetTextForUsp(cchString, pwchString, ped->_fNeutralOverride);
  2615. while ( cchString > 0 &&
  2616. ((cch = SpanSubstring(pusp, &_rpCF, pwchString, cchString, uSubStrLevel,
  2617. fUnicodeBiDi ? SUBSTR_INSPANBLOCK : SUBSTR_INSPANCHARSET,
  2618. (fStreaming || fUseCtxLevel) ? &charflags : NULL,
  2619. wBiDiLangId)) > 0) )
  2620. {
  2621. cchSave = cch;
  2622. fWhiteChunk = FALSE;
  2623. if (uSubStrLevel ^ uParaLevel)
  2624. {
  2625. // Handle Bidi spaces when substring level counters paragraph base direction.
  2626. // Spanning leading spaces
  2627. cch = 0;
  2628. while (cch < cchSave && pwchString[cch] == 0x20)
  2629. cch++;
  2630. if (cch)
  2631. fWhiteChunk = TRUE;
  2632. else
  2633. {
  2634. // Trimming out trailing whitespaces (including CR)
  2635. cch = cchSave;
  2636. while (cch > 0 && IsWhiteSpace(pwchString[cch-1]))
  2637. cch--;
  2638. if (!cch)
  2639. cch = cchSave;
  2640. }
  2641. Assert(cch > 0);
  2642. }
  2643. // Itemize with Unicode Bidi algorithm when
  2644. // a. Plain text mode
  2645. // b. Caller wants (fUnicodeBidi != 0)
  2646. // c. Substring is RTL.
  2647. //
  2648. fRunUnicodeBiDi = fUnicodeBiDi || uSubStrLevel;
  2649. fChangeCharSet = fUnicodeBiDi;
  2650. if (!fUnicodeBiDi && uSubStrLevel == 1 && fStreaming)
  2651. {
  2652. // During RTF streaming if the RTL run contains strong LTR,
  2653. // we resolve them using the paragraph base level
  2654. if (charflags._bContaining & SUBSTR_OUTCCLTR)
  2655. uSubStrLevel = uParaLevel;
  2656. fChangeCharSet = TRUE;
  2657. }
  2658. // Caller wants context based level.
  2659. // We want to itemize incoming plain text (into richtext doc) with the base level
  2660. // of the first strong character found in each substrings (wchao - 7/15/99)
  2661. //
  2662. if (fUnicodeBiDi && fUseCtxLevel && charflags._bFirstStrong)
  2663. uSubStrLevel = (WORD)(charflags._bFirstStrong & SUBSTR_OUTCCRTL ? 1 : 0);
  2664. if (fWhiteChunk || pusp->ItemizeString (pc, uSubStrLevel, &cItems, pwchString, cch,
  2665. fRunUnicodeBiDi, wBiDiLangId) > 0)
  2666. {
  2667. const SCRIPT_PROPERTIES* psp;
  2668. DWORD dwMask1;
  2669. BYTE bCharSetDefault = ped->GetCharFormat(-1)->_bCharSet;
  2670. psi = pc->si->psi;
  2671. if (fWhiteChunk)
  2672. {
  2673. cItems = 1;
  2674. psi[0].a.eScript = SCRIPT_WHITE;
  2675. psi[0].iCharPos = 0;
  2676. psi[1].iCharPos = cch;
  2677. }
  2678. Assert(cItems > 0);
  2679. // process items
  2680. //
  2681. for (i=0; i < cItems; i++)
  2682. {
  2683. cp += psi[i+1].iCharPos - psi[i].iCharPos;
  2684. AssertNr (cp <= cchText);
  2685. SetCp(min(cp, cpMost));
  2686. dwMask1 = 0;
  2687. // Associate the script properties
  2688. psp = pusp->GeteProp(psi[i].a.eScript);
  2689. Assert (psp);
  2690. if (!psp->fComplex && !psp->fNumeric &&
  2691. !psi[i].a.fRTL && psi[i].a.eScript < SCRIPT_MAX_COUNT)
  2692. {
  2693. // Note: Value 0 here is a valid script ID (SCRIPT_UNDEFINED),
  2694. // guaranteed by Uniscribe to be available all the time
  2695. // so we're safe using it as our simplified script ID.
  2696. //
  2697. psi[i].a.eScript = 0;
  2698. psp = pusp->GeteProp(0);
  2699. }
  2700. CF._wScript = psi[i].a.eScript;
  2701. // Stamp appropriate charset
  2702. //
  2703. if (pusp->GetComplexCharSet(psp, bCharSetDefault, CF._bCharSet))
  2704. {
  2705. // Complex script that has distinctive charset
  2706. dwMask1 |= CFM_CHARSET;
  2707. }
  2708. else if (fChangeCharSet)
  2709. {
  2710. // We run UnicodeBidi to analyse the whole thing so
  2711. // we need to figure out the proper charset to use as well.
  2712. //
  2713. // Note that we dont want to apply charset in general, say things
  2714. // like FarEast or GREEK_CHARSET should remain unchanged by
  2715. // this effect. But doing charset check is tough since we deal
  2716. // with text in range basis, so we simply call to update it here
  2717. // and let CCharFormat::Apply do the charset test in down level.
  2718. CF._bCharSet = psp->bCharSet; // assume what Uniscribe has given us
  2719. if (psi[i].a.fRTL || psi[i].a.fLayoutRTL)
  2720. {
  2721. // those of strong RTL and RTL digits need RTL charset
  2722. CF._bCharSet = pusp->GetRtlCharSet(ped);
  2723. }
  2724. Assert(CF._bCharSet != DEFAULT_CHARSET);
  2725. dwMask1 |= CFM_CHARSET;
  2726. }
  2727. // no publdr for this call so no antievent for itemized CF
  2728. SetCharFormat(&CF, SCF_IGNORENOTIFY, NULL, dwMask1, CFM2_SCRIPT);
  2729. Set(cp, 0);
  2730. }
  2731. }
  2732. else
  2733. {
  2734. // Itemization fails.
  2735. cp += cch;
  2736. SetCp(min(cp, cpMost));
  2737. // reset script id to 0
  2738. CF._wScript = 0;
  2739. SetCharFormat(&CF, SCF_IGNORENOTIFY, NULL, 0, CFM2_SCRIPT);
  2740. Set(cp, 0);
  2741. }
  2742. pwchString = &pc->si->pwchString[cp - cpMin]; // point to next substring
  2743. cchString -= cch;
  2744. uParaLevel = IsParaRTL() ? 1 : 0; // paragraph level might have changed
  2745. }
  2746. Assert (cpMost == cp);
  2747. // restore original range
  2748. Set(cpMost, cpMost - cpMin);
  2749. // retrieve ptr to Bidi FSM
  2750. pFSM = pusp->GetFSM();
  2751. if (pFSM)
  2752. hr = BiDiLevelFromFSM (pFSM);
  2753. AssertSz(SUCCEEDED(hr), "Unable to run or running BiDi FSM fails! We are in deep trouble,");
  2754. if (pc && pbBufIn != (BYTE*)pc)
  2755. FreePv(pc);
  2756. // update flags
  2757. ped->_fItemizePending = FALSE;
  2758. // Notify backing store change to all notification sinks
  2759. if(pnm)
  2760. pnm->NotifyPostReplaceRange(this, CP_INFINITE, 0, 0, cpMin, cpMost);
  2761. Exit:
  2762. return cItems > 0;
  2763. }
  2764. /*
  2765. * CTxtRange::IsProtected(iDirection)
  2766. *
  2767. * @mfunc
  2768. * Return TRUE if any part of this range is protected (HACK: or
  2769. * if any part of the range contains DBCS text stored in our Unicode
  2770. * backing store). If degenerate,
  2771. * use CCharFormat from run specified by iDirection, that is, use run
  2772. * valid up to, at, or starting at this GetCp() for iDirection less, =,
  2773. * or greater than 0, respectively.
  2774. *
  2775. * @rdesc
  2776. * TRUE iff any part of this range is protected (HACK: or if any part
  2777. * of the range contains DBCS text stored in our Unicode backing store
  2778. * For this to work correctly, GetCharFormat() needs to return dwMask2
  2779. * as well).
  2780. */
  2781. BOOL CTxtRange::IsProtected (
  2782. LONG iDirection) //@parm Controls which run to check if range is IP
  2783. {
  2784. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::IsProtected");
  2785. CCharFormat CF;
  2786. LONG iFormat = -1; // Default default CF
  2787. _TEST_INVARIANT_
  2788. if(_rpCF.IsValid()) // Active rich-text runs
  2789. {
  2790. if(_cch) // Range is nondegenerate
  2791. {
  2792. DWORD dwMask = GetCharFormat(&CF);
  2793. if(CF._dwEffects & CFE_RUNISDBCS)
  2794. return PROTECTED_YES;
  2795. if (!(dwMask & CFM_PROTECTED) ||
  2796. (CF._dwEffects & CFE_PROTECTED))
  2797. {
  2798. return PROTECTED_ASK;
  2799. }
  2800. return PROTECTED_NO;
  2801. }
  2802. iFormat = _iFormat; // Degenerate range: default
  2803. if(iDirection != 0) // this range's iFormat
  2804. { // Specific run direction
  2805. CFormatRunPtr rpCF(_rpCF);
  2806. if(iDirection < 0) // If at run ambiguous pos,
  2807. rpCF.AdjustBackward(); // use previous run
  2808. else
  2809. rpCF.AdjustForward();
  2810. iFormat = rpCF.GetFormat(); // Get run format
  2811. }
  2812. }
  2813. const CCharFormat *pCF = GetPed()->GetCharFormat(iFormat);
  2814. if(pCF->_dwEffects & CFE_RUNISDBCS)
  2815. return PROTECTED_YES;
  2816. if(pCF->_dwEffects & CFE_PROTECTED)
  2817. return PROTECTED_ASK;
  2818. return PROTECTED_NO;
  2819. }
  2820. /*
  2821. * CTxtRange::AdjustEndEOP (NewChars)
  2822. *
  2823. * @mfunc
  2824. * If this range is a selection and ends with an EOP and consists of
  2825. * more than just this EOP and fAdd is TRUE, or this EOP is the final
  2826. * EOP (at the story end), or this selection doesn't begin at the start
  2827. * of a paragraph, then move cpMost just before the end EOP. This
  2828. * function is used by UI methods that delete the selected text, such
  2829. * as PutChar(), Delete(), cut/paste, drag/drop.
  2830. *
  2831. * @rdesc
  2832. * TRUE iff range end has been adjusted
  2833. *
  2834. * @devnote
  2835. * This method leaves the active end at the selection cpMin. It is a
  2836. * CTxtRange method to handle the selection when it mascarades as a
  2837. * range for Cut/Paste.
  2838. */
  2839. BOOL CTxtRange::AdjustEndEOP (
  2840. EOPADJUST NewChars) //@parm NEWCHARS if chars will be added
  2841. {
  2842. TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtRange::AdjustEndEOP");
  2843. LONG cpMin, cpMost;
  2844. LONG cch = GetRange(cpMin, cpMost);
  2845. LONG cchSave = _cch;
  2846. BOOL fRet = FALSE;
  2847. if(cch && (cch < GetTextLength() || NewChars == NEWCHARS))
  2848. {
  2849. LONG cchEOP = GetPed()->fUseCRLF() ? 2 : 1;
  2850. CTxtPtr tp(_rpTX);
  2851. if(_cch > 0) // Ensure active end is cpMin
  2852. FlipRange(); // (ReplaceRange() needs to
  2853. else // do this anyhow)
  2854. tp.AdvanceCp(-_cch); // Ensure tp is at cpMost
  2855. if(tp.IsAfterEOP()) // Don't delete EOP at sel
  2856. { // end if EOP isn't end of
  2857. CPFRunPtr rp(*this); // table row and if there're
  2858. rp.AdvanceCp(-_cch); // chars to add, or cpMin
  2859. rp.AdjustBackward(); // isn't at BOP and more than
  2860. // EOP is selected
  2861. if(!rp.InTable() &&
  2862. (NewChars == NEWCHARS ||
  2863. cpMin && !_rpTX.IsAfterEOP() && cch > cchEOP))
  2864. {
  2865. _cch -= tp.BackupCpCRLF(); // Shorten range before EOP
  2866. // Note: the -= _adds_ to a
  2867. Update_iFormat(-1); // negative _cch to make
  2868. fRet = TRUE; // it less negative
  2869. }
  2870. }
  2871. if((_cch ^ cchSave) < 0 && _fSel) // Keep active end the same
  2872. FlipRange(); // for selection undo
  2873. }
  2874. return fRet;
  2875. }
  2876. /*
  2877. * CTxtRange::DeleteTerminatingEOP (publdr)
  2878. *
  2879. * @mfunc
  2880. * If this range is an insertion point that follows an EOP, select
  2881. * and delete that EOP
  2882. */
  2883. void CTxtRange::DeleteTerminatingEOP(
  2884. IUndoBuilder *publdr)
  2885. {
  2886. Assert(!_cch);
  2887. if(_rpTX.IsAfterEOP())
  2888. {
  2889. SetExtend(TRUE);
  2890. BackupCRLF();
  2891. ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE);
  2892. }
  2893. }
  2894. /*
  2895. * CTxtRange::CheckTextLength(cch)
  2896. *
  2897. * @mfunc
  2898. * Check to see if can add cch characters. If not, notify parent
  2899. *
  2900. * @rdesc
  2901. * TRUE if OK to add cch chars
  2902. */
  2903. BOOL CTxtRange::CheckTextLength (
  2904. LONG cch,
  2905. LONG *pcch)
  2906. {
  2907. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CheckTextLength");
  2908. _TEST_INVARIANT_
  2909. DWORD cchNew = (DWORD)(CalcTextLenNotInRange() + cch);
  2910. if(cchNew > GetPed()->TxGetMaxLength())
  2911. {
  2912. if (pcch)
  2913. *pcch = cchNew - GetPed()->TxGetMaxLength();
  2914. else
  2915. GetPed()->GetCallMgr()->SetMaxText();
  2916. return FALSE;
  2917. }
  2918. return TRUE;
  2919. }
  2920. /*
  2921. * CTxtRange::FindObject(pcpMin, pcpMost)
  2922. *
  2923. * @mfunc
  2924. * Set *pcpMin = closest embedded object cpMin <lt>= range cpMin
  2925. * Set *pcpMost = closest embedded object cpMost <gt>= range cpMost
  2926. *
  2927. * @rdesc
  2928. * TRUE iff object found
  2929. *
  2930. * @comm
  2931. * An embedded object cpMin points at the first character of an embedded
  2932. * object. For RichEdit, this is the WCH_EMBEDDING character. An
  2933. * embedded object cpMost follows the last character of an embedded
  2934. * object. For RichEdit, this immediately follows the WCH_EMBEDDING
  2935. * character.
  2936. */
  2937. BOOL CTxtRange::FindObject(
  2938. LONG *pcpMin, //@parm Out parm to receive object's cpMin; NULL OK
  2939. LONG *pcpMost) const//@parm Out parm to receive object's cpMost; NULL OK
  2940. {
  2941. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindObject");
  2942. if(!GetObjectCount()) // No objects: can't move, so
  2943. return FALSE; // return FALSE
  2944. BOOL bRet = FALSE; // Default no object
  2945. LONG cpMin, cpMost;
  2946. CTxtPtr tp(_rpTX);
  2947. GetRange(cpMin, cpMost);
  2948. if(pcpMin)
  2949. {
  2950. tp.SetCp(cpMin);
  2951. if(tp.GetChar() != WCH_EMBEDDING)
  2952. {
  2953. cpMin = tp.FindExact(tomBackward, szEmbedding);
  2954. if(cpMin >= 0)
  2955. {
  2956. bRet = TRUE;
  2957. *pcpMin = cpMin;
  2958. }
  2959. }
  2960. }
  2961. if(pcpMost)
  2962. {
  2963. tp.SetCp(cpMost);
  2964. if (tp.PrevChar() != WCH_EMBEDDING &&
  2965. tp.FindExact(tomForward, szEmbedding) >= 0)
  2966. {
  2967. bRet = TRUE;
  2968. *pcpMost = tp.GetCp();
  2969. }
  2970. }
  2971. return bRet;
  2972. }
  2973. /*
  2974. * CTxtRange::FindCell(pcpMin, pcpMost)
  2975. *
  2976. * @mfunc
  2977. * Set *pcpMin = closest cell cpMin <lt>= range cpMin (see comment)
  2978. * Set *pcpMost = closest cell cpMost <gt>= range cpMost
  2979. *
  2980. * @comment
  2981. * This function does nothing if the range isn't completely in a table.
  2982. */
  2983. void CTxtRange::FindCell (
  2984. LONG *pcpMin, //@parm Out parm for bounding-cell cpMin
  2985. LONG *pcpMost) const //@parm Out parm for bounding-cell cpMost
  2986. {
  2987. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindCell");
  2988. WCHAR ch;
  2989. LONG cpMin, cpMost;
  2990. CRchTxtPtr rtp(*this);
  2991. _TEST_INVARIANT_
  2992. GetRange(cpMin, cpMost);
  2993. if(pcpMin)
  2994. {
  2995. if(_cch > 0)
  2996. rtp.Advance(-_cch);
  2997. rtp._rpPF.AdjustBackward();
  2998. if(rtp.InTable())
  2999. {
  3000. while(rtp.GetCp())
  3001. {
  3002. rtp.BackupCRLF();
  3003. ch = rtp.GetChar();
  3004. if(IsASCIIEOP(ch) || ch == CELL)
  3005. {
  3006. rtp.AdvanceCRLF();
  3007. break;
  3008. }
  3009. Assert(rtp.InTable());
  3010. }
  3011. }
  3012. *pcpMin = rtp.GetCp();
  3013. }
  3014. if(pcpMost)
  3015. {
  3016. rtp.SetCp(cpMost);
  3017. if(rtp.InTable())
  3018. {
  3019. rtp.BackupCRLF();
  3020. do
  3021. {
  3022. ch = rtp.GetChar();
  3023. rtp.AdvanceCRLF();
  3024. Assert(rtp.InTable());
  3025. } while(ch && !IsASCIIEOP(ch) && ch != CELL);
  3026. }
  3027. *pcpMost = rtp.GetCp();
  3028. }
  3029. }
  3030. /*
  3031. * CTxtRange::FindParagraph(pcpMin, pcpMost)
  3032. *
  3033. * @mfunc
  3034. * Set *pcpMin = closest paragraph cpMin <lt>= range cpMin (see comment)
  3035. * Set *pcpMost = closest paragraph cpMost <gt>= range cpMost
  3036. *
  3037. * @devnote
  3038. * If this range's cpMost follows an EOP, use it for bounding-paragraph
  3039. * cpMost unless 1) the range is an insertion point, and 2) pcpMin and
  3040. * pcpMost are both nonzero, in which case use the next EOP. Both out
  3041. * parameters are nonzero if FindParagraph() is used to expand to full
  3042. * paragraphs (else StartOf or EndOf is all that's requested). This
  3043. * behavior is consistent with the selection/IP UI. Note that FindEOP
  3044. * treats the beginning/end of document (BOD/EOD) as a BOP/EOP,
  3045. * respectively, but IsAfterEOP() does not.
  3046. */
  3047. void CTxtRange::FindParagraph (
  3048. LONG *pcpMin, //@parm Out parm for bounding-paragraph cpMin
  3049. LONG *pcpMost) const //@parm Out parm for bounding-paragraph cpMost
  3050. {
  3051. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindParagraph");
  3052. LONG cpMin, cpMost;
  3053. CTxtPtr tp(_rpTX);
  3054. _TEST_INVARIANT_
  3055. GetRange(cpMin, cpMost);
  3056. if(pcpMin)
  3057. {
  3058. tp.SetCp(cpMin); // tp points at this range's cpMin
  3059. if(!tp.IsAfterEOP()) // Unless tp directly follows an
  3060. tp.FindEOP(tomBackward); // EOP, search backward for EOP
  3061. *pcpMin = cpMin = tp.GetCp();
  3062. }
  3063. if(pcpMost)
  3064. {
  3065. tp.SetCp(cpMost); // If range cpMost doesn't follow
  3066. if (!tp.IsAfterEOP() || // an EOP or else if expanding
  3067. (!cpMost || pcpMin) &&
  3068. cpMin == cpMost) // IP at paragraph beginning,
  3069. {
  3070. tp.FindEOP(tomForward); // search for next EOP
  3071. }
  3072. *pcpMost = tp.GetCp();
  3073. }
  3074. }
  3075. /*
  3076. * CTxtRange::FindSentence(pcpMin, pcpMost)
  3077. *
  3078. * @mfunc
  3079. * Set *pcpMin = closest sentence cpMin <lt>= range cpMin
  3080. * Set *pcpMost = closest sentence cpMost <gt>= range cpMost
  3081. *
  3082. * @devnote
  3083. * If this range's cpMost follows a sentence end, use it for bounding-
  3084. * sentence cpMost unless the range is an insertion point, in which case
  3085. * use the next sentence end. The routine takes care of aligning on
  3086. * sentence beginnings in the case of range ends that fall on whitespace
  3087. * in between sentences.
  3088. */
  3089. void CTxtRange::FindSentence (
  3090. LONG *pcpMin, //@parm Out parm for bounding-sentence cpMin
  3091. LONG *pcpMost) const //@parm Out parm for bounding-sentence cpMost
  3092. {
  3093. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindSentence");
  3094. LONG cpMin, cpMost;
  3095. CTxtPtr tp(_rpTX);
  3096. _TEST_INVARIANT_
  3097. GetRange(cpMin, cpMost);
  3098. if(pcpMin) // Find sentence beginning
  3099. {
  3100. tp.SetCp(cpMin); // tp points at this range's cpMin
  3101. if(!tp.IsAtBOSentence()) // If not at beginning of sentence
  3102. tp.FindBOSentence(tomBackward); // search backward for one
  3103. *pcpMin = cpMin = tp.GetCp();
  3104. }
  3105. if(pcpMost) // Find sentence end
  3106. { // Point tp at this range's cpLim
  3107. tp.SetCp(cpMost); // If cpMost isn't at sentence
  3108. if (!tp.IsAtBOSentence() || // beginning or if at story
  3109. (!cpMost || pcpMin) && // beginning or expanding
  3110. cpMin == cpMost) // IP at sentence beginning,
  3111. { // find next sentence beginning
  3112. if(!tp.FindBOSentence(tomForward))
  3113. tp.SetCp(GetTextLength()); // End of story counts as
  3114. } // sentence end too
  3115. *pcpMost = tp.GetCp();
  3116. }
  3117. }
  3118. /*
  3119. * CTxtRange::FindVisibleRange(pcpMin, pcpMost)
  3120. *
  3121. * @mfunc
  3122. * Set *pcpMin = _pdp->_cpFirstVisible
  3123. * Set *pcpMost = _pdp->_cpLastVisible
  3124. *
  3125. * @rdesc
  3126. * TRUE iff calculated cp's differ from this range's cp's
  3127. *
  3128. * @devnote
  3129. * CDisplay::GetFirstVisible() and GetCliVisible() return the first cp
  3130. * on the first visible line and the last cp on the last visible line.
  3131. * These won't be visible if they are scrolled off the screen.
  3132. * FUTURE: A more general algorithm would CpFromPoint (0,0) and
  3133. * (right, bottom).
  3134. */
  3135. BOOL CTxtRange::FindVisibleRange (
  3136. LONG *pcpMin, //@parm Out parm for cpFirstVisible
  3137. LONG *pcpMost) const //@parm Out parm for cpLastVisible
  3138. {
  3139. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindVisibleRange");
  3140. _TEST_INVARIANT_
  3141. CDisplay * pdp = GetPed()->_pdp;
  3142. if(!pdp)
  3143. return FALSE;
  3144. if(pcpMin)
  3145. *pcpMin = pdp->GetFirstVisibleCp();
  3146. pdp->GetCliVisible(pcpMost);
  3147. return TRUE;
  3148. }
  3149. /*
  3150. * CTxtRange::FindWord(pcpMin, pcpMost, type)
  3151. *
  3152. * @mfunc
  3153. * Set *pcpMin = closest word cpMin <lt>= range cpMin
  3154. * Set *pcpMost = closest word cpMost <gt>= range cpMost
  3155. *
  3156. * @comm
  3157. * There are two interesting cases for finding a word. The first,
  3158. * (FW_EXACT) finds the exact word, with no extraneous characters.
  3159. * This is useful for situations like applying formatting to a
  3160. * word. The second case, FW_INCLUDE_TRAILING_WHITESPACE does the
  3161. * obvious thing, namely includes the whitespace up to the next word.
  3162. * This is useful for the selection double-click semantics and TOM.
  3163. */
  3164. void CTxtRange::FindWord(
  3165. LONG *pcpMin, //@parm Out parm to receive word's cpMin; NULL OK
  3166. LONG *pcpMost, //@parm Out parm to receive word's cpMost; NULL OK
  3167. FINDWORD_TYPE type) const //@parm Type of word to find
  3168. {
  3169. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindWord");
  3170. LONG cch, cch1;
  3171. LONG cpMin, cpMost;
  3172. CTxtPtr tp(_rpTX);
  3173. _TEST_INVARIANT_
  3174. Assert(type == FW_EXACT || type == FW_INCLUDE_TRAILING_WHITESPACE );
  3175. GetRange(cpMin, cpMost);
  3176. if(pcpMin)
  3177. {
  3178. tp.SetCp(cpMin);
  3179. if(!tp.IsAtBOWord()) // cpMin not at BOW:
  3180. cpMin += tp.FindWordBreak(WB_MOVEWORDLEFT); // go there
  3181. *pcpMin = cpMin;
  3182. Assert(cpMin >= 0 && cpMin <= GetTextLength());
  3183. }
  3184. if(pcpMost)
  3185. {
  3186. tp.SetCp(cpMost);
  3187. if (!tp.IsAtBOWord() || // If not at word strt
  3188. (!cpMost || pcpMin) && cpMin == cpMost) // or there but need
  3189. { // to expand IP,
  3190. cch = tp.FindWordBreak(WB_MOVEWORDRIGHT); // move to next word
  3191. if(cch && type == FW_EXACT) // If moved and want
  3192. { // word proper, move
  3193. cch1 = tp.FindWordBreak(WB_LEFTBREAK); // back to end of
  3194. if(cch + cch1 > 0) // preceding word
  3195. cch += cch1; // Only do so if were
  3196. } // not already at end
  3197. cpMost += cch;
  3198. }
  3199. *pcpMost = cpMost;
  3200. Assert(cpMost >= 0 && cpMost <= GetTextLength());
  3201. Assert(cpMin <= cpMost);
  3202. }
  3203. }
  3204. /*
  3205. * CTxtRange::FindAttributes(pcpMin, pcpMost, dwMask)
  3206. *
  3207. * @mfunc
  3208. * Set *pcpMin = closest attribute-combo cpMin <lt>= range cpMin
  3209. * Set *pcpMost = closest attribute-combo cpMost <gt>= range cpMost
  3210. * The attribute combo is given by Unit and is any OR combination of
  3211. * TOM attributes, e.g., tomBold, tomItalic, or things like
  3212. * tomBold | tomItalic. The combo is found if any of the attributes
  3213. * is present.
  3214. *
  3215. * @devnote
  3216. * Plan to add other logical combinations: tomAND, tomExact
  3217. */
  3218. void CTxtRange::FindAttributes (
  3219. LONG *pcpMin, //@parm Out parm for bounding-sentence cpMin
  3220. LONG *pcpMost, //@parm Out parm for bounding-sentence cpMost
  3221. LONG Unit) const //@parm TOM attribute mask
  3222. {
  3223. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindAttributes");
  3224. LONG cch;
  3225. LONG cpMin, cpMost;
  3226. DWORD dwMask = Unit & ~0x80000000; // Kill sign bit
  3227. CCFRunPtr rp(*this);
  3228. Assert(Unit < 0);
  3229. GetRange(cpMin, cpMost);
  3230. if(!rp.IsValid()) // No CF runs instantiated
  3231. {
  3232. if(rp.IsMask(dwMask)) // Applies to default CF
  3233. {
  3234. if(pcpMin)
  3235. *pcpMin = 0;
  3236. if(pcpMost)
  3237. *pcpMost = GetTextLength();
  3238. }
  3239. return;
  3240. }
  3241. // Start at cpMin
  3242. if(_cch > 0)
  3243. rp.AdvanceCp(-_cch);
  3244. // Go backward until we don't match dwMask
  3245. if(pcpMin)
  3246. {
  3247. rp.AdjustBackward();
  3248. while(rp.IsMask(dwMask) && rp.GetIch())
  3249. {
  3250. cpMin -= rp.GetIch();
  3251. rp.AdvanceCp(-rp.GetIch());
  3252. rp.AdjustBackward();
  3253. }
  3254. *pcpMin = cpMin;
  3255. }
  3256. // Now go forward from cpMost until we don't match dwMask
  3257. if(pcpMost)
  3258. {
  3259. rp.AdvanceCp(cpMost - cpMin);
  3260. rp.AdjustForward(); // In case cpMin = cpMost
  3261. cch = rp.GetCchLeft();
  3262. while(rp.IsMask(dwMask) && cch)
  3263. {
  3264. cpMost += cch;
  3265. rp.AdvanceCp(cch);
  3266. cch = rp.GetCchLeft();
  3267. }
  3268. *pcpMost = cpMost;
  3269. }
  3270. }
  3271. /*
  3272. * CTxtRange::CalcTextLenNotInRange()
  3273. *
  3274. * @mfunc
  3275. * Helper function that calculates the total length of text
  3276. * excluding the current range.
  3277. *
  3278. * @comm
  3279. * Used for limit testing. The problem being solved is that
  3280. * the range can contain the final EOP which is not included
  3281. * in the adjusted text length.
  3282. */
  3283. LONG CTxtRange::CalcTextLenNotInRange()
  3284. {
  3285. LONG cchAdjLen = GetPed()->GetAdjustedTextLength();
  3286. LONG cchLen = cchAdjLen - abs(_cch);
  3287. LONG cpMost = GetCpMost();
  3288. if (cpMost > cchAdjLen)
  3289. {
  3290. // Selection extends beyond adjusted length. Put amount back in the
  3291. // selection as it has become too small by the difference.
  3292. cchLen += cpMost - cchAdjLen;
  3293. }
  3294. return cchLen;
  3295. }
  3296. ////////////////////////// Outline Support //////////////////////////////////
  3297. /*
  3298. * CTxtRange::Promote(lparam, publdr)
  3299. *
  3300. * @mfunc
  3301. * Promote selected text according to:
  3302. *
  3303. * LOWORD(lparam) == 0 ==> promote to body-text
  3304. * LOWORD(lparam) != 0 ==> promote/demote current selection by
  3305. * LOWORD(lparam) levels
  3306. * @rdesc
  3307. * TRUE iff promotion occurred
  3308. *
  3309. * @devnote
  3310. * Changes this range
  3311. */
  3312. HRESULT CTxtRange::Promote (
  3313. LPARAM lparam, //@parm 0 to body, < 0 demote, > 0 promote
  3314. IUndoBuilder *publdr) //@parm undo builder to receive antievents
  3315. {
  3316. TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtRange::Promote");
  3317. if(abs(lparam) >= NHSTYLES)
  3318. return E_INVALIDARG;
  3319. if(publdr)
  3320. publdr->StopGroupTyping();
  3321. if(_cch > 0) // Point at cpMin
  3322. FlipRange();
  3323. LONG cchText = GetTextLength();
  3324. LONG cpEnd = GetCpMost();
  3325. LONG cpMin, cpMost;
  3326. BOOL fHeading = TRUE; // Default heading in range
  3327. HRESULT hr;
  3328. LONG Level;
  3329. LONG nHeading = NHSTYLES; // Setup to find any heading
  3330. CParaFormat PF;
  3331. const CParaFormat *pPF;
  3332. CPFRunPtr rp(*this);
  3333. LONG cch = rp.FindHeading(abs(_cch), nHeading);
  3334. WORD wEffects;
  3335. if(!lparam) // Demote to subtext
  3336. {
  3337. if(cch) // Already in subtext so don't
  3338. return S_FALSE; // need to demote
  3339. CTxtPtr tp(_rpTX);
  3340. if(!tp.IsAfterEOP())
  3341. cch = tp.FindEOP(tomBackward);
  3342. nHeading = 1;
  3343. if(tp.GetCp()) // Get previous level and convert
  3344. { // to heading to set up
  3345. rp.AdvanceCp(cch); // following Level code
  3346. rp.AdjustBackward();
  3347. nHeading = rp.GetOutlineLevel()/2 + 1;
  3348. }
  3349. }
  3350. else if(cch == tomBackward) // No heading in range
  3351. { // Set up to promote to
  3352. nHeading = rp.GetOutlineLevel()/2 // heading
  3353. + (lparam > 0 ? 2 : 1);
  3354. fHeading = FALSE; // Signal no heading in range
  3355. }
  3356. else if(cch) // Range starts in subtext
  3357. {
  3358. SetExtend(TRUE);
  3359. Advance(cch); // Bypass initial nonheading
  3360. }
  3361. Level = 2*(nHeading - 1); // Heading level
  3362. PF._bOutlineLevel = (BYTE)(Level | 1); // Corresponding subtext level
  3363. if (!Level && lparam > 0 || // Can't promote Heading 1
  3364. nHeading == NHSTYLES && lparam < 0) // or demote Heading 9
  3365. {
  3366. return S_FALSE;
  3367. }
  3368. do
  3369. {
  3370. _cch = 0;
  3371. Level -= long(2*lparam); // Promote Level
  3372. pPF = GetPF();
  3373. wEffects = pPF->_wEffects;
  3374. if(pPF->_bOutlineLevel & 1) // Handle contiguous text in
  3375. { // one fell swoop
  3376. cch = fHeading ? _rpPF.GetCchLeft() : cpEnd - GetCp();
  3377. if(cch > 0)
  3378. {
  3379. SetExtend(TRUE);
  3380. Advance(cch);
  3381. }
  3382. }
  3383. Expander(tomParagraph, TRUE, NULL, &cpMin, &cpMost);
  3384. if((unsigned)Level < 2*NHSTYLES)
  3385. { // Promoted Level is valid
  3386. DWORD dwMask = PFM_OUTLINELEVEL;// Default setting subtext level
  3387. if(!(Level & 1) && lparam) // Promoting or demoting heading
  3388. { // Preserve collapse status
  3389. PF._wEffects = Level ? wEffects : 0; // H1 is aways expanded
  3390. PF._sStyle = (SHORT)(-Level/2 + STYLE_HEADING_1);
  3391. PF._bOutlineLevel = (BYTE)(Level | 1);// Set up subtext
  3392. dwMask = PFM_STYLE + PFM_COLLAPSED;
  3393. }
  3394. else if(!lparam) // Changing heading to subtext
  3395. { // or uncollapsing subtext
  3396. PF._wEffects = 0; // Turn off collapsed
  3397. PF._sStyle = STYLE_NORMAL;
  3398. dwMask = PFM_STYLE + PFM_OUTLINELEVEL + PFM_COLLAPSED;
  3399. }
  3400. hr = SetParaStyle(&PF, publdr, dwMask);
  3401. if(hr != NOERROR)
  3402. return hr;
  3403. }
  3404. if(GetCp() >= cchText) // Have handled last PF run
  3405. break;
  3406. Assert(_cch > 0); // Para/run should be selected
  3407. pPF = GetPF(); // Points at next para
  3408. Level = pPF->_bOutlineLevel;
  3409. } // Iterate until past range &
  3410. while((Level & 1) || fHeading && // any subtext that follows
  3411. (GetCp() < cpEnd || pPF->_wEffects & PFE_COLLAPSED));
  3412. return NOERROR;
  3413. }
  3414. /*
  3415. * CTxtRange::ExpandOutline(Level, fWholeDocument)
  3416. *
  3417. * @mfunc
  3418. * Expand outline according to Level and fWholeDocument. Wraps
  3419. * OutlineExpander() helper function and updates selection/view
  3420. *
  3421. * @rdesc
  3422. * NOERROR if success
  3423. */
  3424. HRESULT CTxtRange::ExpandOutline(
  3425. LONG Level, //@parm If < 0, collapse; else expand, etc.
  3426. BOOL fWholeDocument) //@parm If TRUE, whole document
  3427. {
  3428. if (!IsInOutlineView())
  3429. return NOERROR;
  3430. HRESULT hres = OutlineExpander(Level, fWholeDocument);
  3431. if(hres != NOERROR)
  3432. return hres;
  3433. GetPed()->TxNotify(EN_PARAGRAPHEXPANDED, NULL);
  3434. return GetPed()->UpdateOutline();
  3435. }
  3436. /*
  3437. * CTxtRange::OutlineExpander(Level, fWholeDocument)
  3438. *
  3439. * @mfunc
  3440. * Expand/collapse outline for this range according to Level
  3441. * and fWholeDocument. If fWholeDocument is TRUE, then
  3442. * 1 <= Level <= NHSTYLES collapses all headings with numbers
  3443. * greater than Level and collapses all nonheadings. Level = -1
  3444. * expands all.
  3445. *
  3446. * fWholeDocument = FALSE expands/collapses (Level > 0 or < 0)
  3447. * paragraphs depending on whether an EOP and heading are included
  3448. * in the range. If Level = 0, toggle heading's collapsed status.
  3449. *
  3450. * @rdesc
  3451. * (change made) ? NOERROR : S_FALSE
  3452. */
  3453. HRESULT CTxtRange::OutlineExpander(
  3454. LONG Level, //@parm If < 0, collapse; else expand, etc.
  3455. BOOL fWholeDocument) //@parm If TRUE, whole document
  3456. {
  3457. CParaFormat PF;
  3458. if(fWholeDocument) // Apply to whole document
  3459. {
  3460. if (IN_RANGE(1, Level, NHSTYLES) || // Collapse to heading
  3461. Level == -1) // -1 means all
  3462. {
  3463. Set(0, tomBackward); // Select whole document
  3464. PF._sStyle = (SHORT)(STYLE_COMMAND + (BYTE)Level);
  3465. SetParaFormat(&PF, NULL, PFM_STYLE);// No undo
  3466. return NOERROR;
  3467. }
  3468. return S_FALSE; // Nothing happened (illegal
  3469. } // arg)
  3470. // Expand/Collapse for Level positive/negative, respectively
  3471. LONG cpMin, cpMost; // Get range cp's
  3472. LONG cchMax = GetRange(cpMin, cpMost);
  3473. if(_cch > 0) // Ensure cpMin is active
  3474. FlipRange(); // for upcoming rp and tp
  3475. LONG nHeading = NHSTYLES; // Setup to find any heading
  3476. LONG nHeading1;
  3477. CTxtEdit *ped = GetPed();
  3478. CPFRunPtr rp(*this);
  3479. LONG cch = rp.FindHeading(cchMax, nHeading);
  3480. if(cch == tomBackward) // No heading found within range
  3481. return S_FALSE; // Do nothing
  3482. Assert(cch <= cchMax && (Level || !cch)); // cch is count up to heading
  3483. CTxtPtr tp(_rpTX);
  3484. cpMin += cch; // Bypass any nonheading text
  3485. tp.AdvanceCp(cch); // at start of range
  3486. // If toggle collapse or if range contains an EOP,
  3487. // collapse/expand all subordinates
  3488. cch = tp.FindEOP(tomForward); // Find next para
  3489. if(!cch)
  3490. return NOERROR;
  3491. if(!Level || cch < -_cch) // Level = 0 or EOP in range
  3492. {
  3493. if(!Level) // Toggle collapse status
  3494. {
  3495. LONG cchLeft = rp.GetCchLeft();
  3496. if (cch < cchLeft || !rp.NextRun() ||
  3497. nHeading == STYLE_HEADING_1 - rp.GetStyle() + 1)
  3498. {
  3499. return NOERROR; // Next para has same heading
  3500. }
  3501. Assert(cch == cchLeft);
  3502. Level = rp.IsCollapsed();
  3503. rp.AdvanceCp(-cchLeft);
  3504. }
  3505. PF._wEffects = Level > 0 ? 0 : PFE_COLLAPSED;
  3506. while(cpMin < cpMost)
  3507. { // We're at a heading
  3508. tp.SetCp(cpMin);
  3509. cch = tp.FindEOP(-_cch);
  3510. cpMin += cch; // Bypass it
  3511. if(!rp.AdvanceCp(cch)) // Point at next para
  3512. break; // No more, we're done
  3513. nHeading1 = nHeading; // Setup to find heading <= nHeading
  3514. cch = rp.FindHeading(tomForward, nHeading1);
  3515. if(cch == tomBackward) // No more higher headings
  3516. cch = GetTextLength() - cpMin; // Format to end of text
  3517. Set(cpMin, -cch); // Collapse/expand up to here
  3518. SetParaFormat(&PF, NULL, PFM_COLLAPSED);
  3519. cpMin += cch; // Move past formatted area
  3520. nHeading = nHeading1; // Update nHeading to possibly
  3521. } // lower heading #
  3522. return NOERROR;
  3523. }
  3524. // Range contains no EOP: expand/collapse deepest level.
  3525. // If collapsing, collapse all nonheading text too. Expand
  3526. // nonheading text only if all subordinate levels are expanded.
  3527. BOOL fCollapsed;
  3528. LONG nHeadStart, nHeadDeepNC, nHeadDeep;
  3529. LONG nNonHead = -1; // No nonHeading found yet
  3530. const CParaFormat *pPF;
  3531. cpMin = tp.GetCp(); // Point at start of
  3532. cpMost = cpMin; // next para
  3533. pPF = ped->GetParaFormat(_rpPF.GetFormat());
  3534. nHeading = pPF->_bOutlineLevel;
  3535. Assert(!(nHeading & 1) && // Must start with a heading
  3536. !(pPF->_wEffects & PFE_COLLAPSED)); // that isn't collapsed
  3537. nHeadStart = nHeading/2 + 1; // Convert outline level to
  3538. nHeadDeep = nHeadDeepNC = nHeadStart; // heading number
  3539. while(cch) // Determine deepest heading
  3540. { // and deepest collapsed
  3541. rp.AdvanceCp(cch); // heading
  3542. pPF = ped->GetParaFormat(rp.GetFormat());
  3543. fCollapsed = pPF->_wEffects & PFE_COLLAPSED;
  3544. nHeading = pPF->_bOutlineLevel;
  3545. if(nHeading & 1) // Text found
  3546. { // Set nNonHead > 0 if
  3547. nNonHead = fCollapsed; // collapsed; else 0
  3548. cch = rp.GetCchLeft(); // Zip to end of contiguous
  3549. tp.AdvanceCp(cch); // text paras
  3550. }
  3551. else // It's a heading
  3552. {
  3553. nHeading = nHeading/2 + 1; // Convert to heading number
  3554. if(nHeading <= nHeadStart) // If same or shallower as
  3555. break; // start heading we're done
  3556. // Update deepest and deepest nonCollapsed heading #'s
  3557. nHeadDeep = max(nHeadDeep, nHeading);
  3558. if(!fCollapsed)
  3559. nHeadDeepNC = max(nHeadDeepNC, nHeading);
  3560. cch = tp.FindEOP(tomForward); // Go to next paragraph
  3561. }
  3562. cpMost = tp.GetCp(); // Include up to it
  3563. }
  3564. PF._sStyle = (SHORT)(STYLE_COMMAND + nHeadDeepNC);
  3565. if(Level > 0) // Expand
  3566. {
  3567. if(nHeadDeepNC < nHeadDeep) // At least one collapsed
  3568. PF._sStyle++; // heading: expand shallowest
  3569. else // All heads expanded: do others
  3570. PF._sStyle = (unsigned short) (STYLE_COMMAND + 0xFF);
  3571. } // In any case, expand nonheading
  3572. else if(nNonHead) // Collapse. If text collapsed
  3573. { // or missing, do headings
  3574. if(nHeadDeepNC == nHeadStart)
  3575. return S_FALSE; // Everything already collapsed
  3576. PF._sStyle--; // Collapse to next shallower
  3577. } // heading
  3578. Set(cpMin, cpMin - cpMost); // Select range to change
  3579. SetParaFormat(&PF, NULL, PFM_STYLE); // No undo
  3580. return NOERROR;
  3581. }
  3582. /*
  3583. * CTxtRange::CheckOutlineLevel(publdr)
  3584. *
  3585. * @mfunc
  3586. * If the paragraph style at this range isn't a heading, make
  3587. * sure its outline level is compatible with the preceeding one
  3588. */
  3589. void CTxtRange::CheckOutlineLevel(
  3590. IUndoBuilder *publdr) //@parm Undo context for this operation
  3591. {
  3592. LONG LevelBackward, LevelForward;
  3593. CPFRunPtr rp(*this);
  3594. Assert(!_cch);
  3595. rp.AdjustBackward();
  3596. LevelBackward = rp.GetOutlineLevel() | 1; // Nonheading level corresponding
  3597. // to previous PF run
  3598. rp.AdjustForward();
  3599. LevelForward = rp.GetOutlineLevel();
  3600. if (!(LevelForward & 1) || // Any heading can follow
  3601. LevelForward == LevelBackward) // any style. Also if
  3602. { // forward level is correct,
  3603. return; // return
  3604. }
  3605. LONG cch; // One or more nonheadings
  3606. LONG lHeading = NHSTYLES; // with incorrect outline
  3607. CParaFormat PF; // levels follow
  3608. PF._bOutlineLevel = (BYTE)LevelBackward; // level
  3609. cch = rp.FindHeading(tomForward, lHeading); // Find next heading
  3610. if(cch == tomBackward)
  3611. cch = tomForward;
  3612. Set(GetCp(), -cch); // Select all nonheading text
  3613. SetParaFormat(&PF, publdr, PFM_OUTLINELEVEL);// Change its outline level
  3614. Set(GetCp(), 0); // Restore range to IP
  3615. }
  3616. #if defined(DEBUG)
  3617. /*
  3618. * CTxtRange::::DebugFont (void)
  3619. *
  3620. * @mfunc
  3621. * Dump out the character and Font info for current selection.
  3622. */
  3623. void CTxtRange::DebugFont (void)
  3624. {
  3625. LONG ch;
  3626. LONG cpMin, cpMost;
  3627. LONG cch = GetRange(cpMin, cpMost);
  3628. LONG i;
  3629. char szTempBuf[64];
  3630. CTxtEdit *ped = GetPed();
  3631. const WCHAR *wszFontname;
  3632. const CCharFormat *CF; // Temporary CF
  3633. const WCHAR *GetFontName(LONG iFont);
  3634. char szTempPath[MAX_PATH] = "\0";
  3635. DWORD cchLength;
  3636. HANDLE hfileDump;
  3637. DWORD cbWritten;
  3638. SideAssert(cchLength = GetTempPathA(MAX_PATH, szTempPath));
  3639. // append trailing backslash if neccessary
  3640. if(szTempPath[cchLength - 1] != '\\')
  3641. {
  3642. szTempPath[cchLength] = '\\';
  3643. szTempPath[cchLength + 1] = 0;
  3644. }
  3645. strcat(szTempPath, "DumpFontInfo.txt");
  3646. SideAssert(hfileDump = CreateFileA(szTempPath,
  3647. GENERIC_WRITE,
  3648. FILE_SHARE_READ,
  3649. NULL,
  3650. CREATE_ALWAYS,
  3651. FILE_ATTRIBUTE_NORMAL,
  3652. NULL));
  3653. if(_cch > 0) // start from cpMin
  3654. FlipRange();
  3655. CFormatRunPtr rp(_rpCF);
  3656. for (i=0; i <= cch; i++)
  3657. {
  3658. LONG iFormat;
  3659. if (GetChar(&ch) != NOERROR)
  3660. break;
  3661. if (ch <= 0x07f)
  3662. sprintf(szTempBuf, "Char= '%c'\r\n", (char)ch);
  3663. else
  3664. sprintf(szTempBuf, "Char= 0x%x\r\n", ch);
  3665. OutputDebugStringA(szTempBuf);
  3666. if (hfileDump)
  3667. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3668. iFormat = rp.GetFormat();
  3669. CF = ped->GetCharFormat(iFormat);
  3670. Assert(CF);
  3671. sprintf(szTempBuf, "Font iFormat= %d, Charset= %d, Size= %d\r\nName= ",
  3672. iFormat, CF->_bCharSet, CF->_yHeight);
  3673. OutputDebugStringA(szTempBuf);
  3674. if (hfileDump)
  3675. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3676. wszFontname = GetFontName(CF->_iFont);
  3677. if (wszFontname)
  3678. {
  3679. if (*wszFontname <= 0x07f)
  3680. {
  3681. szTempBuf[0] = '\'';
  3682. WCTMB(CP_ACP, 0,
  3683. wszFontname, -1, &szTempBuf[1], sizeof(szTempBuf)-1,
  3684. NULL, NULL, NULL);
  3685. strcat(szTempBuf,"\'");
  3686. OutputDebugStringA(szTempBuf);
  3687. if (hfileDump)
  3688. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3689. }
  3690. else
  3691. {
  3692. for (; *wszFontname; wszFontname++)
  3693. {
  3694. sprintf(szTempBuf, "0x%x,", *wszFontname);
  3695. OutputDebugStringA(szTempBuf);
  3696. if (hfileDump)
  3697. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3698. }
  3699. }
  3700. }
  3701. OutputDebugStringA("\r\n");
  3702. if (hfileDump)
  3703. WriteFile(hfileDump, "\r\n", 2, &cbWritten, NULL);
  3704. Advance(1);
  3705. rp.AdvanceCp(1);
  3706. }
  3707. // Now dump the doc font info
  3708. CF = ped->GetCharFormat(-1);
  3709. Assert(CF);
  3710. sprintf(szTempBuf, "Default Font iFormat= -1, Charset= %d, Size= %d\r\nName= ",
  3711. CF->_bCharSet, CF->_yHeight);
  3712. OutputDebugStringA(szTempBuf);
  3713. if (hfileDump)
  3714. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3715. wszFontname = GetFontName(CF->_iFont);
  3716. if (wszFontname)
  3717. {
  3718. if (*wszFontname <= 0x07f)
  3719. {
  3720. szTempBuf[0] = '\'';
  3721. WCTMB(CP_ACP, 0,
  3722. wszFontname, -1, &szTempBuf[1], sizeof(szTempBuf),
  3723. NULL, NULL, NULL);
  3724. strcat(szTempBuf,"\'");
  3725. OutputDebugStringA(szTempBuf);
  3726. if (hfileDump)
  3727. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3728. }
  3729. else
  3730. {
  3731. for (; *wszFontname; wszFontname++)
  3732. {
  3733. sprintf(szTempBuf, "0x%x,", *wszFontname);
  3734. OutputDebugStringA(szTempBuf);
  3735. if (hfileDump)
  3736. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3737. }
  3738. }
  3739. }
  3740. OutputDebugStringA("\r\n");
  3741. if (hfileDump)
  3742. WriteFile(hfileDump, "\r\n", 2, &cbWritten, NULL);
  3743. if (ped->IsRich())
  3744. {
  3745. if (ped->fUseUIFont())
  3746. sprintf(szTempBuf, "Rich Text with UI Font");
  3747. else
  3748. sprintf(szTempBuf, "Rich Text Control");
  3749. }
  3750. else
  3751. sprintf(szTempBuf, "Plain Text Control");
  3752. OutputDebugStringA(szTempBuf);
  3753. if (hfileDump)
  3754. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  3755. OutputDebugStringA("\r\n");
  3756. if (hfileDump)
  3757. WriteFile(hfileDump, "\r\n", 2, &cbWritten, NULL);
  3758. if (hfileDump)
  3759. CloseHandle(hfileDump);
  3760. }
  3761. #endif