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.

5599 lines
154 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module RANGE.C - Implement the CTxtRange Class |
  5. *
  6. * This module implements the internal CTxtRange methods.
  7. * See tomrange.cpp 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-2000, 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. #ifndef NOLINESERVICES
  32. #include "_ols.h"
  33. #endif
  34. ASSERTDATA
  35. WCHAR 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. BOOL CTxtRange::IsOneEndUnHidden() const
  55. {
  56. CCFRunPtr rp(*this);
  57. rp.AdjustBackward();
  58. if(!rp.IsHidden())
  59. return TRUE;
  60. rp.AdjustForward();
  61. if(!rp.IsHidden())
  62. return TRUE;
  63. if(!_cch)
  64. return FALSE;
  65. rp.Move(-_cch);
  66. if(!rp.IsHidden())
  67. return TRUE;
  68. rp.AdjustBackward();
  69. if(!rp.IsHidden())
  70. return TRUE;
  71. return FALSE;
  72. }
  73. #endif
  74. void CTxtRange::RangeValidateCp(LONG cp, LONG cch)
  75. {
  76. LONG cchText = GetAdjustedTextLength();
  77. LONG cpOther = cp - cch; // Calculate cpOther with entry cp
  78. _wFlags = FALSE; // This range isn't a selection
  79. _iFormat = -1; // Set up the default format, which
  80. // doesn't get AddRefFormat'd
  81. ValidateCp(cpOther); // Validate requested other end
  82. cp = GetCp(); // Validated cp
  83. if(cp == cpOther && cp > cchText) // IP cannot follow undeletable
  84. cp = cpOther = SetCp(cchText, FALSE);// EOP at end of story
  85. _cch = cp - cpOther; // Store valid length
  86. }
  87. CTxtRange::CTxtRange(CTxtEdit *ped, LONG cp, LONG cch) :
  88. CRchTxtPtr(ped, cp)
  89. {
  90. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CTxtRange");
  91. RangeValidateCp(cp, cch);
  92. Update_iFormat(-1); // Choose _iFormat
  93. CNotifyMgr *pnm = ped->GetNotifyMgr();
  94. if(pnm)
  95. pnm->Add( (ITxNotify *)this );
  96. }
  97. CTxtRange::CTxtRange(CRchTxtPtr& rtp, LONG cch) :
  98. CRchTxtPtr(rtp)
  99. {
  100. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CTxtRange");
  101. RangeValidateCp(GetCp(), cch);
  102. Update_iFormat(-1); // Choose _iFormat
  103. CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
  104. if(pnm)
  105. pnm->Add( (ITxNotify *)this );
  106. }
  107. CTxtRange::CTxtRange(const CTxtRange &rg) :
  108. CRchTxtPtr((CRchTxtPtr)rg)
  109. {
  110. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CTxtRange");
  111. _cch = rg._cch;
  112. _wFlags = FALSE; // This range isn't a selection
  113. _iFormat = -1; // Set up the default format, which
  114. // doesn't get AddRefFormat'd
  115. Set_iCF(rg._iFormat);
  116. CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
  117. if(pnm)
  118. pnm->Add((ITxNotify *)this);
  119. }
  120. CTxtRange::~CTxtRange()
  121. {
  122. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::~CTxtRange");
  123. if(!IsZombie())
  124. {
  125. CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
  126. if(pnm )
  127. pnm->Remove((ITxNotify *)this);
  128. }
  129. ReleaseFormats(_iFormat, -1);
  130. }
  131. CRchTxtPtr& CTxtRange::operator =(const CRchTxtPtr &rtp)
  132. {
  133. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::operator =");
  134. _TEST_INVARIANT_ON(rtp)
  135. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  136. CRchTxtPtr::operator =(rtp);
  137. Assert(FALSE);
  138. CheckChange(cpSave, FALSE);
  139. return *this;
  140. }
  141. CTxtRange& CTxtRange::operator =(const CTxtRange &rg)
  142. {
  143. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::operator =");
  144. _TEST_INVARIANT_ON( rg );
  145. LONG cchSave = _cch; // Save entry _cp, _cch for change check
  146. LONG cpSave = GetCp();
  147. CRchTxtPtr::operator =(rg);
  148. _cch = rg._cch;
  149. Update_iFormat(-1);
  150. _TEST_INVARIANT_
  151. if( _fSel && (cpSave != GetCp() || cchSave != _cch) )
  152. GetPed()->GetCallMgr()->SetSelectionChanged();
  153. return *this;
  154. }
  155. /*
  156. * CTxtRange::OnPreReplaceRange (cp, cchDel, cchNew, cpFormatMin,
  157. * cpFormatMax, pNotifyData)
  158. *
  159. * @mfunc
  160. * called when the backing store changes
  161. *
  162. * @devnote
  163. * 1) if this range is before the changes, do nothing
  164. *
  165. * 2) if the changes are before this range, simply
  166. * add the delta change to GetCp()
  167. *
  168. * 3) if the changes overlap one end of the range, collapse
  169. * that end to the edge of the modifications
  170. *
  171. * 4) if the changes are completely internal to the range,
  172. * adjust _cch and/or GetCp() to reflect the new size. Note
  173. * that two overlapping insertion points will be viewed as
  174. * a 'completely internal' change.
  175. *
  176. * 5) if the changes overlap *both* ends of the range, collapse
  177. * the range to cp
  178. *
  179. * Note that there is an ambiguous cp case; namely the changes
  180. * occur *exactly* at a boundary. In this case, the type of
  181. * range matters. If a range is normal, then the changes
  182. * are assumed to fall within the range. If the range is
  183. * is protected (either in reality or via DragDrop), then
  184. * the changes are assumed to be *outside* of the range.
  185. */
  186. void CTxtRange::OnPreReplaceRange (
  187. LONG cp, //@parm cp where ReplaceRange starts ("cpMin")
  188. LONG cchDel, //@parm Count of chars after cp that are deleted
  189. LONG cchNew, //@parm Count of chars inserted after cp
  190. LONG cpFormatMin, //@parm cpMin for a formatting change
  191. LONG cpFormatMax, //@parm cpMost for a formatting change
  192. NOTIFY_DATA *pNotifyData) //@parm special data to indicate changes
  193. {
  194. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::OnPreReplaceRange");
  195. if(CONVERT_TO_PLAIN == cp)
  196. {
  197. // We need to dump our formatting because it is gone
  198. _rpCF.SetToNull();
  199. _rpPF.SetToNull();
  200. if(_fSel)
  201. GetPed()->_fUpdateSelection = TRUE;
  202. Update_iFormat(-1);
  203. return;
  204. }
  205. }
  206. /*
  207. * CTxtRange::OnPostReplaceRange (cp, cchDel, cchNew, cpFormatMin,
  208. * cpFormatMax, pNotifyData)
  209. *
  210. * @mfunc
  211. * called when the backing store changes
  212. *
  213. * @devnote
  214. * 1) if this range is before the changes, do nothing
  215. *
  216. * 2) if the changes are before this range, simply
  217. * add the delta change to GetCp()
  218. *
  219. * 3) if the changes overlap one end of the range, collapse
  220. * that end to the edge of the modifications
  221. *
  222. * 4) if the changes are completely internal to the range,
  223. * adjust _cch and/or GetCp() to reflect the new size. Note
  224. * that two overlapping insertion points will be viewed as
  225. * a 'completely internal' change.
  226. *
  227. * 5) if the changes overlap *both* ends of the range, collapse
  228. * the range to cp
  229. *
  230. * Note that there is an ambiguous cp case; namely the changes
  231. * occur *exactly* at a boundary. In this case, the type of
  232. * range matters. If a range is normal, then the changes
  233. * are assumed to fall within the range. If the range is
  234. * is protected (either in reality or via DragDrop), then
  235. * the changes are assumed to be *outside* of the range.
  236. */
  237. void CTxtRange::OnPostReplaceRange (
  238. LONG cp, //@parm cp where ReplaceRange starts ("cpMin")
  239. LONG cchDel, //@parm Count of chars after cp that are deleted
  240. LONG cchNew, //@parm Count of chars inserted after cp
  241. LONG cpFormatMin, //@parm cpMin for a formatting change
  242. LONG cpFormatMax, //@parm cpMost for a formatting change
  243. NOTIFY_DATA *pNotifyData) //@parm special data to indicate changes
  244. {
  245. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::OnPostReplaceRange");
  246. // NB!! We can't do invariant testing here, because we could
  247. // be severely out of date!
  248. LONG cchtemp;
  249. LONG cpMin, cpMost;
  250. LONG cchAdjTextLen;
  251. LONG delta = cchNew - cchDel;
  252. Assert (CONVERT_TO_PLAIN != cp);
  253. GetRange(cpMin, cpMost);
  254. // This range is before the changes. Note: an insertion pt at cp
  255. // shouldn't be changed
  256. if( cp >= cpMost )
  257. {
  258. if (pNotifyData)
  259. {
  260. if (pNotifyData->id == NOTIFY_DATA_TEXT_ID &&
  261. (pNotifyData->dwFlags & TN_TX_CELL_SHRINK)) // Rebind TX cp if cell number decrease.
  262. _rpTX.BindToCp(GetCp()); // or else we may be using the wrong cell.
  263. }
  264. // Double check to see if we need to fix up our format
  265. // run pointers. If so, all we need to do is rebind
  266. // our inherited rich text pointer
  267. if(cpFormatMin <= cpMost || cpFormatMin == CP_INFINITE)
  268. InitRunPtrs();
  269. else
  270. {
  271. // It's possible that the format runs changed anyway,
  272. // e.g., they became allocated, deallocated, or otherwise
  273. // changed. Normally, BindToCp takes care of this
  274. // situation, but we don't want to pay that cost all
  275. // the time.
  276. //
  277. // Note that starting up the rich text subsystem will
  278. // generate a notification with cpFormatMin == CP_INFINITE
  279. //
  280. // So here, call CheckFormatRuns. This makes sure that
  281. // the runs are in sync with what CTxtStory has
  282. // (doing an InitRunPtrs() _only_ if absolutely necessary).
  283. CheckFormatRuns();
  284. }
  285. return;
  286. }
  287. // Anywhere in the following that we want to increment the current cp by a
  288. // delta, we are counting on the following invariant.
  289. Assert(GetCp() >= 0);
  290. // Changes are entirely before this range. Specifically,
  291. // that's determined by looking at the incoming cp *plus* the number
  292. // of characters deleted
  293. if(cp + cchDel < cpMin || _fDragProtection && cp + cchDel <= cpMin)
  294. {
  295. cchtemp = _cch;
  296. BindToCp(GetCp() + delta);
  297. _cch = cchtemp;
  298. }
  299. // The changes are internal to the range or start within the
  300. // range and go beyond.
  301. else if( cp >= cpMin && cp <= cpMost )
  302. {
  303. // Nobody should be modifying a drag-protected range. Unfortunately,
  304. // Ren re-enters us with a SetText call during drag drop, so we need
  305. // to handle this case 'gracefully'.
  306. if( _fDragProtection )
  307. {
  308. TRACEWARNSZ("REENTERED during a DRAG DROP!! Trying to recover!");
  309. }
  310. if( cp + cchDel <= cpMost )
  311. {
  312. // Changes are purely internal, so
  313. // be sure to preserve the active end. Basically, if
  314. // GetCp() *is* cpMin, then we only need to update _cch.
  315. // Otherwise, GetCp() needs to be moved as well
  316. if( _cch >= 0 )
  317. {
  318. Assert(GetCp() == cpMost);
  319. cchtemp = _cch;
  320. BindToCp(GetCp() + delta);
  321. _cch = cchtemp + delta;
  322. }
  323. else
  324. {
  325. BindToCp(GetCp());
  326. _cch -= delta;
  327. }
  328. // Special case: the range is left with only the final EOP
  329. // selected. This means all the characters in the range were
  330. // deleted so we want to move the range back to an insertion
  331. // point at the end of the text.
  332. cchAdjTextLen = GetAdjustedTextLength();
  333. if(GetCpMin() >= cchAdjTextLen && !GetPed()->IsStreaming())
  334. {
  335. // Reduce the range to an insertion point
  336. _cch = 0;
  337. // Set the cp to the end of the document.
  338. SetCp(cchAdjTextLen, FALSE);
  339. }
  340. }
  341. else
  342. {
  343. // Changes extended beyond cpMost. In this case,
  344. // we want to truncate cpMost to the *beginning* of
  345. // the changes (i.e. cp)
  346. if( _cch > 0 )
  347. {
  348. BindToCp(cp);
  349. _cch = cp - cpMin;
  350. }
  351. else
  352. {
  353. BindToCp(cpMin);
  354. _cch = cpMin - cp;
  355. }
  356. }
  357. }
  358. else if( cp + cchDel >= cpMost )
  359. {
  360. // Nobody should be modifying a drag-protected range. Unfortunately,
  361. // Ren re-enters us with a SetText call during drag drop, so we need
  362. // to handle this case 'gracefully'.
  363. if( _fDragProtection )
  364. {
  365. TRACEWARNSZ("REENTERED during a DRAG DROP!! Trying to recover!");
  366. }
  367. // Entire range was deleted, so collapse to an insertion point at cp
  368. BindToCp(cp);
  369. _cch = 0;
  370. }
  371. else
  372. {
  373. // Nobody should be modifying a drag-protected range. Unfortunately,
  374. // Ren re-enters us with a SetText call during drag drop, so we need
  375. // to handle this case 'gracefully'.
  376. if( _fDragProtection )
  377. {
  378. TRACEWARNSZ("REENTERED during a DRAG DROP!! Trying to recover!");
  379. }
  380. // The change crossed over just cpMin. In this case move cpMin
  381. // forward to the unchanged part
  382. LONG cchdiff = (cp + cchDel) - cpMin;
  383. Assert( cp + cchDel < cpMost );
  384. Assert( cp + cchDel >= cpMin );
  385. Assert( cp < cpMin );
  386. cchtemp = _cch;
  387. if( _cch > 0 )
  388. {
  389. BindToCp(GetCp() + delta);
  390. _cch = cchtemp - cchdiff;
  391. }
  392. else
  393. {
  394. BindToCp(cp + cchNew);
  395. _cch = cchtemp + cchdiff;
  396. }
  397. }
  398. if( _fSel )
  399. {
  400. GetPed()->_fUpdateSelection = TRUE;
  401. GetPed()->GetCallMgr()->SetSelectionChanged();
  402. }
  403. Update_iFormat(-1); // Make sure _iFormat is up to date
  404. _TEST_INVARIANT_
  405. }
  406. /*
  407. * CTxtRange::Zombie ()
  408. *
  409. * @mfunc
  410. * Turn this range into a zombie (_cp = _cch = 0, NULL ped, ptrs to
  411. * backing store arrays. CTxtRange methods like GetRange(),
  412. * GetCpMost(), GetCpMin(), and GetTextLength() all work in zombie mode,
  413. * returning zero values.
  414. */
  415. void CTxtRange::Zombie()
  416. {
  417. CRchTxtPtr::Zombie();
  418. _cch = 0;
  419. }
  420. /*
  421. * CTxtRange::CheckChange(cpSave, fExtend)
  422. *
  423. * @mfunc
  424. * Set _cch according to fExtend and set selection-changed flag if
  425. * this range is a CTxtSelection and the new _cp or _cch differ from
  426. * cp and cch, respectively.
  427. *
  428. * @devnote
  429. * We can count on GetCp() and cpSave both being <= GetTextLength(),
  430. * but we can't leave GetCp() equal to GetTextLength() unless _cch ends
  431. * up > 0.
  432. */
  433. LONG CTxtRange::CheckChange(
  434. LONG cpSave, //@parm Original _cp for this range
  435. BOOL fExtend) //@parm Extend range iff TRUE
  436. {
  437. LONG cchAdj = GetAdjustedTextLength();
  438. LONG cchSave = _cch;
  439. if(fExtend) // Wants to be nondegenerate
  440. { // and maybe it is
  441. LONG cp = GetCp();
  442. _cch = cp - (cpSave - cchSave);
  443. CheckIfSelHasEOP(cpSave, cchSave);
  444. }
  445. else
  446. {
  447. _cch = 0; // Insertion point
  448. _fSelHasEOP = FALSE; // Selection doesn't contain
  449. _fSelExpandCell = FALSE; // any char, let alone a CR
  450. _nSelExpandLevel = 0; // or table cell or row
  451. }
  452. if(!_cch && GetCp() > cchAdj) // If still IP and active end
  453. CRchTxtPtr::SetCp(cchAdj); // follows nondeletable EOP,
  454. // backspace over that EOP
  455. LONG cch = GetCp() - cpSave;
  456. _fMoveBack = cch < 0;
  457. if(cch || cchSave != _cch)
  458. {
  459. Update_iFormat(-1);
  460. if(_fSel)
  461. GetPed()->GetCallMgr()->SetSelectionChanged();
  462. _TEST_INVARIANT_
  463. }
  464. return cch;
  465. }
  466. /*
  467. * CTxtRange::CheckIfSelHasEOP(cpSave, cchSave, fDoRange)
  468. *
  469. * @mfunc
  470. * Maintains _fSelHasEOP = TRUE iff selection contains one or more EOPs.
  471. * When cpSave = -1, calculates _fSelHasEOP unconditionally and cchSave
  472. * is ignored (it's only used for conditional execution). Else _fSelHasEOP
  473. * is only calculated for cases that may change it, i.e., it's assumed
  474. * be up to date before the change.
  475. *
  476. * @rdesc
  477. * TRUE iff (_fSel or fDoRange) and _cch
  478. *
  479. * @devnote
  480. * Call after updating range _cch
  481. */
  482. BOOL CTxtRange::CheckIfSelHasEOP(
  483. LONG cpSave, //@parm Previous active end cp or -1
  484. LONG cchSave, //@parm Previous signed length if cpSave != -1
  485. BOOL fDoRange) //@parm Do function even if !_fSel
  486. {
  487. // _fSelHasEOP only maintained for the selection
  488. if(!_fSel && !fDoRange)
  489. return FALSE;
  490. _fSelExpandCell = FALSE; // Default no need to expand
  491. _nSelExpandLevel = 0; // to table row/cell
  492. if(!_cch)
  493. {
  494. _fSelHasEOP = FALSE; // Selection doesn't contain
  495. return FALSE; // CR, CELL or table row delims
  496. }
  497. LONG cpMin, cpMost;
  498. LONG cpMinPrev = cpSave;
  499. LONG cpMostPrev = cpSave;
  500. LONG FEOP_Results;
  501. GetRange(cpMin, cpMost);
  502. if(cpSave != -1) // Selection may have changed
  503. {
  504. if(cchSave > 0) // Calculate previous cpMin
  505. cpMinPrev -= cchSave; // and cpMost
  506. else
  507. cpMostPrev -= cchSave;
  508. if(!_fSelHasEOP && cpMin >= cpMinPrev && cpMost <= cpMostPrev)
  509. return TRUE; // _fSelHasEOP can't change
  510. } // nor can _fSelHasCell/Row
  511. CTxtPtr tp(_rpTX);
  512. if (!_fSelHasEOP || cpSave == -1 || // If any of these conditions, scan
  513. cpMin > cpMinPrev || // range for an EOP. Could be
  514. cpMost < cpMostPrev) // CELL or CR. Table row delims have
  515. { // CRs, so catch them too
  516. tp.SetCp(cpMin);
  517. tp.FindEOP(cpMost - cpMin, &FEOP_Results);
  518. _fSelHasEOP = (FEOP_Results & FEOP_EOP) != 0;
  519. }
  520. if(_fSelHasEOP && _rpPF.IsValid()) // Might have CELL or unmatched table
  521. CalcTableExpandParms(); // row delim at range table level
  522. return TRUE;
  523. }
  524. /*
  525. * CTxtRange::CalcTableExpandParms()
  526. *
  527. * @mfunc
  528. * Calculate _fSelExpandCell and _nSelExpandLevel for ensuring that
  529. * range selects a valid table piece (fraction of single cell,
  530. * multiple, but not all, cells in a table row, one or more table
  531. * rows.
  532. */
  533. void CTxtRange::CalcTableExpandParms()
  534. {
  535. LONG cpMin, cpMost;
  536. LONG cch = GetRange(cpMin, cpMost);
  537. CPFRunPtr rp(*this);
  538. if(_cch > 0)
  539. rp.Move(-_cch); // Start at cpMin
  540. _fSelExpandCell = FALSE; // Default no need to expand
  541. _nSelExpandLevel = 0; // to table row/cell
  542. LONG cchRun;
  543. LONG LevelCpMin = rp.GetTableLevel();
  544. LONG LevelCpMost = LevelCpMin;
  545. LONG LevelMin = LevelCpMin;
  546. LONG LevelTRDMin = tomForward;
  547. while(cch > 0) // Walk range fast using PF runs
  548. { // gathering table level info
  549. LevelCpMost = rp.GetTableLevel();
  550. LevelMin = min(LevelMin, LevelCpMost);
  551. if(rp.IsTableRowDelimiter())
  552. LevelTRDMin = min(LevelTRDMin, LevelCpMost);
  553. cchRun = rp.GetCchLeft();
  554. cch -= cchRun;
  555. rp.Move(cchRun);
  556. }
  557. if(!(LevelCpMin | LevelCpMost)) // If beginning & end are 0, we're done
  558. return;
  559. if(LevelCpMin >= LevelTRDMin || // Crossed a table-row delimiter
  560. LevelCpMost >= LevelTRDMin) // of minimum level
  561. {
  562. Assert(LevelTRDMin < 16); // Only nibble is allocated
  563. _nSelExpandLevel = LevelTRDMin;
  564. if(LevelTRDMin == LevelMin)
  565. return; // Expand to LevelTRDMin
  566. }
  567. // At least one end has a table level < minimum table row delim level.
  568. // May need to expand to row, but check if a CELL is included
  569. // at the minimum level, in which case, need to expand to Cell.
  570. if(cch < 0)
  571. rp.Move(cch); // rp is at cpMost
  572. LONG cp = cpMost;
  573. CTxtPtr tp(_rpTX);
  574. for(cch = cpMost - cpMin; cch > 0; )
  575. {
  576. rp.AdjustBackward();
  577. Assert(rp.GetIch() || rp._iRun);
  578. if(rp.GetTableLevel() == LevelMin)
  579. { // Only look for CELLs at min level
  580. LONG cchFind;
  581. cchRun = rp.GetIch();
  582. cchRun = min(cchRun, cch);
  583. tp.SetCp(cp);
  584. while(cchRun > 0)
  585. {
  586. if(tp.GetPrevChar() == CELL)
  587. {
  588. _fSelExpandCell = TRUE;
  589. _nSelExpandLevel = 0;
  590. return; // Found a CELL at min level, so need
  591. } // to expand to Cell at that level
  592. cchFind = tp.FindEOP(-cchRun, NULL);
  593. if(!cchFind)
  594. break;
  595. cchRun += cchFind;
  596. }
  597. }
  598. cch -= rp.GetIch(); // Go back to previous PF run
  599. cp -= rp.GetIch(); // Cheaper to move cp than tp
  600. rp.SetIch(0); // (might be at/before cpMin)
  601. }
  602. }
  603. /*
  604. * CTxtRange::CheckTableSelection(fUpdate, fEnableExpandCell, pfTRDsInvolved, dwFlags)
  605. *
  606. * @mfunc
  607. * Select only the first cell if one or more CELLs are selected, but
  608. * not the whole row, at the minimum table level.
  609. *
  610. * @rdesc
  611. * TRUE iff selected only contents of first cell in range without the
  612. * CELL mark
  613. */
  614. BOOL CTxtRange::CheckTableSelection (
  615. BOOL fUpdate, //@parm Call Update() if change occurs
  616. BOOL fEnableExpandCell,//@parm If TRUE select only 1st cell of multiple
  617. BOOL *pfTRDsInvolved, //@parm Out parm to say if TRDs at range ends
  618. DWORD dwFlags) //@parm Flags for ReplaceRange()
  619. {
  620. LONG cpMin, cpMost;
  621. BOOL fRet = FALSE;
  622. BOOL fTRDsInvolved = FALSE;
  623. AdjustCRLF(1);
  624. if(!_cch)
  625. {
  626. while(_rpTX.IsAtTRD(0))
  627. AdvanceCRLF(CSC_NORMAL, FALSE);
  628. goto checkLP;
  629. }
  630. if(!_fSel && _rpPF.IsValid()) // It's a range; find out if
  631. { // tables are involved
  632. CPFRunPtr rp(*this);
  633. LONG cch = _cch;
  634. if(_cch > 0) // Active end at cpMost: will
  635. rp.AdjustBackward(); // scan backward
  636. while(!rp.InTable())
  637. {
  638. if(_cch > 0)
  639. {
  640. cch -= rp.GetIch();
  641. if(!rp.PrevRun())
  642. goto checkLP; // Done, since not in table
  643. cch -= rp.GetCchLeft();
  644. if(cch <= 0)
  645. goto checkLP; // Ditto
  646. }
  647. else
  648. {
  649. cch += rp.GetCchLeft();
  650. if(cch >= 0 || !rp.NextRun())
  651. goto checkLP; // Not in table
  652. }
  653. }
  654. // Range fiddling with tables: calc whether to expand to cell or row
  655. // NB: expand to cell/row includes contained nested tables.
  656. CalcTableExpandParms();
  657. if(!_fSelExpandCell && _nSelExpandLevel)
  658. { // Expand to row (but not to cell)
  659. FindRow(&cpMin, &cpMost, _nSelExpandLevel);
  660. Set(cpMost, cpMost - cpMin);
  661. _nSelExpandLevel = 0;
  662. fTRDsInvolved = TRUE;
  663. goto checkLP;
  664. }
  665. }
  666. if(_fSelExpandCell && fEnableExpandCell)// Partial row selected
  667. { // Reduce selection to contents
  668. Collapser(TRUE); // of first cell
  669. while(_rpTX.IsAtTRD(STARTFIELD))
  670. AdvanceCRLF(CSC_NORMAL, FALSE);
  671. FindCell(&cpMin, &cpMost);
  672. Assert(cpMost > cpMin || _rpTX.GetChar() == CELL && _rpTX.IsAtStartOfCell());
  673. cpMost--;
  674. Set(cpMost, cpMost - cpMin);
  675. Assert(!_fSelExpandCell);
  676. if(fUpdate)
  677. Update(TRUE);
  678. fRet = TRUE;
  679. }
  680. if(pfTRDsInvolved) // Caller wants to know if table-
  681. { // row-delimiters are involved
  682. CPFRunPtr rp(*this);
  683. rp.AdjustForward();
  684. fTRDsInvolved = TRUE; // Default TRUE
  685. if(!rp.IsTableRowDelimiter()) // Check both sides of one end
  686. {
  687. rp.AdjustBackward();
  688. if(!rp.IsTableRowDelimiter())
  689. {
  690. rp.Move(-_cch); // Check both sides of other end
  691. if(!rp.IsTableRowDelimiter())
  692. {
  693. rp.AdjustBackward();
  694. if(!rp.IsTableRowDelimiter())
  695. fTRDsInvolved = FALSE;// Neither end has TRD
  696. }
  697. }
  698. }
  699. }
  700. checkLP:
  701. LONG iFormat = _iFormat;
  702. if(CheckLinkProtection(dwFlags, iFormat))
  703. Set_iCF(iFormat);
  704. if(pfTRDsInvolved)
  705. *pfTRDsInvolved = fTRDsInvolved;
  706. return fRet;
  707. }
  708. /*
  709. * CTxtRange::CheckLinkProtection(&dwFlags, &iFormat)
  710. *
  711. * @mfunc
  712. * If friendly part of link is selected, select hidden part too.
  713. *
  714. * @rdesc
  715. * TRUE iff change made
  716. */
  717. BOOL CTxtRange::CheckLinkProtection(
  718. DWORD & dwFlags,
  719. LONG & iFormat)
  720. {
  721. if(dwFlags & RR_NO_LP_CHECK)
  722. return FALSE;
  723. LONG cpMin, cpMost;
  724. LONG cch = GetRange(cpMin, cpMost);
  725. CCFRunPtr rp(*this);
  726. if(_cch > 0) // Ensure rp is positioned at
  727. rp.Move(-_cch); // cpMin
  728. rp.AdjustBackward();
  729. DWORD dw = rp.GetEffects();
  730. if((dw & (CFE_LINKPROTECTED | CFE_HIDDEN)) == (CFE_LINKPROTECTED | CFE_HIDDEN))
  731. {
  732. if(_cch)
  733. {
  734. if(!cpMin) // Already at start of inst field
  735. return FALSE;
  736. // If deleting rest of friendly
  737. while(cch >= 0) // hyperlink name, need to
  738. { // delete instruction field too
  739. if(!rp.IsLinkProtected())
  740. {
  741. FindAttributes(&cpMin, NULL, CFE_LINKPROTECTED | 0x80000000);
  742. Set(cpMin, cpMin - cpMost);
  743. return TRUE;
  744. }
  745. cch -= rp.GetCchLeft();
  746. rp.NextRun();
  747. }
  748. }
  749. else
  750. {
  751. // Inserting new text between hidden and friendly part of hyperlink:
  752. // back up over hidden part
  753. FindAttributes(&cpMin, NULL, CFE_LINKPROTECTED | 0x80000000);
  754. SetCp(cpMin, FALSE);
  755. dwFlags |= RR_UNHIDE;
  756. _rpCF.AdjustBackward();
  757. iFormat = _rpCF.GetFormat();
  758. return TRUE;
  759. }
  760. }
  761. else if(!(dw & CFE_LINK) && GetPed()->GetCharFormat(iFormat)->_dwEffects & CFE_LINK)
  762. {
  763. iFormat = rp.GetFormat();
  764. return TRUE;
  765. }
  766. return FALSE;
  767. }
  768. /*
  769. * CTxtRange::GetRange(&cpMin, &cpMost)
  770. *
  771. * @mfunc
  772. * set cpMin = this range cpMin
  773. * set cpMost = this range cpMost
  774. * return cpMost - cpMin, i.e. abs(_cch)
  775. *
  776. * @rdesc
  777. * abs(_cch)
  778. */
  779. LONG CTxtRange::GetRange (
  780. LONG& cpMin, //@parm Pass-by-ref cpMin
  781. LONG& cpMost) const //@parm Pass-by-ref cpMost
  782. {
  783. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetRange");
  784. LONG cch = _cch;
  785. if(cch >= 0)
  786. {
  787. cpMost = GetCp();
  788. cpMin = cpMost - cch;
  789. }
  790. else
  791. {
  792. cch = -cch;
  793. cpMin = GetCp();
  794. cpMost = cpMin + cch;
  795. }
  796. return cch;
  797. }
  798. /*
  799. * CTxtRange::GetCpMin()
  800. *
  801. * @mfunc
  802. * return this range's cpMin
  803. *
  804. * @rdesc
  805. * cpMin
  806. *
  807. * @devnote
  808. * If you need cpMost and/or cpMost - cpMin, GetRange() is faster
  809. */
  810. LONG CTxtRange::GetCpMin() const
  811. {
  812. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetCpMin");
  813. LONG cp = GetCp();
  814. return _cch <= 0 ? cp : cp - _cch;
  815. }
  816. /*
  817. * CTxtRange::GetCpMost()
  818. *
  819. * @mfunc
  820. * return this range's cpMost
  821. *
  822. * @rdesc
  823. * cpMost
  824. *
  825. * @devnote
  826. * If you need cpMin and/or cpMost - cpMin, GetRange() is faster
  827. */
  828. LONG CTxtRange::GetCpMost() const
  829. {
  830. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetCpMost");
  831. LONG cp = GetCp();
  832. return _cch >= 0 ? cp : cp - _cch;
  833. }
  834. /*
  835. * CTxtRange::Update(fScrollIntoView)
  836. *
  837. * @mfunc
  838. * Virtual stub routine overruled by CTxtSelection::Update() when this
  839. * text range is a text selection. The purpose is to update the screen
  840. * display of the caret or selection to correspond to changed cp's.
  841. *
  842. * @rdesc
  843. * TRUE
  844. */
  845. BOOL CTxtRange::Update (
  846. BOOL fScrollIntoView) //@parm TRUE if should scroll caret into view
  847. {
  848. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Update");
  849. return TRUE; // Simple range has no selection colors or
  850. } // caret, so just return TRUE
  851. /*
  852. * CTxtRange::SetCp(cp, fExtend)
  853. *
  854. * @mfunc
  855. * Set active end of this range to cp. Leave other end where it is or
  856. * collapse range depending on fExtend (see CheckChange()).
  857. *
  858. * @rdesc
  859. * cp at new active end (may differ from cp, since cp may be invalid).
  860. */
  861. LONG CTxtRange::SetCp(
  862. LONG cp, //@parm new cp for active end of this range
  863. BOOL fExtend) //@parm Extend range iff TRUE
  864. {
  865. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtRange::SetCp");
  866. LONG cpSave = GetCp();
  867. CRchTxtPtr::SetCp(cp);
  868. CheckChange(cpSave, fExtend); // NB: this changes _cp if after
  869. return GetCp(); // final CR and _cch = 0
  870. }
  871. /*
  872. * CTxtRange::Set (cp, cch)
  873. *
  874. * @mfunc
  875. * Set this range's active-end cp and signed cch
  876. *
  877. * @rdesc
  878. * TRUE if range cp or cch changed.
  879. */
  880. BOOL CTxtRange::Set (
  881. LONG cp, //@parm Desired active end cp
  882. LONG cch) //@parm Desired signed count of chars
  883. {
  884. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Set");
  885. BOOL bRet = FALSE;
  886. LONG cchSave = _cch; // Save entry _cp, _cch for change check
  887. LONG cchText = GetAdjustedTextLength();
  888. LONG cpSave = GetCp();
  889. LONG cpOther = cp - cch; // Desired "other" end
  890. ValidateCp(cp); // Be absolutely sure to validate
  891. ValidateCp(cpOther); // both ends
  892. if(cp == cpOther && cp > cchText) // IP cannot follow undeletable
  893. cp = cpOther = cchText; // EOP at end of story
  894. CRchTxtPtr::Move(cp - GetCp());
  895. AssertSz(cp == GetCp(),
  896. "CTxtRange::Set: inconsistent cp");
  897. if(GetPed()->fUseCRLF())
  898. {
  899. cch = _rpTX.AdjustCRLF();
  900. if(cch)
  901. {
  902. _rpCF.Move(cch); // Keep all 3 runptrs in sync
  903. _rpPF.Move(cch);
  904. cp = GetCp();
  905. }
  906. if(cpOther != cp)
  907. {
  908. CTxtPtr tp(_rpTX);
  909. tp.Move(cpOther - cp);
  910. cpOther += tp.AdjustCRLF();
  911. }
  912. }
  913. _cch = cp - cpOther; // Validated _cch value
  914. CheckIfSelHasEOP(cpSave, cchSave); // Maintain _fSelHasEOP in
  915. // outline mode
  916. _fMoveBack = GetCp() < cpSave;
  917. if(cpSave != GetCp() || cchSave != _cch)
  918. {
  919. if(_fSel)
  920. GetPed()->GetCallMgr()->SetSelectionChanged();
  921. Update_iFormat(-1);
  922. bRet = TRUE;
  923. }
  924. _TEST_INVARIANT_
  925. return bRet;
  926. }
  927. /*
  928. * CTxtRange::Move(cch, fExtend)
  929. *
  930. * @mfunc
  931. * Advance active end of range by cch.
  932. * Other end stays put iff fExtend
  933. *
  934. * @rdesc
  935. * cch active end actually moved
  936. */
  937. LONG CTxtRange::Move (
  938. LONG cch, //@parm Signed char count to move active end
  939. BOOL fExtend) //@parm Extend range iff TRUE
  940. {
  941. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Move");
  942. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  943. CRchTxtPtr::Move(cch);
  944. return CheckChange(cpSave, fExtend);
  945. }
  946. /*
  947. * CTxtRange::AdvanceCRLF(csc, fExtend)
  948. *
  949. * @mfunc
  950. * Advance active end of range one char, treating CRLF as a single char.
  951. * Other end stays put iff fExtend is nonzero.
  952. *
  953. * @rdesc
  954. * cch active end actually moved
  955. */
  956. LONG CTxtRange::AdvanceCRLF(
  957. CSCONTROL csc, //@parm Complex Script Control
  958. BOOL fExtend) //@parm Extend range iff TRUE
  959. {
  960. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::AdvanceCRLF");
  961. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  962. CRchTxtPtr::AdvanceCRLF();
  963. #ifndef NOCOMPLEXSCRIPTS
  964. if(csc == CSC_SNAPTOCLUSTER)
  965. SnapToCluster(1); // Snap to cluster forward
  966. #endif
  967. return CheckChange(cpSave, fExtend);
  968. }
  969. /*
  970. * CTxtRange::BackupCRLF(csc, fExtend)
  971. *
  972. * @mfunc
  973. * Backup active end of range one char, treating CRLF as a single char.
  974. * Other end stays put iff fExtend
  975. *
  976. * @rdesc
  977. * cch actually moved
  978. */
  979. LONG CTxtRange::BackupCRLF(
  980. CSCONTROL csc, //@parm Complex Script Control
  981. BOOL fExtend) //@parm Extend range iff TRUE
  982. {
  983. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::BackupCRLF");
  984. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  985. CRchTxtPtr::BackupCRLF(csc != CSC_NOMULTICHARBACKUP);
  986. #ifndef NOCOMPLEXSCRIPTS
  987. if(csc == CSC_SNAPTOCLUSTER)
  988. SnapToCluster(-1); // Snap to cluster backward
  989. #endif
  990. return CheckChange(cpSave, fExtend);
  991. }
  992. /*
  993. * CTxtRange::AdjustCRLF(iDir)
  994. *
  995. * @mfunc
  996. * Adjust the position of this range's ends to the beginning of a CRLF
  997. * or CRCRLF combination, if it is in the middle of such a combination.
  998. * Move range end to the end of a Unicode surrogate pair or a STARTFIELD/
  999. * ENDFIELD pair if it is in the middle of such a pair. Similarly move
  1000. * the range start to the beginning of such a pair.
  1001. *
  1002. * @rdesc
  1003. * TRUE iff change occurred
  1004. */
  1005. BOOL CTxtRange::AdjustCRLF(
  1006. LONG iDir) //@parm Move forward/backward for iDir = 1/-1, respectively
  1007. {
  1008. LONG cch;
  1009. if(!_cch) // Insertion point
  1010. {
  1011. cch = _rpTX.AdjustCRLF(iDir);
  1012. if(cch)
  1013. {
  1014. _rpCF.Move(cch);
  1015. _rpPF.Move(cch);
  1016. return TRUE;
  1017. }
  1018. return FALSE;
  1019. }
  1020. CTxtPtr tp(_rpTX); // Nondegenerate range
  1021. cch = _cch + tp.AdjustCRLF(_cch); // Adjust active end
  1022. LONG cp = tp.GetCp(); // Possibly new active end
  1023. tp.Move(-cch); // Go to other end
  1024. cch -= tp.AdjustCRLF(-cch); // Calc its adjustment
  1025. if(cch != _cch || cp != GetCp()) // If adjustment occurred,
  1026. { // set new values
  1027. Set(cp, cch);
  1028. return TRUE;
  1029. }
  1030. return FALSE;
  1031. }
  1032. /*
  1033. * CTxtRange::FindWordBreak(action, fExtend)
  1034. *
  1035. * @mfunc
  1036. * Move active end as determined by plain-text FindWordBreak().
  1037. * Other end stays put iff fExtend
  1038. *
  1039. * @rdesc
  1040. * cch active end actually moved
  1041. */
  1042. LONG CTxtRange::FindWordBreak (
  1043. INT action, //@parm action defined by CTxtPtr::FindWordBreak()
  1044. BOOL fExtend) //@parm Extend range iff TRUE
  1045. {
  1046. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtPtr::FindWordBreak");
  1047. LONG cpSave = GetCp(); // Save entry _cp for CheckChange()
  1048. CRchTxtPtr::FindWordBreak(action);
  1049. return CheckChange(cpSave, fExtend);
  1050. }
  1051. /*
  1052. * CTxtRange::FlipRange()
  1053. *
  1054. * @mfunc
  1055. * Flip active and non active ends
  1056. */
  1057. void CTxtRange::FlipRange()
  1058. {
  1059. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FlipRange");
  1060. _TEST_INVARIANT_
  1061. CRchTxtPtr::Move(-_cch);
  1062. _cch = -_cch;
  1063. }
  1064. /*
  1065. * CTxtRange::HexToUnicode(publdr)
  1066. *
  1067. * @mfunc
  1068. * Convert hex number ending at this range's cpMost to a Unicode
  1069. * character and replace the hex number by that character. Take into
  1070. * account Unicode surrogates for hex values from 0x10000 up to 0x10FFFF.
  1071. *
  1072. * @rdesc
  1073. * HRESULT S_OK if conversion successful and hex number is replaced by
  1074. * the corresponding Unicode character
  1075. */
  1076. HRESULT CTxtRange::HexToUnicode (
  1077. IUndoBuilder *publdr) //@parm UndoBuilder to receive antievents
  1078. {
  1079. LONG ch; // Handy char value
  1080. LONG cpMin, cpMost; // This range's cpMin and cpMost
  1081. LONG cch = GetRange(cpMin, cpMost); // Count of chars in range
  1082. LONG i; // = cpMost - cpMin
  1083. LONG lch = 0; // Collects hex into binary value
  1084. LONG cchSave = _cch; // Save this range's cch
  1085. LONG cpSave = GetCp(); // and cp so we can restore in case of error
  1086. if(cch) // Range has cch chars selected
  1087. { // so only convert these chars
  1088. if(cpMost > GetAdjustedTextLength() || cch > 6)
  1089. return S_FALSE;
  1090. Collapser(tomEnd); // Collapse range to IP at cpMost
  1091. }
  1092. else // Range is insertion point so
  1093. cch = 6; // check up to 6 prev chars
  1094. // Convert preceding span of up to cch hexadigits to binary number (lch)
  1095. for(i = 0; cch--; i += 4) // i is shift count for hex
  1096. {
  1097. ch = GetPrevChar(); // ch = previous char
  1098. if(ch == '+') // Check for U+xxxx notation
  1099. { // If it's there, set up to
  1100. Move(-1, TRUE); // delete the U+ (or u+)
  1101. Move((GetPrevChar() | 0x20) == 'u' ? -1 : 1, TRUE);
  1102. break; // Else leave the +
  1103. }
  1104. if(ch > 'f' || !IsXDigit(ch)) // Break on nonhex chars
  1105. break;
  1106. Move(-1, TRUE); // Move back one char
  1107. ch |= 0x20; // Convert hex to lower case if
  1108. ch -= (ch >= 'a') ? 'a' - 10 : '0'; // upper case; then to binary
  1109. lch += (ch << i); // Shift left & add in binary hex
  1110. }
  1111. if(!lch) // No number: convert preceding
  1112. return UnicodeToHex(publdr); // char back to hex
  1113. if (lch > 0x10FFFF || // Don't insert numbers beyond Unicode's 17 planes
  1114. IN_RANGE(0xD800, lch, 0xDFFF) || // nor Unicode surrogate lead/trail word,
  1115. IN_RANGE(STARTFIELD, lch,NOTACHAR)||// nor internal use chars
  1116. lch == CELL ||
  1117. IsEOP(GetPrevChar()) && IN_RANGE(0x300, lch, 0x36F))
  1118. { // Note: CleanseAndReplaceRange suppresses others
  1119. Set(cpSave, cchSave); // Restore previous selection
  1120. return S_FALSE;
  1121. }
  1122. WCHAR str[2] = {(WCHAR)lch};
  1123. cch = 1; // Default one 16-bit code
  1124. if(lch > 0xFFFF) // Beyond BMP: so use
  1125. { // Unicode surrogate pair
  1126. lch -= 0x10000;
  1127. str[0] = 0xD800 + (lch >> 10);
  1128. str[1] = 0xDC00 + (lch & 0x3FF);
  1129. cch = 2;
  1130. }
  1131. if(publdr) // If undo enabled, stop
  1132. publdr->StopGroupTyping(); // collecting typing string
  1133. _rpCF.AdjustBackward(); // Use format of run preceding
  1134. Set_iCF(_rpCF.GetFormat()); // hex number
  1135. _fUseiFormat = TRUE;
  1136. _rpCF.AdjustForward();
  1137. // Replace hexadigits with corresponding Unicode character choosing an
  1138. // appropriate font if font preceding hex can't support character
  1139. CleanseAndReplaceRange(cch, str, FALSE, publdr, NULL);
  1140. return S_OK;
  1141. }
  1142. /*
  1143. * CTxtRange::UnicodeToHex(publdr)
  1144. *
  1145. * @mfunc
  1146. * Convert Unicode character(s) preceeding cpMin to a hex number and
  1147. * select it. Translate Unicode surrogates into corresponding for hex
  1148. * values from 0x10000 up to 0x10FFFF.
  1149. *
  1150. * @rdesc
  1151. * HRESULT S_OK if conversion successful and Unicode character(s) is
  1152. * replaced by corresponding hex number.
  1153. */
  1154. HRESULT CTxtRange::UnicodeToHex (
  1155. IUndoBuilder *publdr) //@parm UndoBuilder to receive antievents
  1156. {
  1157. if(_cch) // If there's a selection,
  1158. { // convert 1st char in sel
  1159. Collapser(tomStart);
  1160. AdvanceCRLF(CSC_NORMAL, FALSE);
  1161. }
  1162. LONG cp = GetCp();
  1163. if(!cp || _rpTX.IsAfterTRD(0))
  1164. return S_FALSE; // No character to convert
  1165. _cch = 1; // Select previous char
  1166. LONG n = GetPrevChar(); // Get it
  1167. if(n == CELL || IN_RANGE(STARTFIELD, n, NOTACHAR))
  1168. return S_FALSE; // Don't convert CELL marks
  1169. if(publdr)
  1170. publdr->StopGroupTyping();
  1171. if(IN_RANGE(0xDC00, n, 0xDFFF)) // Unicode surrogate trail word
  1172. {
  1173. if(cp <= 1) // No lead word
  1174. return S_FALSE;
  1175. Move(-2, TRUE);
  1176. LONG ch = CRchTxtPtr::GetChar();
  1177. Assert(IN_RANGE(0xD800, ch, 0xDBFF));
  1178. n = (n & 0x3FF) + ((ch & 0x3FF) << 10) + 0x10000;
  1179. _cch = -2;
  1180. }
  1181. // Convert ch to str
  1182. LONG cch = 0;
  1183. LONG quot, rem; // ldiv results
  1184. WCHAR str[6];
  1185. WCHAR * pch = &str[0];
  1186. for(LONG d = 1; d < n; d <<= 4) // d = smallest power of 16 > n
  1187. ;
  1188. if(n && d > n)
  1189. d >>= 4;
  1190. while(d)
  1191. {
  1192. quot = n / d; // Avoid an ldiv
  1193. rem = n % d;
  1194. n = quot + '0';
  1195. if(n > '9')
  1196. n += 'A' - '9' - 1;
  1197. *pch++ = (WCHAR)n; // Store digit
  1198. cch++;
  1199. n = rem; // Setup remainder
  1200. d >>= 4;
  1201. }
  1202. CleanseAndReplaceRange(cch, str, FALSE, publdr, NULL);
  1203. _cch = cch; // Select number
  1204. if(_fSel)
  1205. Update(FALSE);
  1206. return S_OK;
  1207. }
  1208. /*
  1209. * CTxtRange::IsInputSequenceValid(pch, cchIns, fOverType, pfBaseChar)
  1210. *
  1211. * @mfunc
  1212. * Verify the sequence of incoming text. Return FALSE if invalid
  1213. * combination is found. The criteria is to allow any combinations
  1214. * that are displayable on screen (the simplest approach used by system
  1215. * edit control).
  1216. *
  1217. * @rdesc
  1218. * Return FALSE if invalid combination is found; else TRUE.
  1219. *
  1220. * FUTURE: We may consider to support bad sequence filter or text streaming.
  1221. * The code below can be extended easily enough to do so.
  1222. */
  1223. BOOL CTxtRange::IsInputSequenceValid(
  1224. WCHAR* pch, // Inserting string
  1225. LONG cchIns, // Character count
  1226. BOOL fOverType, // Insert or Overwrite mode
  1227. BOOL* pfBaseChar) // Is pwch[0] a cluster start (base char)?
  1228. {
  1229. #ifndef NOCOMPLEXSCRIPTS
  1230. CTxtEdit* ped = GetPed();
  1231. CTxtPtr tp(_rpTX);
  1232. HKL hkl = GetKeyboardLayout(0);
  1233. BOOL fr = TRUE;
  1234. if (ped->fUsePassword() || ped->_fNoInputSequenceChk)
  1235. return TRUE; // no check when editing password
  1236. if (PRIMARYLANGID(hkl) == LANG_VIETNAMESE)
  1237. {
  1238. // No concern about overtyping or cluster since we look backward only
  1239. // 1 char and dont care characters following the insertion point.
  1240. if(_cch > 0)
  1241. tp.Move(-_cch);
  1242. fr = IsVietCdmSequenceValid(tp.GetPrevChar(), *pch);
  1243. }
  1244. else if (PRIMARYLANGID(hkl) == LANG_THAI ||
  1245. W32->IsIndicLcid(LOWORD(hkl)))
  1246. {
  1247. // Do complex things for Thai and Indic
  1248. WCHAR rgchText[32];
  1249. WCHAR* pchText = rgchText;
  1250. CUniscribe* pusp = ped->Getusp();
  1251. CTxtBreaker* pbrk = ped->_pbrk;
  1252. LONG found = 0;
  1253. LONG cp, cpSave, cpLimMin, cpLimMax;
  1254. LONG cchDel = 0, cchText, ich;
  1255. LONG cpEnd = ped->GetAdjustedTextLength();
  1256. if (_cch > 0)
  1257. tp.Move(-_cch);
  1258. cp = cpSave = cpLimMin = cpLimMax = tp.GetCp();
  1259. if (_cch)
  1260. {
  1261. cchDel = abs(_cch);
  1262. }
  1263. else if (fOverType && !tp.IsAtEOP() && cp != cpEnd)
  1264. {
  1265. // Delete up to the next cluster in overtype mode
  1266. cchDel++;
  1267. if (pbrk)
  1268. while (cp + cchDel < cpEnd && !pbrk->CanBreakCp(BRK_CLUSTER, cp + cchDel))
  1269. cchDel++;
  1270. }
  1271. cpLimMax += cchDel;
  1272. // Figure the min/max boundaries
  1273. if (pbrk)
  1274. {
  1275. // Min boundary
  1276. cpLimMin += tp.FindEOP(tomBackward, &found);
  1277. if (!(found & FEOP_EOP))
  1278. cpLimMin = 0;
  1279. while (--cp > cpLimMin && !pbrk->CanBreakCp(BRK_CLUSTER, cp));
  1280. cpLimMin = max(cp, cpLimMin); // more precise boundary
  1281. // Max boundary
  1282. cp = cpLimMax;
  1283. tp.SetCp(cpLimMax);
  1284. cpLimMax += tp.FindEOP(tomForward, &found);
  1285. if (!(found & FEOP_EOP))
  1286. cpLimMax = ped->GetTextLength();
  1287. while (cp < cpLimMax && !pbrk->CanBreakCp(BRK_CLUSTER, cp++));
  1288. cpLimMax = min(cp, cpLimMax); // more precise boundary
  1289. }
  1290. else
  1291. {
  1292. // No cluster info we statically bound to -1/+1 from selection range
  1293. cpLimMin--;
  1294. cpLimMin = max(0, cpLimMin);
  1295. cpLimMax += cchDel + 1;
  1296. cpLimMax = min(cpLimMax, ped->GetTextLength());
  1297. }
  1298. cp = cpSave + cchDel;
  1299. cchText = cpSave - cpLimMin + cchIns + cpLimMax - cp;
  1300. tp.SetCp(cpLimMin);
  1301. if (cchText > 32)
  1302. pchText = new WCHAR[cchText];
  1303. if (pchText)
  1304. {
  1305. // prepare text
  1306. cchText = tp.GetText (cpSave - cpLimMin, pchText);
  1307. tp.Move (cchText + cchDel);
  1308. ich = cchText;
  1309. wcsncpy (&pchText[cchText], pch, cchIns);
  1310. cchText += cchIns;
  1311. cchText += tp.GetText (cpLimMax - cpSave - cchDel, &pchText[cchText]);
  1312. Assert (cchText == cpLimMax - cpLimMin - cchDel + cchIns);
  1313. if (pusp)
  1314. {
  1315. SCRIPT_STRING_ANALYSIS ssa;
  1316. HRESULT hr;
  1317. BOOL fDecided = FALSE;
  1318. hr = ScriptStringAnalyse(NULL, pchText, cchText, GLYPH_COUNT(cchText), -1,
  1319. SSA_BREAK, -1, NULL, NULL, NULL, NULL, NULL, &ssa);
  1320. if (S_OK == hr)
  1321. {
  1322. if (fOverType)
  1323. {
  1324. const SCRIPT_LOGATTR* psla = ScriptString_pLogAttr(ssa);
  1325. BOOL fBaseChar = !psla || psla[ich].fCharStop;
  1326. if (!fBaseChar)
  1327. {
  1328. // In overtype mode, if the inserted char is not a cluster start.
  1329. // We act like insert mode. Recursive call with fOvertype = FALSE.
  1330. fr = IsInputSequenceValid(pch, cchIns, 0, NULL);
  1331. fDecided = TRUE;
  1332. }
  1333. if (pfBaseChar)
  1334. *pfBaseChar = fBaseChar;
  1335. }
  1336. if (!fDecided && S_FALSE == ScriptStringValidate(ssa))
  1337. fr = FALSE;
  1338. ScriptStringFree(&ssa);
  1339. }
  1340. }
  1341. if (pchText != rgchText)
  1342. delete[] pchText;
  1343. }
  1344. }
  1345. return fr;
  1346. #else
  1347. return TRUE;
  1348. #endif // NOCOMPLEXSCRIPTS
  1349. }
  1350. /*
  1351. * CTxtRange::CleanseAndReplaceRange(cch, *pch, fTestLimit, publdr,
  1352. * pchD, pcchMove, dwFlags)
  1353. * @mfunc
  1354. * Cleanse the string pch (replace CRLFs by CRs, etc.) and substitute
  1355. * the resulting string for the text in this range using the CCharFormat
  1356. * _iFormat and updating other text runs as needed. For single-line
  1357. * controls, truncate on the first EOP and substitute the truncated
  1358. * string. Also truncate if string would overflow the max text length.
  1359. *
  1360. * @rdesc
  1361. * Count of new characters added
  1362. */
  1363. LONG CTxtRange::CleanseAndReplaceRange (
  1364. LONG cchS, //@parm Length of replacement (Source) text
  1365. const WCHAR * pchS, //@parm Replacement (Source) text
  1366. BOOL fTestLimit, //@parm Whether to do limit test
  1367. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1368. WCHAR * pchD, //@parm Destination string (multiline only)
  1369. LONG* pcchMove, //@parm Count of chars moved in 1st replace
  1370. DWORD dwFlags) //@parm ReplaceRange's flags
  1371. {
  1372. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CleanseAndReplaceRange");
  1373. CTxtEdit * ped = GetPed();
  1374. BYTE iCharRepDefault;
  1375. LONG cchM = 0;
  1376. LONG cchMove = 0;
  1377. LONG cchNew = 0; // Collects total cch inserted
  1378. LONG cch; // Collects cch for cur charset
  1379. DWORD ch, ch1;
  1380. WCHAR chPassword = ped->TxGetPasswordChar();
  1381. LONG cpFirst = GetCpMin();
  1382. QWORD qw = 0;
  1383. QWORD qwCharFlags = 0;
  1384. QWORD qwCharMask = GetCharRepMask();
  1385. DWORD dwCurrentFontUsed = 0;
  1386. BOOL f10Mode = ped->Get10Mode();
  1387. BOOL fCallerDestination = pchD != 0; // Save if pchD enters as 0
  1388. BOOL fDefFontHasASCII = FALSE;
  1389. CFreezeDisplay fd(ped->_pdp);
  1390. BOOL fDefFontSymbol = qwCharMask == FSYMBOL;
  1391. BOOL fFEBaseFont;
  1392. BOOL fMultiLine = ped->_pdp->IsMultiLine();
  1393. BOOL fSurrogate;
  1394. bool fUIFont = fUseUIFont();
  1395. BOOL fUseCRLF = ped->fUseCRLF();
  1396. const WCHAR * pch = pchS;
  1397. CTempWcharBuf twcb; // Buffer needed for multiline if pchD = 0
  1398. CCharFormat CFCurrent; // Current CF used during IME composition
  1399. const DWORD fALPHA = 0x01;
  1400. BOOL fDeleteChar = !ped->_fIMEInProgress && !ped->IsRich() && _cch;
  1401. DWORD dwFlagsSave = dwFlags;
  1402. LONG iFormatCurrent = GetiFormat();
  1403. dwFlags = (dwFlags & ~7) | RR_ITMZ_NONE;
  1404. if (ped->_fIMEInProgress)
  1405. {
  1406. // Initialize data to handle alpha/ASCII dual font mode
  1407. // during IME composition
  1408. dwCurrentFontUsed = FFE;
  1409. CFCurrent = *ped->GetCharFormat(iFormatCurrent);
  1410. UINT iCharRepKB = GetKeyboardCharRep(0);
  1411. if (iCharRepKB != CFCurrent._iCharRep && IsFECharRep(iCharRepKB))
  1412. {
  1413. CCFRunPtr rp(_rpCF, ped);
  1414. int iFormatFound = iFormatCurrent;
  1415. CCharFormat CFTemp;
  1416. // Search the range for font that support this keyboard
  1417. if(rp.GetPreferredFontInfo(iCharRepKB, CFTemp._iCharRep, CFTemp._iFont, CFTemp._yHeight,
  1418. CFTemp._bPitchAndFamily, -1, MATCH_CURRENT_CHARSET, &iFormatFound)
  1419. && iFormatFound != iFormatCurrent)
  1420. {
  1421. CFCurrent = *ped->GetCharFormat(iFormatFound);
  1422. }
  1423. }
  1424. }
  1425. // Check if default font supports full ASCII and Symbol
  1426. if (fUIFont)
  1427. {
  1428. QWORD qwMaskDefFont = GetCharRepMask(TRUE);
  1429. fDefFontHasASCII = (qwMaskDefFont & FASCII) == FASCII;
  1430. fDefFontSymbol = qwMaskDefFont == FSYMBOL;
  1431. iCharRepDefault = ped->GetCharFormat(-1)->_iCharRep;
  1432. }
  1433. else
  1434. iCharRepDefault = ped->GetCharFormat(iFormatCurrent)->_iCharRep;
  1435. fFEBaseFont = IsFECharRep(iCharRepDefault);
  1436. if(!pchS)
  1437. cchS = 0;
  1438. else if(fMultiLine)
  1439. {
  1440. if(cchS < 0) // Calculate length for
  1441. cchS = wcslen(pchS); // target buffer
  1442. if(cchS && !pchD)
  1443. {
  1444. pchD = twcb.GetBuf(cchS);
  1445. if(!pchD) // Couldn't allocate buffer:
  1446. return 0; // give up with no update
  1447. }
  1448. pch = pchD;
  1449. }
  1450. else if(cchS < 0) // Calculate string length
  1451. cchS = tomForward; // while looking for EOP
  1452. WORD fDontUpdateFmt = _fDontUpdateFmt;
  1453. _fDontUpdateFmt = TRUE;
  1454. for(cch = 0; cchS; cchS--)
  1455. {
  1456. ch = *pchS;
  1457. if(!ch && (!fMultiLine || !fCallerDestination))
  1458. break;
  1459. if(IN_RANGE(CELL, ch, CR)) // Handle CR and LF combos
  1460. {
  1461. if(!fMultiLine && !IN_RANGE(8, ch, 9))// Truncate at 1st EOP to
  1462. break; // be compatible with user.exe SLE
  1463. // and for consistent behavior
  1464. if(ch == CR && !f10Mode)
  1465. {
  1466. if(cchS > 1)
  1467. {
  1468. ch1 = *(pchS + 1);
  1469. if(cchS > 2 && ch1 == CR && *(pchS+2) == LF)
  1470. {
  1471. if(fUseCRLF)
  1472. {
  1473. *pchD++ = ch;
  1474. *pchD++ = ch1;
  1475. ch = LF;
  1476. cch += 2;
  1477. }
  1478. else
  1479. {
  1480. // Translate CRCRLF to CR or to ' '
  1481. ch = ped->fXltCRCRLFtoCR() ? CR : ' ';
  1482. }
  1483. pchS += 2; // Bypass two chars
  1484. cchS -= 2;
  1485. }
  1486. else if(ch1 == LF)
  1487. {
  1488. if(fUseCRLF) // Copy over whole CRLF
  1489. {
  1490. *pchD++ = ch; // Here we copy CR
  1491. ch = ch1; // Setup to copy LF
  1492. cch++;
  1493. }
  1494. pchS++;
  1495. cchS--;
  1496. }
  1497. }
  1498. }
  1499. else if(!fUseCRLF && ch == LF) // Treat lone LFs as EOPs, i.e.,
  1500. ch = CR; // be nice to Unix text files
  1501. else if(ch == CELL)
  1502. ch = ' ';
  1503. }
  1504. else if((ch | 1) == PS) // Translate Unicode para/line
  1505. { // separators into CR/VT
  1506. if(!fMultiLine)
  1507. break;
  1508. ch = (ch == PS) ? CR : VT;
  1509. }
  1510. else if(IN_RANGE(STARTFIELD, ch, NOTACHAR))
  1511. ch = ' ';
  1512. qw = FSYMBOL;
  1513. fSurrogate = FALSE;
  1514. if(!fDefFontSymbol)
  1515. {
  1516. qw = GetCharFlags(pchS, cchS, iCharRepDefault);// Check for complex scripts
  1517. if(qw & FSURROGATE && cchS > 1)
  1518. {
  1519. fSurrogate = TRUE;
  1520. pchS++;
  1521. cchS--;
  1522. if (pchD)
  1523. *pchD++ = ch; // Copy lead surrogate
  1524. ch = *pchS; // Setup to copy trail surrogate
  1525. }
  1526. }
  1527. if(chPassword)
  1528. qw = GetCharFlags(&chPassword, 1, iCharRepDefault);
  1529. qwCharFlags |= qw; // FE, and charset changes
  1530. qw &= ~0x2F; // Exclude non-fontbind flags
  1531. if(fMultiLine) // In multiline controls, collect
  1532. { // possibly translated chars
  1533. if(qw & FSYMBOL) // Convert 0xF000 thru 0xF0FF to
  1534. ch &= 0xFF; // SYMBOL_CHARSET with 0x00 thru
  1535. *pchD++ = ch; // 0xFF. FUTURE: make work for
  1536. } // single line too...
  1537. pchS++; // pchS points at next char
  1538. if(ped->IsAutoFont() && !fDefFontSymbol)
  1539. {
  1540. BOOL fReplacedText = FALSE;
  1541. if (fDeleteChar)
  1542. {
  1543. fDeleteChar = FALSE;
  1544. ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE, NULL, dwFlags);
  1545. Set_iCF(-1);
  1546. qwCharMask = GetCharRepMask(TRUE);
  1547. }
  1548. if (!ped->_fIMEInProgress)
  1549. {
  1550. // Simp. Chinese uses some of the Latin2 symbols
  1551. if (fUIFont && (qw == FLATIN2 || IN_RANGE(0x0250, ch, 0x02FF) ||
  1552. IN_RANGE(0xFE50, ch, 0xFE6F)))
  1553. {
  1554. if (iCharRepDefault == BIG5_INDEX || iCharRepDefault == GB2312_INDEX ||
  1555. GetACP() == CP_CHINESE_SIM || GetACP() == CP_CHINESE_TRAD)
  1556. {
  1557. if (VerifyFEString(CP_CHINESE_SIM, (const WCHAR *)&ch, 1, TRUE) == CP_CHINESE_SIM ||
  1558. VerifyFEString(CP_CHINESE_TRAD, (const WCHAR *)&ch, 1, TRUE) == CP_CHINESE_TRAD)
  1559. qw = FCHINESE;
  1560. }
  1561. }
  1562. if (fUIFont && qw == FHILATIN1 && fFEBaseFont &&
  1563. (iCharRepDefault == SHIFTJIS_INDEX || iCharRepDefault == HANGUL_INDEX || ch >= 0x100)) // Special characters that are classiied as HiAnsi
  1564. {
  1565. // Use Ansi font for HiAnsi
  1566. if (dwCurrentFontUsed != FHILATIN1)
  1567. {
  1568. fReplacedText = TRUE;
  1569. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1570. qw, &cchM, cpFirst, IGNORE_CURRENT_FONT, dwFlags); // Replace text up to previous char
  1571. }
  1572. dwCurrentFontUsed = FHILATIN1;
  1573. }
  1574. else if (fUIFont && fDefFontHasASCII && _rpCF.IsValid() &&
  1575. (qw & FASCII || IN_RANGE(0x2018, ch, 0x201D)))
  1576. {
  1577. if (dwCurrentFontUsed != FASCII)
  1578. {
  1579. fReplacedText = TRUE;
  1580. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1581. 0, &cchM, cpFirst, IGNORE_CURRENT_FONT, dwFlags); // Replace text up to previous char
  1582. // Use the -1 font charset/face/size so the current font effect
  1583. // will still be used.
  1584. CCharFormat CFDefault = *ped->GetCharFormat(-1);
  1585. SetCharFormat(&CFDefault, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1586. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1587. dwCurrentFontUsed = FASCII;
  1588. }
  1589. }
  1590. else if (!fUIFont && qwCharMask & FLATIN1
  1591. && ((IN_RANGE(0x2018, ch, 0x201D) || ch == 0x2026))
  1592. || (qw & (FCHINESE | FBIG5) && qwCharMask & (FCHINESE | FKANA | FBIG5 | FHANGUL)))
  1593. { // Stay with current font
  1594. ; // and do Nothing
  1595. }
  1596. else if (qw && (qw & qwCharMask) != qw // No match: need charset change
  1597. || dwCurrentFontUsed) // or change in classification
  1598. {
  1599. fReplacedText = TRUE;
  1600. dwCurrentFontUsed = 0;
  1601. if(qw & (FCHINESE | FBIG5 | FFE2)) // If Han char, check next few
  1602. { // chars for a Hangul or Kana
  1603. Assert(cchS);
  1604. const WCHAR *pchT = pchS;
  1605. QWORD qw0;
  1606. LONG i = min(10, cchS - 1);
  1607. for (int j=0; j < 2; j++)
  1608. {
  1609. if (j)
  1610. {
  1611. // Check current text
  1612. pchT = pch;
  1613. i = min(6, cch);
  1614. }
  1615. for(; i > 0 && *pchT; i--)
  1616. {
  1617. qw0 = GetCharFlags(pchT++, i, iCharRepDefault);
  1618. qw |= qw0 & ~FSURROGATE;
  1619. if(qw0 & FSURROGATE && i > 1)
  1620. {
  1621. pchT++;
  1622. i--;
  1623. }
  1624. }
  1625. }
  1626. i = CalcTextLenNotInRange();
  1627. if(cchS < 6 && i) // Get flags around range
  1628. {
  1629. CTxtPtr tp(_rpTX);
  1630. i = min(i, 6);
  1631. if(!_cch) // For insertion point, backup
  1632. tp.Move(-i/2); // half way
  1633. else if(_cch < 0) // Active end at cpMin, backup
  1634. tp.Move(-i); // whole way
  1635. qw |= tp.GetCharFlagsInRange(i, iCharRepDefault);
  1636. }
  1637. qw &= FFE | FFE2 | FSURROGATE;
  1638. }
  1639. else if(qw & (FHILATIN1 | FLATIN2) && qwCharMask & FLATIN)
  1640. {
  1641. LONG i = qwCharMask & FLATIN;
  1642. qw = W32->GetCharFlags125x(ch) & FLATIN;
  1643. if(!(qw & i))
  1644. for(i = 0x100; i < 0x20000 && !(qw & i); i <<= 1)
  1645. ;
  1646. qw &= i;
  1647. }
  1648. else if(qw & FMATH)
  1649. {
  1650. // Bind math fonts here (combos of ital, bold, script,
  1651. // fraktur, open, sans, mono)
  1652. }
  1653. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1654. qw, &cchM, cpFirst, MATCH_FONT_SIG, dwFlags); // Replace text up to previous char
  1655. }
  1656. }
  1657. else
  1658. { // IME in progress, only need to check ASCII cases
  1659. BOOL fHandled = FALSE;
  1660. if (ch <= 0x7F)
  1661. {
  1662. if (fUIFont)
  1663. {
  1664. // Use default font
  1665. if (dwCurrentFontUsed != FASCII)
  1666. {
  1667. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1668. 0, &cchM, cpFirst, IGNORE_CURRENT_FONT, dwFlags); // Replace text up to previous char
  1669. // Use the -1 font charset/face/size so the current font effect
  1670. // will still be used.
  1671. CCharFormat CFDefault = *ped->GetCharFormat(-1);
  1672. SetCharFormat(&CFDefault, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1673. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1674. fReplacedText = TRUE;
  1675. dwCurrentFontUsed = FASCII;
  1676. }
  1677. fHandled = TRUE;
  1678. }
  1679. else if (ped->_fDualFont && IsASCIIAlpha(ch))
  1680. {
  1681. // Use English Font
  1682. if (dwCurrentFontUsed != fALPHA)
  1683. {
  1684. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1685. qw, &cchM, cpFirst, IGNORE_CURRENT_FONT, dwFlags); // Replace text up to previous char
  1686. fReplacedText = TRUE;
  1687. dwCurrentFontUsed = fALPHA;
  1688. }
  1689. fHandled = TRUE;
  1690. }
  1691. }
  1692. else if (qw & FSURROGATE ||
  1693. (qw & FOTHER &&
  1694. (IN_RANGE(0x03400, ch, 0x04DFF) || IN_RANGE(0xE000, ch, 0x0F8FF))))
  1695. {
  1696. // Try font binding for Surrogate, Extension-A, and Private Usage Area
  1697. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1698. qw, &cchM, cpFirst, IGNORE_CURRENT_FONT, dwFlags); // Replace text up to previous char
  1699. fReplacedText = TRUE;
  1700. dwCurrentFontUsed = FSURROGATE;
  1701. fHandled = TRUE;
  1702. }
  1703. // Use current FE font
  1704. if(!fHandled && dwCurrentFontUsed != FFE)
  1705. {
  1706. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr,
  1707. 0, &cchM, cpFirst, IGNORE_CURRENT_FONT, dwFlags); // Replace text up to previous char
  1708. SetCharFormat(&CFCurrent, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1709. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1710. fReplacedText = TRUE;
  1711. dwCurrentFontUsed = FFE;
  1712. }
  1713. }
  1714. if (fReplacedText)
  1715. {
  1716. qwCharMask = (qw & FSYMBOL) ? FSYMBOL : GetCharRepMask();
  1717. if(cchM)
  1718. cchMove = cchM; // Can only happen on 1st replace
  1719. pch = fMultiLine ? pchD : pchS;
  1720. pch--;
  1721. if(fSurrogate)
  1722. pch--;
  1723. cch = 0;
  1724. }
  1725. }
  1726. cch++;
  1727. if(fSurrogate)
  1728. cch++;
  1729. }
  1730. ped->OrCharFlags(qwCharFlags, publdr);
  1731. cchNew += CheckLimitReplaceRange(cch, pch, fTestLimit, publdr, (qw & (FOTHER | FSURROGATE)), &cchM, cpFirst,
  1732. IGNORE_CURRENT_FONT, dwFlags);
  1733. if(cchM)
  1734. cchMove = cchM; // Can only happen on 1st replace
  1735. if (pcchMove)
  1736. *pcchMove = cchMove;
  1737. if (ped->IsComplexScript())
  1738. {
  1739. if (dwFlagsSave & RR_ITMZ_NONE || ped->IsStreaming())
  1740. ped->_fItemizePending = TRUE;
  1741. else
  1742. ItemizeReplaceRange(cchNew, cchMove, publdr, dwFlagsSave & RR_ITMZ_UNICODEBIDI);
  1743. }
  1744. _fDontUpdateFmt = fDontUpdateFmt;
  1745. Update_iFormat(-1); // Choose _iFormat
  1746. return cchNew;
  1747. }
  1748. /*
  1749. * CTxtRange::CheckLimitReplaceRange(cchNew, *pch, fTestLimit, publdr,
  1750. * qwCharFlags, pcchMove, prp, iMatchCurrent, &dwFlags)
  1751. * @mfunc
  1752. * Replace the text in this range by pch using CCharFormat _iFormat
  1753. * and updating other text runs as needed.
  1754. *
  1755. * @rdesc
  1756. * Count of new characters added
  1757. *
  1758. * @devnote
  1759. * moves this text pointer to end of replaced text and
  1760. * may move text block and formatting arrays
  1761. */
  1762. LONG CTxtRange::CheckLimitReplaceRange (
  1763. LONG cch, //@parm Length of replacement text
  1764. WCHAR const * pch, //@parm Replacement text
  1765. BOOL fTestLimit, //@parm Whether to do limit test
  1766. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1767. QWORD qwCharFlags, //@parm CharFlags following pch
  1768. LONG * pcchMove, //@parm Count of chars moved in 1st replace
  1769. LONG cpFirst, //@parm Starting cp for font binding
  1770. int iMatchCurrent, //@parm Font matching method
  1771. DWORD & dwFlags) //@parm ReplaceRange's flags
  1772. {
  1773. CTxtEdit *ped = GetPed();
  1774. if(cch || _cch)
  1775. {
  1776. if(fTestLimit)
  1777. {
  1778. LONG cchLen = CalcTextLenNotInRange();
  1779. DWORD cchMax = ped->TxGetMaxLength();
  1780. if((DWORD)(cch + cchLen) > cchMax) // New plus old count exceeds
  1781. { // max allowed, so truncate
  1782. cch = cchMax - cchLen; // down to what fits
  1783. cch = max(cch, 0); // Keep it positive
  1784. ped->GetCallMgr()->SetMaxText(); // Report exceeded
  1785. }
  1786. }
  1787. if (cch && ped->IsAutoFont() && !ped->_fIMEInProgress)
  1788. {
  1789. LONG iFormatTemp;
  1790. if (fUseUIFont() && GetAdjustedTextLength() != _cch)
  1791. {
  1792. // Delete the old string first so _iFormat is defined
  1793. ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE, pcchMove, dwFlags);
  1794. iFormatTemp = _iFormat;
  1795. }
  1796. else
  1797. iFormatTemp = GetiFormat();
  1798. BYTE iCharRepCurrent = ped->GetCharFormat(iFormatTemp)->_iCharRep;
  1799. if (IsFECharRep(iCharRepCurrent) && !(qwCharFlags & (FOTHER | FSURROGATE)))
  1800. {
  1801. // Check if current font can handle this string.
  1802. INT cpgCurrent = CodePageFromCharRep(iCharRepCurrent);
  1803. INT cpgNew = VerifyFEString(cpgCurrent, pch, cch, FALSE);
  1804. if (cpgCurrent != cpgNew)
  1805. {
  1806. // Setup the new CodePage to handle this string
  1807. CCharFormat CF;
  1808. BYTE iCharRep = CharRepFromCodePage(cpgNew);
  1809. CCFRunPtr rp(_rpCF, ped);
  1810. rp.Move(cpFirst - GetCp());
  1811. CF._iCharRep = iCharRep;
  1812. if(rp.GetPreferredFontInfo(iCharRep, CF._iCharRep, CF._iFont, CF._yHeight,
  1813. CF._bPitchAndFamily, _iFormat, iMatchCurrent))
  1814. {
  1815. SetCharFormat(&CF, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1816. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1817. }
  1818. }
  1819. }
  1820. }
  1821. cch = ReplaceRange(cch, pch, publdr, SELRR_REMEMBERRANGE, pcchMove, dwFlags);
  1822. dwFlags |= RR_NO_LP_CHECK;
  1823. }
  1824. // If following string contains Hangul or Kana, use Korean or Japanese
  1825. // font signatures, respectively. Else use incoming qwCharFlags
  1826. if(!qwCharFlags)
  1827. return cch;
  1828. qwCharFlags &= ~0x2F;
  1829. if(qwCharFlags & (FFE | FFE2))
  1830. {
  1831. BOOL fFE2 = (qwCharFlags & FSURROGATE) != 0;
  1832. if(qwCharFlags & FHANGUL)
  1833. qwCharFlags = fFE2 ? (FKOR2 | FSURROGATE) : FHANGUL;
  1834. else if(qwCharFlags & FKANA)
  1835. qwCharFlags = fFE2 ? (FJPN2 | FSURROGATE) : FKANA;
  1836. else if(qwCharFlags & FBIG5)
  1837. qwCharFlags = fFE2 ? (FCHT2 | FSURROGATE) : FBIG5;
  1838. else if(fFE2)
  1839. qwCharFlags = FCHS2 | FSURROGATE;
  1840. }
  1841. CCharFormat CF;
  1842. bool fCFDefined = FALSE;
  1843. bool fCFDefDefined = FALSE;
  1844. bool fUIFont = ped->fUseUIFont();
  1845. CF._iCharRep = W32->CharRepFromFontSig(qwCharFlags);
  1846. CF._iFont = 0;
  1847. if (W32->IsExternalFontCheckActive() &&
  1848. (qwCharFlags & (FOTHER | FSURROGATE) ||
  1849. !(fCFDefDefined = W32->IsDefaultFontDefined(CF._iCharRep, fUIFont, CF._iFont))))
  1850. {
  1851. // REMARK: Currently pch[cch] is the current char and others might
  1852. // follow as well. We could get some text from _rpTX to provide more
  1853. // context.
  1854. fCFDefined = W32->GetExternalPreferredFontInfo(pch,
  1855. (qwCharFlags & FSURROGATE) ? cch + 2 : cch + 1,
  1856. CF._iCharRep, CF._iFont, CF._bPitchAndFamily, fUIFont || ped->Get10Mode());
  1857. }
  1858. if(fCFDefined || fCFDefDefined || qwCharFlags != FOTHER)
  1859. {
  1860. SHORT iFontDummy = CF._iFont;
  1861. CCFRunPtr rp(_rpCF, ped);
  1862. rp.Move(cpFirst - GetCp());
  1863. fCFDefined = rp.GetPreferredFontInfo(CF._iCharRep, CF._iCharRep, fCFDefined ? iFontDummy : CF._iFont, CF._yHeight,
  1864. CF._bPitchAndFamily, (_cch ? -1 : _iFormat), fCFDefined ? GET_HEIGHT_ONLY : iMatchCurrent);
  1865. }
  1866. if(fCFDefined)
  1867. {
  1868. SetCharFormat(&CF, 0, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
  1869. CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK);
  1870. }
  1871. return cch;
  1872. }
  1873. /*
  1874. * CTxtRange::ReplaceRange(cchNew, *pch, publdr. selaemode, pcchMove)
  1875. *
  1876. * @mfunc
  1877. * Replace the text in this range by pch using CCharFormat _iFormat
  1878. * and updating other text runs as needed.
  1879. *
  1880. * @rdesc
  1881. * Count of new characters added
  1882. *
  1883. * @devnote
  1884. * moves this text pointer to end of replaced text and
  1885. * may move text block and formatting arrays
  1886. */
  1887. LONG CTxtRange::ReplaceRange (
  1888. LONG cchNew, //@parm Length of replacement text
  1889. WCHAR const * pch, //@parm Replacement text
  1890. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1891. SELRR selaemode, //@parm Controls how selection antievents are to be generated.
  1892. LONG * pcchMove, //@parm Number of chars moved after replace
  1893. DWORD dwFlags) //@parm Special flags
  1894. {
  1895. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::ReplaceRange");
  1896. LONG lRet;
  1897. LONG iFormat = _iFormat;
  1898. BOOL fReleaseFormat = FALSE;
  1899. ICharFormatCache * pcf = GetCharFormatCache();
  1900. _TEST_INVARIANT_
  1901. if(!(cchNew | _cch)) // Nothing to add or delete,
  1902. { // so we're done
  1903. if(pcchMove)
  1904. *pcchMove = 0;
  1905. return 0;
  1906. }
  1907. if(publdr && selaemode != SELRR_IGNORE)
  1908. {
  1909. Assert(selaemode == SELRR_REMEMBERRANGE);
  1910. HandleSelectionAEInfo(GetPed(), publdr, GetCp(), _cch,
  1911. GetCpMin() + cchNew, 0, SELAE_MERGE);
  1912. }
  1913. CFreezeDisplay fd(GetPed()->_pdp);
  1914. if(_cch > 0)
  1915. FlipRange();
  1916. CheckLinkProtection(dwFlags, iFormat);
  1917. // If we are replacing a non-degenerate selection, then the Word95
  1918. // UI specifies that we should use the rightmost formatting at cpMin.
  1919. if(_cch < 0 && _rpCF.IsValid() && !_fDualFontMode && !_fUseiFormat)
  1920. {
  1921. _rpCF.AdjustForward();
  1922. iFormat = _rpCF.GetFormat();
  1923. // This is a bit icky, but the idea is to stabilize the
  1924. // reference count on iFormat. When we get it above, it's
  1925. // not addref'ed, so if we happen to delete the text in the
  1926. // range and the range is the only one with that format,
  1927. // then the format will go away.
  1928. pcf->AddRef(iFormat);
  1929. fReleaseFormat = TRUE;
  1930. }
  1931. const CCharFormat *pCF = GetPed()->GetCharFormat(iFormat);
  1932. BOOL fTmpDispAttr = pCF->_sTmpDisplayAttrIdx != -1;
  1933. if((fTmpDispAttr || dwFlags & (RR_UNHIDE | RR_UNLINK)) && cchNew) // Don't hide or protect or apply temp.
  1934. { // display attributes to inserted text
  1935. BOOL fUnhide = dwFlags & RR_UNHIDE && pCF->_dwEffects & (CFE_HIDDEN | CFE_PROTECTED);
  1936. BOOL fUnlink = dwFlags & RR_UNLINK && pCF->_dwEffects & CFE_LINK;
  1937. if(fTmpDispAttr | fUnhide | fUnlink) // Switch to unhidden/unlinked iFormat or
  1938. { // turn of temp. display attribute
  1939. CCharFormat CF = *pCF;
  1940. if(fReleaseFormat) // Need to release iFormat ref'd
  1941. pcf->Release(iFormat); // above, since no longer need it
  1942. if(fUnhide)
  1943. {
  1944. CF._dwEffects &= ~(CFE_HIDDEN | CFE_PROTECTED);
  1945. if (CF._dwEffects & CFE_LINKPROTECTED)
  1946. CF._dwEffects &= ~(CFE_LINKPROTECTED | CFE_LINK);
  1947. }
  1948. if(fUnlink)
  1949. CF._dwEffects &= ~CFE_LINK;
  1950. if(fTmpDispAttr)
  1951. CF._sTmpDisplayAttrIdx = -1;
  1952. pcf->Cache(&CF, &iFormat);
  1953. fReleaseFormat = TRUE; // Be sure to release new one
  1954. }
  1955. }
  1956. _fUseiFormat = FALSE;
  1957. LONG cchForReplace = -_cch;
  1958. _cch = 0;
  1959. lRet = CRchTxtPtr::ReplaceRange(cchForReplace, cchNew, pch, publdr,
  1960. iFormat, pcchMove, dwFlags);
  1961. if(cchForReplace)
  1962. CheckMergedCells(publdr);
  1963. if(lRet)
  1964. _fMoveBack = FALSE;
  1965. Update_iFormat(fReleaseFormat ? iFormat : -1);
  1966. if(fReleaseFormat)
  1967. {
  1968. Assert(pcf);
  1969. pcf->Release(iFormat);
  1970. }
  1971. return lRet;
  1972. }
  1973. /*
  1974. * CTxtRange::DeleteWithTRDCheck(publdr. selaemode, pcchMove, dwFlags)
  1975. *
  1976. * @mfunc
  1977. * Delete text in this range, inserting an EOP in place of the text
  1978. * if the range ends at a table-row start delimiter
  1979. *
  1980. * @rdesc
  1981. * Count of new characters added
  1982. *
  1983. * @devnote
  1984. * moves this text pointer to end of replaced text and
  1985. * may move text block and formatting arrays
  1986. */
  1987. LONG CTxtRange::DeleteWithTRDCheck (
  1988. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  1989. SELRR selaemode, //@parm Controls how selection antievents are to be generated.
  1990. LONG * pcchMove, //@parm Count of chars moved after replace
  1991. DWORD dwFlags) //@parm ReplaceRange flags
  1992. {
  1993. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::ReplaceRange");
  1994. LONG cchEOP = 0;
  1995. WCHAR szEOP[] = {CR, LF, 0};
  1996. if(IsRich())
  1997. {
  1998. CTxtPtr tp(_rpTX); // If inserting before a table
  1999. if(GetCch() < 0) // row, ReplaceRange with EOP,
  2000. tp.Move(-GetCch()); // which we delete after read
  2001. if(tp.IsAtTRD(STARTFIELD)) // if read ends with an EOP
  2002. cchEOP = GetPed()->fUseCRLF() ? 2 : 1;
  2003. }
  2004. if(!(_cch | cchEOP))
  2005. return 0; // Nothing to do
  2006. ReplaceRange(cchEOP, szEOP, publdr, selaemode, pcchMove, dwFlags);
  2007. if(GetCch())
  2008. {
  2009. // Text deletion failed because range didn't collapse. Our work
  2010. // here is done.
  2011. return 0;
  2012. }
  2013. if(cchEOP)
  2014. {
  2015. _rpPF.AdjustBackward();
  2016. const CParaFormat *pPF = GetPF();
  2017. _rpPF.AdjustForward();
  2018. if(pPF->_wEffects & PFE_TABLE)
  2019. {
  2020. CParaFormat PF = *GetPed()->GetParaFormat(-1);
  2021. PF._bTableLevel = pPF->_bTableLevel - 1;
  2022. Assert(PF._bTableLevel >= 0);
  2023. _cch = cchEOP; // Select EOP just inserted
  2024. PF._wEffects &= ~PFE_TABLE; // Default not in table
  2025. if(PF._bTableLevel)
  2026. PF._wEffects |= PFE_TABLE; // It's in a table
  2027. SetParaFormat(&PF, publdr, PFM_ALL2, PFM2_ALLOWTRDCHANGE);
  2028. SetCp(GetCp() - cchEOP, FALSE); // Collapse before EOP
  2029. }
  2030. }
  2031. return cchEOP;
  2032. }
  2033. /*
  2034. * CTxtRange::Delete(publdr. selaemode)
  2035. *
  2036. * @mfunc
  2037. * Delete text in this range.
  2038. */
  2039. void CTxtRange::Delete (
  2040. IUndoBuilder * publdr, //@parm UndoBuilder to receive antievents
  2041. SELRR selaemode) //@parm Controls generation of selection antievents
  2042. {
  2043. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Delete");
  2044. if(!_cch)
  2045. return; // Nothing to delete
  2046. if(!GetPed()->IsBiDi())
  2047. {
  2048. ReplaceRange(0, NULL, publdr, selaemode, NULL);
  2049. return;
  2050. }
  2051. CFreezeDisplay fd(GetPed()->_pdp);
  2052. ReplaceRange(0, NULL, publdr, selaemode);
  2053. }
  2054. /*
  2055. * CTxtRange::BypassHiddenText(iDir, fExtend)
  2056. *
  2057. * @mfunc
  2058. * Bypass hidden text forward/backward for iDir positive/negative
  2059. *
  2060. * @rdesc
  2061. * TRUE if succeeded or no hidden text. FALSE if at document limit
  2062. * (end/start for Direction positive/negative) or if hidden text between
  2063. * cp and that limit.
  2064. */
  2065. BOOL CTxtRange::BypassHiddenText(
  2066. LONG iDir,
  2067. BOOL fExtend)
  2068. {
  2069. if (!_rpCF.IsValid())
  2070. return TRUE; // No format run, not hidden
  2071. if(iDir > 0)
  2072. _rpCF.AdjustForward();
  2073. else
  2074. _rpCF.AdjustBackward();
  2075. if(!(GetPed()->GetCharFormat(_rpCF.GetFormat())->_dwEffects & CFE_HIDDEN))
  2076. return TRUE;
  2077. CCFRunPtr rp(*this);
  2078. LONG cch = (iDir > 0)
  2079. ? rp.FindUnhiddenForward() : rp.FindUnhiddenBackward();
  2080. BOOL bRet = !rp.IsHidden(); // Note whether still hidden
  2081. if(bRet) // It isn't:
  2082. Move(cch, fExtend); // bypass hidden text
  2083. return bRet;
  2084. }
  2085. /*
  2086. * CTxtRange::CheckMergedCells(publdr)
  2087. *
  2088. * @mfunc
  2089. * If range is at a table-row start delimiter, ensure that cells
  2090. * that are vertically merged with cells above have a top cell.
  2091. *
  2092. * If the text before this range is a row that contains top cells
  2093. * these cells have to be converted to nonmerged cells unless the
  2094. * text starting at this range contains corresponding low cells.
  2095. *
  2096. * If the text starting at this range is a table row with low cells,
  2097. * they may have to be converted to top or nonmerged cells.
  2098. */
  2099. void CTxtRange::CheckMergedCells (
  2100. IUndoBuilder *publdr) //@parm UndoBuilder to receive antievents
  2101. {
  2102. Assert(!_cch); // Assumes this range is an IP
  2103. unsigned ch = _rpTX.GetChar();
  2104. if(ch != CR && ch != STARTFIELD) // Early out
  2105. return;
  2106. if(ch == CR) // CRchTxtPtr::ReplaceRange() may
  2107. { // leave a CR at end before a table
  2108. CTxtPtr tp(_rpTX); // row start delimiter, so check for
  2109. LONG cch = tp.AdvanceCRLF(FALSE);// that case
  2110. if(!tp.IsAtTRD(STARTFIELD))
  2111. return;
  2112. Move(cch, FALSE); // Check that row
  2113. }
  2114. else if(!_rpTX.IsAtTRD(STARTFIELD)) // Allow top cells to remain w/o row
  2115. return; // below so that complex tables can
  2116. // be inserted
  2117. const CParaFormat *pPF0 = NULL; // Default no row to compare to
  2118. if(_rpTX.IsAfterTRD(ENDFIELD)) // Range is at table row, so do top
  2119. { // cell check on previous row
  2120. CheckTopCells(publdr);
  2121. _rpPF.AdjustBackward();
  2122. pPF0 = GetPF(); // Compare to preceding row
  2123. _rpPF.AdjustForward();
  2124. }
  2125. const CParaFormat * pPF1 = GetPF(); // Point at current row PF
  2126. CELLPARMS rgCellParms[MAX_TABLE_CELLS];
  2127. if(CheckCells(&rgCellParms[0], pPF1, pPF0, fLowCell, fLowCell | fTopCell))
  2128. { // One or more cells need changes
  2129. CTxtRange rg(*this);
  2130. rg._rpPF.AdjustForward();
  2131. rg.SetCellParms(&rgCellParms[0], pPF1->_bTabCount, TRUE, publdr);
  2132. rg.CheckTopCells(publdr); // Do top cell check on entry row
  2133. }
  2134. if(ch == CR)
  2135. BackupCRLF(CSC_NORMAL, FALSE); // Restore this range
  2136. }
  2137. /*
  2138. * CTxtRange::CheckTopCells(publdr)
  2139. *
  2140. * @mfunc
  2141. * If this range follows a table-row end delimiter, normalize any top
  2142. * cells in the previous row that don't have corresponding low cells
  2143. * in the current row.
  2144. */
  2145. void CTxtRange::CheckTopCells (
  2146. IUndoBuilder *publdr) //@parm UndoBuilder to receive antievents
  2147. {
  2148. Assert(_rpTX.IsAfterTRD(ENDFIELD));
  2149. _rpPF.AdjustBackward();
  2150. const CParaFormat * pPF0 = GetPF();
  2151. _rpPF.AdjustForward();
  2152. const CParaFormat * pPF1 = _rpTX.IsAtTRD(STARTFIELD) ? GetPF() : NULL;
  2153. CELLPARMS rgCellParms[MAX_TABLE_CELLS];
  2154. if(CheckCells(&rgCellParms[0], pPF0, pPF1, fTopCell, fLowCell))
  2155. {
  2156. LONG cpMin;
  2157. CTxtRange rg(*this);
  2158. rg.Move(-2, FALSE); // Back up before table row end delimiter
  2159. rg.FindRow(&cpMin, NULL, rg.GetPF()->_bTableLevel);
  2160. rg.Set(cpMin, 0);
  2161. rg.SetCellParms(&rgCellParms[0], pPF0->_bTabCount, FALSE, publdr);
  2162. }
  2163. }
  2164. /*
  2165. * CTxtRange::CheckCells(prgCellParms, pPF1, pPF0, dwMaskCell, dwMaskCellAssoc)
  2166. *
  2167. * @mfunc
  2168. * Check cells with type dwMaskCell in row for pPF1 to see if their
  2169. * associated cells in row for pPF0 are compatible and make appropriate
  2170. * changes in prgCellParms if a discrepancy is found.
  2171. *
  2172. * @rdesc
  2173. * TRUE if prgCellParms contains changes from the cells in pPF1.
  2174. */
  2175. BOOL CTxtRange::CheckCells (
  2176. CELLPARMS * prgCellParms, //@parm CellParms to update
  2177. const CParaFormat * pPF1, //@parm PF for row to check cells on
  2178. const CParaFormat * pPF0, //@parm PF for row to compare cells to
  2179. DWORD dwMaskCell, //@parm Mask for cell type to check
  2180. DWORD dwMaskCellAssoc) //@parm Mask for desired associated type
  2181. {
  2182. LONG cCell1 = pPF1->_bTabCount;
  2183. BOOL fCellsChanged = FALSE;
  2184. const CELLPARMS *prgCellParms1 = pPF1->GetCellParms();
  2185. for(LONG iCell1 = 0, dul1 = 0; iCell1 < cCell1; iCell1++)
  2186. {
  2187. LONG uCell1 = prgCellParms1[iCell1].uCell;
  2188. prgCellParms[iCell1] = prgCellParms1[iCell1];// Copy current cell parms
  2189. dul1 += GetCellWidth(uCell1);
  2190. if(uCell1 & dwMaskCell) // Need to check cell: see if
  2191. { // associated cell is compatible
  2192. BOOL fChangeCellParm = TRUE; // Default that it isn't
  2193. if(pPF0) // Compare to associated row
  2194. {
  2195. LONG cCell0 = pPF0->_bTabCount;
  2196. const CELLPARMS *prgCellParms0 = pPF0->GetCellParms();
  2197. fChangeCellParm = FALSE; // Maybe no change needed
  2198. for(LONG iCell0 = 0, dul0 = 0; iCell0 < cCell0; iCell0++)
  2199. { // Find cell above
  2200. LONG uCell0 = prgCellParms0[iCell0].uCell;
  2201. dul0 += GetCellWidth(uCell0);
  2202. if(dul0 == dul1) // Found cell above
  2203. {
  2204. // Should check both ends of cell to be sure it
  2205. // matches the present one
  2206. if(!(uCell0 & dwMaskCellAssoc))
  2207. fChangeCellParm = TRUE; // Need cell parm change
  2208. break;
  2209. }
  2210. }
  2211. }
  2212. if(fChangeCellParm)
  2213. {
  2214. uCell1 &= ~dwMaskCell;
  2215. if(dwMaskCell == fLowCell)
  2216. {
  2217. // REMARK: it would be possible to get the pPF for the para
  2218. // following this row and check to see if the current cell
  2219. // should be a top cell. For now we assume it is and fix it
  2220. // up by an additional pass in CheckMergeCells()
  2221. uCell1 |= fTopCell;
  2222. }
  2223. prgCellParms[iCell1].uCell = uCell1;
  2224. fCellsChanged = TRUE;
  2225. }
  2226. }
  2227. }
  2228. return fCellsChanged;
  2229. }
  2230. /*
  2231. * CTxtRange::SetCellParms(prgCellParms, cCell, fConvertLowCells, publdr)
  2232. *
  2233. * @mfunc
  2234. * Set cell parms for row pointed to by this range equal to *prgCellParms.
  2235. * Return with this range pointing just after the row end delimiter.
  2236. */
  2237. void CTxtRange::SetCellParms (
  2238. CELLPARMS * prgCellParms, //@parm New cell parms to use
  2239. LONG cCell, //@parm # cells
  2240. BOOL fConvertLowCells, //@parm TRUE if low cells are being converted
  2241. IUndoBuilder *publdr) //@parm UndoBuilder to receive antievents
  2242. {
  2243. LONG cpMost;
  2244. LONG Level = GetPF()->_bTableLevel;
  2245. CParaFormat PF;
  2246. Assert(_rpTX.IsAtTRD(STARTFIELD) && Level > 0);
  2247. PF._bTabCount = cCell;
  2248. PF._iTabs = GetTabsCache()->Cache((LONG *)prgCellParms, cCell*(CELL_EXTRA+1));
  2249. Move(2, TRUE); // Select table-row-start delimiter
  2250. SetParaFormat(&PF, publdr, PFM_TABSTOPS, PFM2_ALLOWTRDCHANGE);
  2251. _cch = 0;
  2252. FindRow(NULL, &cpMost, Level);
  2253. if(fConvertLowCells)
  2254. {
  2255. while(GetCp() < cpMost - 2) // Delete NOTACHARs
  2256. {
  2257. if(_rpTX.GetChar() == NOTACHAR && Level == GetPF()->_bTableLevel)
  2258. {
  2259. _cch = -1;
  2260. ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE, NULL);
  2261. cpMost--;
  2262. }
  2263. Move(1, FALSE);
  2264. }
  2265. }
  2266. Set(cpMost, 2); // Select table-row end delimiter
  2267. Assert(_rpTX.IsAfterTRD(ENDFIELD));
  2268. SetParaFormat(&PF, publdr, PFM_TABSTOPS, PFM2_ALLOWTRDCHANGE);
  2269. GetTabsCache()->Release(PF._iTabs);
  2270. _cch = 0;
  2271. }
  2272. /*
  2273. * CTxtRange::GetCharFormat(pCF, flags)
  2274. *
  2275. * @mfunc
  2276. * Set *pCF = CCharFormat for this range. If cbSize = sizeof(CHARFORMAT)
  2277. * only transfer CHARFORMAT data.
  2278. *
  2279. * @rdesc
  2280. * Mask of unchanged properties over range (for CHARFORMAT::dwMask)
  2281. *
  2282. * @devnote
  2283. * NINCH means No Input No CHange (a Microsoft Word term). Here used for
  2284. * properties that change during the range of cch characters. NINCHed
  2285. * properties in a Word-Font dialog have grayed boxes. They are indicated
  2286. * by zero values in their respective dwMask bit positions. Note that
  2287. * a blank at the end of the range does not participate in the NINCH
  2288. * test, i.e., it can have a different CCharFormat without zeroing the
  2289. * corresponding dwMask bits. This is done to be compatible with Word
  2290. * (see also CTxtSelection::SetCharFormat when _fWordSelMode is TRUE).
  2291. */
  2292. DWORD CTxtRange::GetCharFormat (
  2293. CCharFormat *pCF, //@parm CCharFormat to fill with results
  2294. DWORD flags) const //@parm flags
  2295. {
  2296. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetCharFormat");
  2297. _TEST_INVARIANT_
  2298. CTxtEdit * const ped = GetPed();
  2299. if(!_cch || !_rpCF.IsValid()) // IP or invalid CF
  2300. { // run ptr: use CF at
  2301. *pCF = *ped->GetCharFormat(_iFormat); // this text ptr
  2302. return CFM_ALL2;
  2303. }
  2304. LONG cpMin, cpMost; // Nondegenerate range:
  2305. LONG cch = GetRange(cpMin, cpMost); // need to scan
  2306. LONG cchChunk; // cch in CF run
  2307. DWORD dwMask = CFM_ALL2; // Initially all prop def'd
  2308. LONG iDirection; // Direction of scan
  2309. CFormatRunPtr rp(_rpCF); // Nondegenerate range
  2310. /*
  2311. * The code below reads character formatting the way Word does it,
  2312. * that is, by not including the formatting of the last character in the
  2313. * range if that character is a blank.
  2314. *
  2315. * See also the corresponding code in CTxtSelection::SetCharFormat().
  2316. */
  2317. if(cch > 1 && _fSel && (flags & SCF_USEUIRULES))// If more than one char,
  2318. { // don't include trailing
  2319. CTxtPtr tp(ped, cpMost - 1); // blank in NINCH test
  2320. if(tp.GetChar() == ' ')
  2321. { // Have trailing blank:
  2322. cch--; // one less char to check
  2323. if(_cch > 0) // Will scan backward, so
  2324. rp.Move(-1); // backup before blank
  2325. }
  2326. }
  2327. if(_cch < 0) // Setup direction and
  2328. { // initial cchChunk
  2329. iDirection = 1; // Scan forward
  2330. rp.AdjustForward();
  2331. cchChunk = rp.GetCchLeft(); // Chunk size for _rpCF
  2332. }
  2333. else
  2334. {
  2335. iDirection = -1; // Scan backward
  2336. rp.AdjustBackward(); // If at BOR, go to
  2337. cchChunk = rp.GetIch(); // previous EOR
  2338. }
  2339. *pCF = *ped->GetCharFormat(rp.GetFormat()); // Initialize *pCF to
  2340. // starting format
  2341. while(cchChunk < cch) // NINCH properties that
  2342. { // change over the range
  2343. cch -= cchChunk; // given by cch
  2344. if(!rp.ChgRun(iDirection)) // No more runs
  2345. break; // (cch too big)
  2346. cchChunk = rp.GetRun(0)->_cch;
  2347. const CCharFormat *pCFTemp = ped->GetCharFormat(rp.GetFormat());
  2348. dwMask &= ~pCFTemp->Delta(pCF, // NINCH properties that
  2349. flags & CFM2_CHARFORMAT); // changed, i.e., reset
  2350. } // corresponding bits
  2351. return dwMask;
  2352. }
  2353. /*
  2354. * CTxtRange::SetCharFormat(pCF, flags, publdr, dwMask, dwMask2)
  2355. *
  2356. * @mfunc
  2357. * apply CCharFormat *pCF to this range. If range is an insertion point,
  2358. * and (flags & SCF_WORD) != 0, then apply CCharFormat to word surrounding
  2359. * this insertion point
  2360. *
  2361. * @rdesc
  2362. * HRESULT = (successfully set whole range) ? NOERROR : S_FALSE
  2363. *
  2364. * @devnote
  2365. * SetParaFormat() is similar, but simpler, since it doesn't have to
  2366. * special case insertion-point ranges or worry about bullet character
  2367. * formatting, which is given by EOP formatting.
  2368. */
  2369. HRESULT CTxtRange::SetCharFormat (
  2370. const CCharFormat *pCF, //@parm CCharFormat to fill with results
  2371. DWORD flags, //@parm SCF_WORD OR SCF_IGNORESELAE
  2372. IUndoBuilder *publdr, //@parm Undo builder to use
  2373. DWORD dwMask, //@parm CHARFORMAT2 mask
  2374. DWORD dwMask2) //@parm Second mask
  2375. {
  2376. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::SetCharFormat");
  2377. LONG cch = -_cch; // Defaults for _cch <= 0
  2378. LONG cchBack = 0; // cch to back up for formatting
  2379. LONG cchFormat; // cch for formatting
  2380. CCharFormat CF; // Temporary CF
  2381. LONG cp;
  2382. LONG cpMin, cpMost;
  2383. LONG cpStart = 0;
  2384. LONG cpWordMin, cpWordMost;
  2385. BOOL fApplyToEOP = FALSE;
  2386. BOOL fProtected = FALSE;
  2387. HRESULT hr = NOERROR;
  2388. LONG iCF;
  2389. CTxtEdit * const ped = GetPed(); // defined and not style
  2390. ICharFormatCache * pf = GetCharFormatCache();
  2391. CFreezeDisplay fd(ped->_pdp);
  2392. _TEST_INVARIANT_
  2393. if(!Check_rpCF()) // Not rich
  2394. return NOERROR;
  2395. if(_cch > 0) // Active end at range end
  2396. {
  2397. cchBack = -_cch; // Setup to back up to
  2398. cch = _cch; // start of format area
  2399. }
  2400. else if(_cch < 0)
  2401. _rpCF.AdjustForward();
  2402. else if(!cch && (flags & (SCF_WORD | SCF_USEUIRULES)))
  2403. {
  2404. BOOL fCheckEOP = TRUE;
  2405. if(flags & SCF_WORD)
  2406. {
  2407. FindWord(&cpWordMin, &cpWordMost, FW_EXACT);
  2408. // If nearest word is within this range, calculate cchback and cch
  2409. // so that we can apply the given format to the word
  2410. if(cpWordMin < GetCp() && GetCp() < cpWordMost)
  2411. {
  2412. // RichEdit 1.0 made 1 final check: ensure word's format
  2413. // is constant w.r.t. the format passed in
  2414. CTxtRange rg(*this);
  2415. rg.Set(cpWordMin, cpWordMin - cpWordMost);
  2416. fProtected = rg.WriteAccessDenied();
  2417. if(!fProtected && (rg.GetCharFormat(&CF) & dwMask) == dwMask)
  2418. {
  2419. cchBack = cpWordMin - GetCp();
  2420. cch = cpWordMost - cpWordMin;
  2421. }
  2422. fCheckEOP = FALSE;
  2423. }
  2424. }
  2425. if(fCheckEOP && _rpTX.IsAtEOP() && !GetPF()->_wNumbering)
  2426. {
  2427. CTxtPtr tp(_rpTX);
  2428. cch = tp.AdvanceCRLF(FALSE);
  2429. _rpCF.AdjustForward(); // Go onto format EOP
  2430. fApplyToEOP = TRUE;
  2431. // Apply the characterset and face to EOP because EOP can be in any charset
  2432. dwMask2 |= CFM2_NOCHARSETCHECK;
  2433. }
  2434. }
  2435. cchFormat = cch;
  2436. BOOL fApplyStyle = pCF->fSetStyle(dwMask, dwMask2);
  2437. if(!cch) // Set degenerate-range (IP)
  2438. { // CF
  2439. LApplytoIP:
  2440. DWORD dwMsk = dwMask;
  2441. dwMask2 |= CFM2_NOCHARSETCHECK;
  2442. CF = *ped->GetCharFormat(_iFormat); // Copy current CF at IP to CF
  2443. if ((CF._dwEffects & CFE_LINK) && // Don't allow our URL
  2444. ped->GetDetectURL() && !ped->IsStreaming()) // formatting to be changed
  2445. {
  2446. dwMsk &= ~CFM_LINK;
  2447. }
  2448. if(fApplyStyle)
  2449. CF.ApplyDefaultStyle(pCF->_sStyle);
  2450. hr = CF.Apply(pCF, dwMsk, dwMask2); // Apply *pCF
  2451. if(hr != NOERROR) // Cache result if new
  2452. return hr;
  2453. hr = pf->Cache(&CF, &iCF); // In any case, get iCF
  2454. if(hr != NOERROR) // (which AddRef's it)
  2455. return hr;
  2456. #ifndef NOLINESERVICES
  2457. if (g_pols)
  2458. g_pols->DestroyLine(NULL);
  2459. #endif
  2460. pf->Release(_iFormat);
  2461. _iFormat = iCF;
  2462. if(fProtected) // Signal to beep if UI
  2463. hr = S_FALSE;
  2464. }
  2465. else // Set nondegenerate-range CF
  2466. { // Get start of affected area
  2467. CNotifyMgr *pnm = NULL;
  2468. if (!(flags & SCF_IGNORENOTIFY))
  2469. {
  2470. pnm = ped->GetNotifyMgr(); // Get the notification mgr
  2471. if(pnm)
  2472. {
  2473. cpStart = GetCp() + cchBack; // Bulletting may move
  2474. // affected area back if
  2475. if(GetPF()->_wNumbering) // formatting hits EOP that
  2476. { // affects bullet at BOP
  2477. FindParagraph(&cpMin, &cpMost);
  2478. if(cpMost <= GetCpMost())
  2479. cpStart = cpMin;
  2480. }
  2481. pnm->NotifyPreReplaceRange(this,// Notify interested parties of
  2482. CP_INFINITE, 0, 0, cpStart, // the impending update
  2483. cpStart + cchFormat);
  2484. }
  2485. }
  2486. // Move _rpCF in front of the changes to allow easy rebinding
  2487. _rpCF.Move(cchBack); // Back up to formatting start
  2488. CFormatRunPtr rp(_rpCF); // Clone _rpCF to walk range
  2489. cp = GetCp() + cchBack;
  2490. if(publdr)
  2491. {
  2492. LONG cchBackup = 0, cchAdvance = 0;
  2493. if (ped->IsBiDi())
  2494. {
  2495. CRchTxtPtr rtp(*this);
  2496. if(cchBack)
  2497. {
  2498. rtp._rpPF.Move(cchBack); // Move _rpPF and _rpTX back
  2499. rtp._rpTX.Move(cchBack); // (_rpCF moved back above)
  2500. }
  2501. cchBackup = rtp.ExpandRangeFormatting(cch, 0, cchAdvance);
  2502. Assert(cchBackup <= 0);
  2503. }
  2504. rp.Move(cchBackup); // Move rp back
  2505. IAntiEvent *pae = gAEDispenser.CreateReplaceFormattingAE(ped,
  2506. cp + cchBackup, rp, cch - cchBackup + cchAdvance,
  2507. pf, CharFormat);
  2508. rp.Move(-cchBackup); // Restore rp
  2509. if(pae)
  2510. publdr->AddAntiEvent(pae);
  2511. }
  2512. // Following Word, we translate runs for 8-bit charsets to/from
  2513. // SYMBOL_CHARSET
  2514. LONG cchLeft;
  2515. LONG cchRun;
  2516. LONG cchSkip = 0;
  2517. LONG cchSkipHidden = 0;
  2518. LONG cchTrans;
  2519. QWORD qwFontSig = 0;
  2520. DWORD dwMaskSave = dwMask;
  2521. DWORD dwMask2Save = dwMask2;
  2522. BOOL fBiDiCharRep = IsBiDiCharRep(pCF->_iCharRep);
  2523. BOOL fFECharRep = IsFECharRep(pCF->_iCharRep);
  2524. BOOL fFontCheck = (dwMask2 & CFM2_MATCHFONT);
  2525. BOOL fHidden = dwMask & CFM_HIDDEN & pCF->_dwEffects;
  2526. BOOL fInRange;
  2527. BOOL fSymbolCharRep = IsSymbolOrOEMCharRep(pCF->_iCharRep);
  2528. UINT iCharRep = 0;
  2529. CTxtPtr tp(_rpTX);
  2530. if(fFontCheck && !fSymbolCharRep)
  2531. {
  2532. GetFontSignatureFromFace(pCF->_iFont, &qwFontSig);
  2533. if(!qwFontSig)
  2534. qwFontSig = FontSigFromCharRep(pCF->_iCharRep);
  2535. }
  2536. if (ped->_fIMEInProgress && !(dwMask2 & CFM2_SCRIPT))
  2537. dwMask2 |= CFM2_NOCHARSETCHECK; // Don't check charset or it will display garbage
  2538. while(cch > 0 && rp.IsValid())
  2539. {
  2540. CF = *ped->GetCharFormat(rp.GetFormat());// Copy rp CF to temp CF
  2541. if(fApplyStyle)
  2542. CF.ApplyDefaultStyle(pCF->_sStyle);
  2543. cchRun = cch;
  2544. // Don't apply CFE_HIDDEN to table row delimiters or CELL
  2545. if(fHidden)
  2546. {
  2547. // For better perf, this could be done as a CTxtPtr function
  2548. // that has a prototype like CTxtPtr::TranslateRange().
  2549. // REMARK: similar run breaking should be incorporated into
  2550. // CTxtRange::ReplaceRange in case table structure elements
  2551. // are inserted into a hidden run.
  2552. if(cchSkipHidden)
  2553. {
  2554. cchRun = cchSkipHidden; // Bypass table structure chars
  2555. cchSkipHidden = 0; // found on preceding pass
  2556. dwMask &= ~CFM_HIDDEN;
  2557. }
  2558. else // Check for table structure
  2559. { // characters
  2560. WCHAR ch;
  2561. tp.SetCp(cp);
  2562. cchLeft = rp.GetCchLeft();
  2563. for(LONG i = 0; i < cchLeft;)
  2564. {
  2565. ch = tp.GetChar();
  2566. if(ch == CELL)
  2567. {
  2568. cchSkipHidden = 1;
  2569. cchRun = i;
  2570. break;
  2571. }
  2572. if(IN_RANGE(STARTFIELD, ch, ENDFIELD) &&
  2573. tp.IsAtTRD(0))
  2574. {
  2575. cchSkipHidden = 2;
  2576. cchRun = i;
  2577. break;
  2578. }
  2579. LONG cch = tp.AdvanceCRLF(TRUE);
  2580. if(IsASCIIEOP(ch)) // Don't hide EOP if
  2581. { // followed by a TRD start
  2582. if(tp.IsAtTRD(STARTFIELD))
  2583. {
  2584. cchSkipHidden = cch + 2;
  2585. cchRun = i;
  2586. break;
  2587. }
  2588. }
  2589. i += cch;
  2590. }
  2591. if(!cchRun)
  2592. {
  2593. AssertSz(cchSkipHidden, "CTxtRange::SetCharFormat: cchRun = 0");
  2594. continue;
  2595. }
  2596. }
  2597. }
  2598. if (CF._dwEffects & CFE_RUNISDBCS)
  2599. {
  2600. // Don't allow charset/face name change for DBCS run
  2601. // causing these are garbage characters
  2602. dwMask &= ~(CFM_CHARSET | CFM_FACE);
  2603. }
  2604. else if(fFontCheck) // Only apply font if it
  2605. { // supports run's charset
  2606. cchLeft = rp.GetCchLeft();
  2607. cchRun = min(cchRun, cchLeft); // Translate up to end of
  2608. cchRun = min(cch, cchRun); // current CF run
  2609. dwMask &= ~CFM_CHARSET; // Default no charset change
  2610. if(cchSkip)
  2611. { // Skip cchSkip chars (were
  2612. cchRun = cchSkip; // not translatable with
  2613. cchSkip = 0; // CodePage)
  2614. }
  2615. else if(fSymbolCharRep ^ IsSymbolOrOEMCharRep(CF._iCharRep))
  2616. { // SYMBOL to/from nonSYMBOL
  2617. iCharRep = fSymbolCharRep ? CF._iCharRep : pCF->_iCharRep;
  2618. if(!Is8BitCharRep(iCharRep))
  2619. goto DoASCII;
  2620. dwMask |= CFM_CHARSET; // Need to change charset
  2621. if(fSymbolCharRep)
  2622. CF._iCharRepSave = iCharRep;
  2623. else if(Is8BitCharRep(CF._iCharRepSave))
  2624. {
  2625. iCharRep = CF._iCharRep = CF._iCharRepSave;
  2626. dwMask &= ~CFM_CHARSET; // Already changed
  2627. }
  2628. tp.SetCp(cp); // Point tp at start of run
  2629. cchTrans = tp.TranslateRange(cchRun, CodePageFromCharRep(iCharRep),
  2630. fSymbolCharRep, publdr /*, cchSkip */);
  2631. if(cchTrans < cchRun) // Ran into char not in
  2632. { // CodePage, so set up to
  2633. cchSkip = 1; // skip the char
  2634. cchRun = cchTrans; // FUTURE: use cchSkip out
  2635. if(!cchRun) // parm from TranslateRange
  2636. continue; // instead of skipping 1 char
  2637. } // at a time
  2638. }
  2639. else if(!fSymbolCharRep)
  2640. {
  2641. DoASCII: tp.SetCp(cp); // Point tp at start of run
  2642. fInRange = tp.GetChar() < 0x80;
  2643. if (!fBiDiCharRep && !IsBiDiCharRep(CF._iCharRep) &&
  2644. fInRange &&
  2645. ((qwFontSig & FASCII) == FASCII || fFECharRep || fSymbolCharRep))
  2646. {
  2647. // ASCII text and new font supports ASCII
  2648. // -FUTURE-
  2649. // We exclude BiDi here. We cannot allow applying BiDi
  2650. // charset to non-BiDi run or vice versa. This because
  2651. // we use charset for BiDi reordering. In the future,
  2652. // we should use something more elegant than charset.
  2653. if (!(FontSigFromCharRep(CF._iCharRep) & ~FASCII & qwFontSig))
  2654. // New font doesn't support underlying charset,
  2655. // apply new charset to ASCII
  2656. dwMask |= CFM_CHARSET;
  2657. }
  2658. else if (!(FontSigFromCharRep(CF._iCharRep) & ~FASCII & qwFontSig) &&
  2659. CF._iCharRep != DEFAULT_INDEX &&
  2660. !IN_RANGE(JPN2_INDEX, CF._iCharRep, CHT2_INDEX))
  2661. // New font doesn't support underlying charset: suppress
  2662. // both new charset and facename except for default
  2663. // index and surrogate pairs, for which we still know
  2664. // too little to be sure
  2665. dwMask &= ~CFM_FACE;
  2666. cchRun -= tp.MoveWhile(cchRun, 0, 0x7F, fInRange);
  2667. }
  2668. }
  2669. hr = CF.Apply(pCF, dwMask, dwMask2);// Apply *pCF
  2670. if(hr != NOERROR)
  2671. return hr;
  2672. dwMask = dwMaskSave; // Restore mask in case
  2673. dwMask2 = dwMask2Save; // changed above
  2674. hr = pf->Cache(&CF, &iCF); // Cache result if new, In any
  2675. if(hr != NOERROR) // cause, use format index iCF
  2676. break;
  2677. #ifndef NOLINESERVICES
  2678. if (g_pols)
  2679. g_pols->DestroyLine(NULL);
  2680. #endif
  2681. // Set format for this run. Proper levels will be generated
  2682. // later by BiDi FSM.
  2683. cchLeft = rp.SetFormat(iCF, cchRun, pf);
  2684. if(cchLeft < cchRun) // Didn't format all of cchRun:
  2685. cchSkip = cchSkipHidden = 0; // Turn off cchSkip, since need
  2686. cchRun = cchLeft; // to format rest of cchRun 1st
  2687. pf->Release(iCF); // Release count from Cache above
  2688. // rp.SetFormat AddRef's as needed
  2689. if(cchRun == CP_INFINITE)
  2690. {
  2691. ped->GetCallMgr()->SetOutOfMemory();
  2692. break;
  2693. }
  2694. cp += cchRun;
  2695. cch -= cchRun;
  2696. }
  2697. _rpCF.AdjustBackward(); // Expand scope for merging
  2698. rp.AdjustForward(); // runs
  2699. rp.MergeRuns(_rpCF._iRun, pf); // Merge adjacent runs that
  2700. // have the same format
  2701. if(cchBack) // Move _rpCF back to where it
  2702. _rpCF.Move(-cchBack); // was
  2703. else // Active end at range start:
  2704. _rpCF.AdjustForward(); // don't leave at EOR
  2705. if(pnm)
  2706. {
  2707. pnm->NotifyPostReplaceRange(this, // Notify interested parties
  2708. CP_INFINITE, 0, 0, cpStart, // of the change.
  2709. cpStart + cchFormat - cch);
  2710. }
  2711. if(publdr && !(flags & SCF_IGNORESELAE))
  2712. {
  2713. HandleSelectionAEInfo(ped, publdr, GetCp(), _cch, GetCp(), _cch,
  2714. SELAE_FORCEREPLACE);
  2715. }
  2716. if(!_cch) // In case IP with ApplyToWord
  2717. {
  2718. if(fApplyToEOP) // Formatting EOP only
  2719. goto LApplytoIP;
  2720. Update_iFormat(-1);
  2721. }
  2722. if (ped->IsRich())
  2723. ped->GetCallMgr()->SetChangeEvent(CN_GENERIC);
  2724. }
  2725. if(_fSel && ped->IsRich() && !ped->_f10Mode /*bug fix #5211*/)
  2726. ped->GetCallMgr()->SetSelectionChanged();
  2727. AssertSz(GetCp() == (cp = _rpCF.CalculateCp()),
  2728. "RTR::SetCharFormat(): incorrect format-run ptr");
  2729. if (!(dwMask2 & (CFM2_SCRIPT | CFM2_HOLDITEMIZE)) && cchFormat && hr == NOERROR && !cch)
  2730. {
  2731. // A non-degenerate range not coming from ItemizeRuns
  2732. // It's faster to make a copy pointer since we dont need to worry about fExtend.
  2733. CRchTxtPtr rtp(*this);
  2734. rtp.Move(cchBack + cchFormat);
  2735. rtp.ItemizeReplaceRange(cchFormat, 0, publdr);
  2736. return hr;
  2737. }
  2738. return (hr == NOERROR && cch) ? S_FALSE : hr;
  2739. }
  2740. /*
  2741. * CTxtRange::GetParaFormat(pPF)
  2742. *
  2743. * @mfunc
  2744. * return CParaFormat for this text range. If no PF runs are allocated,
  2745. * then return default CParaFormat
  2746. *
  2747. * @rdesc
  2748. * Mask of defined properties: 1 bit means corresponding property is
  2749. * defined and constant throughout range. 0 bit means it isn't constant
  2750. * throughout range. Note that PARAFORMAT has fewer relevant bits
  2751. * (PFM_ALL vs PFM_ALL2)
  2752. */
  2753. DWORD CTxtRange::GetParaFormat (
  2754. CParaFormat *pPF, //@parm ptr to CParaFormat to be filled
  2755. DWORD dwMask2) const //@parm Mask specifying PFM2_PARAFORMAT
  2756. {
  2757. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::GetParaFormat");
  2758. CTxtEdit * const ped = GetPed();
  2759. _TEST_INVARIANT_
  2760. DWORD dwMask = dwMask2 & PFM2_PARAFORMAT // Default presence of
  2761. ? PFM_ALL : PFM_ALL2; // all properties
  2762. CFormatRunPtr rp(_rpPF);
  2763. LONG cch = -_cch;
  2764. if(cch < 0) // At end of range:
  2765. { // go to start of range
  2766. rp.Move(cch);
  2767. cch = -cch; // Count with cch > 0
  2768. }
  2769. *pPF = *ped->GetParaFormat(rp.GetFormat()); // Initialize *pPF to
  2770. // starting paraformat
  2771. if(!cch || !rp.IsValid()) // No cch or invalid PF
  2772. return dwMask; // run ptr: use PF at
  2773. // this text ptr
  2774. LONG cchChunk = rp.GetCchLeft(); // Chunk size for rp
  2775. while(cchChunk < cch) // NINCH properties that
  2776. { // change over the range
  2777. cch -= cchChunk; // given by cch
  2778. if(!rp.NextRun()) // Go to next run // No more runs
  2779. break; // (cch too big)
  2780. cchChunk = rp.GetCchLeft();
  2781. dwMask &= ~ped->GetParaFormat(rp.GetFormat())// NINCH properties that
  2782. ->Delta(pPF, dwMask2 & PFM2_PARAFORMAT);// changed, i.e., reset
  2783. } // corresponding bits
  2784. return dwMask;
  2785. }
  2786. /*
  2787. * CTxtRange::SetParaFormat(pPF, publdr, dwMask, dwMask2)
  2788. *
  2789. * @mfunc
  2790. * apply CParaFormat *pPF to this range.
  2791. *
  2792. * @rdesc
  2793. * if successfully set whole range, return NOERROR, otherwise
  2794. * return error code or S_FALSE.
  2795. */
  2796. HRESULT CTxtRange::SetParaFormat (
  2797. const CParaFormat* pPF, //@parm CParaFormat to apply to this range
  2798. IUndoBuilder *publdr, //@parm Undo context for this operation
  2799. DWORD dwMask, //@parm Mask to use
  2800. DWORD dwMask2) //@parm Second mask
  2801. {
  2802. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::SetParaFormat");
  2803. LONG cch; // Length of text to format
  2804. LONG cchBack; // cch to back up for formatting
  2805. LONG cp;
  2806. LONG cpMin, cpMost; // Limits of text to format
  2807. LONG delta;
  2808. HRESULT hr = NOERROR;
  2809. LONG iPF = 0; // Index to a CParaFormat
  2810. CTxtEdit * const ped = GetPed();
  2811. CParaFormat PF; // Temporary CParaFormat
  2812. IParaFormatCache * pf = GetParaFormatCache();// Format cache ptr for Cache,
  2813. // AddRefFormat, ReleaseFormat
  2814. CBiDiLevel* pLevel;
  2815. CBiDiLevel lvRTL = {1, 0};
  2816. CBiDiLevel lvLTR = {0, 0};
  2817. CFreezeDisplay fd(ped->_pdp);
  2818. _TEST_INVARIANT_
  2819. if(!Check_rpPF())
  2820. return E_FAIL;
  2821. FindParagraph(&cpMin, &cpMost); // Get limits of text to
  2822. cch = cpMost - cpMin; // format, namely closest
  2823. CNotifyMgr *pnm = ped->GetNotifyMgr();
  2824. if(pnm)
  2825. {
  2826. pnm->NotifyPreReplaceRange(this, // Notify interested parties of
  2827. CP_INFINITE, 0, 0, cpMin, cpMost); // the impending update
  2828. }
  2829. cchBack = cpMin - GetCp();
  2830. // Move _rpPF in front of the changes to allow easy rebinding
  2831. _rpPF.Move(cchBack); // Back up to formatting start
  2832. CFormatRunPtr rp(_rpPF); // Clone _rpPF to walk range
  2833. if(publdr)
  2834. {
  2835. IAntiEvent *pae = gAEDispenser.CreateReplaceFormattingAE(ped,
  2836. cpMin, rp, cch, pf, ParaFormat);
  2837. if(pae)
  2838. publdr->AddAntiEvent(pae);
  2839. }
  2840. BOOL fLevelChanged = FALSE;
  2841. const CParaFormat *pPFRun = ped->GetParaFormat(rp.GetFormat());
  2842. LONG TableLevel = pPFRun->_bTableLevel;
  2843. if(pPFRun->IsTableRowDelimiter())
  2844. TableLevel--;
  2845. CParaFormat PFStyle;
  2846. if(ped->HandleStyle(&PFStyle, pPF, dwMask, dwMask2) == NOERROR)
  2847. {
  2848. pPF = &PFStyle;
  2849. dwMask = PFM_ALL2 ^ PFM_TABLE;
  2850. }
  2851. DWORD dwMaskSave = dwMask;
  2852. do
  2853. {
  2854. dwMask = dwMaskSave;
  2855. pPFRun = ped->GetParaFormat(rp.GetFormat());// Get current PF
  2856. LONG TableLevelRun = pPFRun->_bTableLevel;
  2857. WORD wEffectsRun = pPFRun->_wEffects; // Save effects to avoid using
  2858. // pPFRun, which may be invalid
  2859. if(pPFRun->IsTableRowDelimiter()) // after PF.Apply()
  2860. {
  2861. TableLevelRun--;
  2862. if(!(dwMask2 & PFM2_ALLOWTRDCHANGE))
  2863. {
  2864. dwMask &= PFM_STARTINDENT | PFM_ALIGNMENT | PFM_OFFSETINDENT |
  2865. PFM_RIGHTINDENT | PFM_RTLPARA;
  2866. }
  2867. }
  2868. if(TableLevelRun > TableLevel)
  2869. {
  2870. cch -= rp.GetCchLeft(); // Skip run
  2871. rp.NextRun();
  2872. continue;
  2873. }
  2874. PF = *pPFRun;
  2875. hr = PF.Apply(pPF, dwMask, dwMask2); // Apply *pPF
  2876. if(hr != NOERROR) // (Probably E_INVALIDARG)
  2877. break; // Cache result if new; in any
  2878. hr = pf->Cache(&PF, &iPF); // case, get format index iPF
  2879. if(hr != NOERROR) // Can't necessarily return
  2880. break; // error, since may need
  2881. if(!fLevelChanged)
  2882. fLevelChanged = (wEffectsRun ^ PF._wEffects) & PFE_RTLPARA;
  2883. pLevel = PF.IsRtl() ? &lvRTL : &lvLTR;
  2884. delta = rp.SetFormat(iPF, cch, pf, pLevel); // Set format for this run
  2885. pf->Release(iPF); // rp.SetFormat AddRefs as needed
  2886. if(delta == CP_INFINITE)
  2887. {
  2888. ped->GetCallMgr()->SetOutOfMemory();
  2889. break;
  2890. }
  2891. cch -= delta;
  2892. } while (cch > 0) ;
  2893. _rpPF.AdjustBackward(); // If at BOR, go to prev EOR
  2894. rp.MergeRuns(_rpPF._iRun, pf); // Merge any adjacent runs
  2895. // that have the same format
  2896. if(cchBack) // Move _rpPF back to where it
  2897. _rpPF.Move(-cchBack); // was
  2898. else // Active end at range start:
  2899. _rpPF.AdjustForward(); // don't leave at EOR
  2900. if(pnm)
  2901. {
  2902. pnm->NotifyPostReplaceRange(this, // Notify interested parties of
  2903. CP_INFINITE, 0, 0, cpMin, cpMost); // the update
  2904. }
  2905. if(publdr)
  2906. {
  2907. // Paraformatting works a bit differently, it just remembers the
  2908. // current selection. Cast selection to range to avoid including
  2909. // _select.h; we only need range methods.
  2910. CTxtRange *psel = (CTxtRange *)GetPed()->GetSel();
  2911. if(psel)
  2912. {
  2913. cp = psel->GetCp();
  2914. HandleSelectionAEInfo(ped, publdr, cp, psel->GetCch(),
  2915. cp, psel->GetCch(), SELAE_FORCEREPLACE);
  2916. }
  2917. }
  2918. ped->GetCallMgr()->SetChangeEvent(CN_GENERIC);
  2919. AssertSz(GetCp() == (cp = _rpPF.CalculateCp()),
  2920. "RTR::SetParaFormat(): incorrect format-run ptr");
  2921. if (fLevelChanged && cpMost > cpMin)
  2922. {
  2923. ped->OrCharFlags(FRTL, publdr);
  2924. // make sure the CF is valid
  2925. Check_rpCF();
  2926. CTxtRange rg(*this);
  2927. if (publdr)
  2928. {
  2929. // Create anti-events to keep BiDi level of paragraphs in need
  2930. ICharFormatCache* pcfc = GetCharFormatCache();
  2931. CFormatRunPtr rp(_rpCF);
  2932. rp.Move(cpMin - _rpTX.GetCp());
  2933. IAntiEvent *pae = gAEDispenser.CreateReplaceFormattingAE (ped,
  2934. cpMin, rp, cpMost - cpMin, pcfc, CharFormat);
  2935. if (pae)
  2936. publdr->AddAntiEvent(pae);
  2937. }
  2938. rg.Set(cpMost, cpMost - cpMin);
  2939. rg.ItemizeRuns (publdr);
  2940. }
  2941. return (hr == NOERROR && cch) ? S_FALSE : hr;
  2942. }
  2943. /*
  2944. * CTxtRange::SetParaStyle(pPF, publdr, dwMask)
  2945. *
  2946. * @mfunc
  2947. * apply CParaFormat *pPF using the style pPF->sStyle to this range.
  2948. *
  2949. * @rdesc
  2950. * if successfully set whole range, return NOERROR, otherwise
  2951. * return error code or S_FALSE.
  2952. *
  2953. * @comm
  2954. * If pPF->dwMask & PFM_STYLE is nonzero, this range is expanded to
  2955. * complete paragraphs. If it's zero, this call just passes control
  2956. * to CTxtRange::SetParaStyle().
  2957. */
  2958. HRESULT CTxtRange::SetParaStyle (
  2959. const CParaFormat* pPF, //@parm CParaFormat to apply to this range
  2960. IUndoBuilder *publdr, //@parm Undo context for this operation
  2961. DWORD dwMask) //@parm Mask to use
  2962. {
  2963. LONG cchSave = _cch; // Save range cp and cch in case
  2964. LONG cpSave = GetCp(); // para expand needed
  2965. HRESULT hr;
  2966. if(publdr)
  2967. publdr->StopGroupTyping();
  2968. if(pPF->fSetStyle(dwMask, 0))
  2969. {
  2970. CCharFormat CF; // Need to apply associated CF
  2971. LONG cpMin, cpMost;
  2972. DWORD dwMaskCF = CFM_STYLE;
  2973. Expander(tomParagraph, TRUE, NULL, &cpMin, &cpMost);
  2974. CF._sStyle = pPF->_sStyle;
  2975. if(CF._sStyle == STYLE_NORMAL)
  2976. {
  2977. CF = *GetPed()->GetCharFormat(-1);
  2978. dwMaskCF = CFM_ALL2;
  2979. }
  2980. hr = SetCharFormat(&CF, 0, publdr, dwMaskCF, 0);
  2981. if(hr != NOERROR)
  2982. return hr;
  2983. }
  2984. hr = SetParaFormat(pPF, publdr, dwMask, 0);
  2985. Set(cpSave, cchSave); // Restore this range in case expanded
  2986. return hr;
  2987. }
  2988. /*
  2989. * CTxtRange::Update_iFormat(iFmtDefault)
  2990. *
  2991. * @mfunc
  2992. * update _iFormat to CCharFormat at current active end
  2993. *
  2994. * @devnote
  2995. * _iFormat is only used when the range is degenerate
  2996. *
  2997. * The Word 95 UI specifies that the *previous* format should
  2998. * be used if we're in at an ambiguous cp (i.e. where a formatting
  2999. * change occurs) _unless_ the previous character is an EOP
  3000. * marker _or_ if the previous character is protected.
  3001. */
  3002. void CTxtRange::Update_iFormat (
  3003. LONG iFmtDefault) //@parm Format index to use if _rpCF isn't valid
  3004. {
  3005. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Update_iFormat");
  3006. DWORD dwEffects;
  3007. LONG ifmt, iFormatForward;
  3008. const CCharFormat *pCF, *pCFForward;
  3009. if(_cch)
  3010. return;
  3011. _fSelHasEOP = FALSE; // Empty ranges don't contain
  3012. _fSelExpandCell = FALSE; // anything, incl EOPs/cells
  3013. _nSelExpandLevel = 0;
  3014. if(_fDontUpdateFmt) // _iFormat is only used
  3015. return; // for degenerate ranges
  3016. if(_rpCF.IsValid() && iFmtDefault == -1)
  3017. {
  3018. // Get forward info before possibly adjusting backward
  3019. _rpCF.AdjustForward();
  3020. ifmt = iFormatForward = _rpCF.GetFormat();
  3021. pCF = pCFForward = GetPed()->GetCharFormat(ifmt);
  3022. if(!_rpTX.IsAfterEOP())
  3023. {
  3024. _rpCF.AdjustBackward(); // Adjust backward
  3025. ifmt = _rpCF.GetFormat();
  3026. pCF = GetPed()->GetCharFormat(ifmt);
  3027. }
  3028. dwEffects = pCF->_dwEffects;
  3029. if (!(GetPed()->_fIMEInProgress)) // Dont change format during IME
  3030. {
  3031. if(!_rpTX.GetCp() && (dwEffects & CFE_RUNISDBCS))
  3032. {
  3033. // If at beginning of document, and text is protected, just use
  3034. // default format.
  3035. ifmt = iFmtDefault;
  3036. }
  3037. else if(dwEffects & (CFE_PROTECTED | CFE_LINK | CFE_HIDDEN | CFE_RUNISDBCS))
  3038. {
  3039. // If range is protected, hidden, or a friendly hyperlink,
  3040. // pick forward format
  3041. ifmt = iFormatForward;
  3042. }
  3043. else if(ifmt != iFormatForward && _fMoveBack &&
  3044. IsRTLCharRep(pCF->_iCharRep) != IsRTLCharRep(pCFForward->_iCharRep))
  3045. {
  3046. ifmt = iFormatForward;
  3047. }
  3048. }
  3049. iFmtDefault = ifmt;
  3050. }
  3051. // Don't allow _iFormat to include CFE_HIDDEN attributes
  3052. // unless they're the default
  3053. if(iFmtDefault != -1)
  3054. {
  3055. pCF = GetPed()->GetCharFormat(iFmtDefault);
  3056. if(pCF->_dwEffects & (CFE_HIDDEN | CFE_LINKPROTECTED))
  3057. {
  3058. if(!(pCF->_dwEffects & CFE_LINKPROTECTED))
  3059. {
  3060. CCharFormat CF = *pCF;
  3061. CF._dwEffects &= ~CFE_HIDDEN;
  3062. Assert(_cch == 0); // Must be an insertion point
  3063. SetCharFormat(&CF, FALSE, NULL, CFM_ALL2, 0);
  3064. return;
  3065. }
  3066. if(!(pCF->_dwEffects & CFE_HIDDEN) && !(GetCF()->_dwEffects & CFE_LINK))
  3067. iFmtDefault = _rpCF.GetFormat();// Don't extend friendly link names
  3068. }
  3069. }
  3070. Set_iCF(iFmtDefault);
  3071. }
  3072. /*
  3073. * CTxtRange::GetCharRepMask(fUseDocFormat)
  3074. *
  3075. * @mfunc
  3076. * Get this range's char repertoire mask corresponding to _iFormat.
  3077. * If fUseDocFormat is TRUE, then use -1 instead of _iFormat.
  3078. *
  3079. * @rdesc
  3080. * char repertoire mask for range or default document
  3081. */
  3082. QWORD CTxtRange::GetCharRepMask(
  3083. BOOL fUseDocFormat)
  3084. {
  3085. LONG iFormat = fUseDocFormat ? -1 : GetiFormat();
  3086. QWORD qwMask = FontSigFromCharRep((GetPed()->GetCharFormat(iFormat))->_iCharRep);
  3087. qwMask &= ~FOTHER;
  3088. if(qwMask & FSYMBOL)
  3089. return qwMask;
  3090. // For now, Indic fonts match only ASCII digits
  3091. qwMask |= FBELOWX40;
  3092. if(!(qwMask & FINDIC))
  3093. qwMask |= FASCII; // FASCIIUPR+FBELOWX40
  3094. if(qwMask & FLATIN)
  3095. qwMask |= FCOMBINING;
  3096. return qwMask;
  3097. }
  3098. /*
  3099. * CTxtRange::GetiFormat()
  3100. *
  3101. * @mfunc
  3102. * Return (!_cch || _fUseiFormat) ? _iFormat : iFormat at cpMin
  3103. *
  3104. * @rdesc
  3105. * iFormat at cpMin if nondegenerate and !_fUseiFormat; else _iFormat
  3106. *
  3107. * @devnote
  3108. * This routine doesn't AddRef iFormat, so it shouldn't be used if
  3109. * it needs to be valid after character formatting has changed, e.g.,
  3110. * by ReplaceRange or SetCharFormat or SetParaStyle
  3111. */
  3112. LONG CTxtRange::GetiFormat() const
  3113. {
  3114. if(!_cch || _fUseiFormat)
  3115. return _iFormat;
  3116. if(_cch > 0)
  3117. {
  3118. CFormatRunPtr rp(_rpCF);
  3119. rp.Move(-_cch);
  3120. return rp.GetFormat();
  3121. }
  3122. return _rpCF.GetFormat();
  3123. }
  3124. /*
  3125. * CTxtRange::Get_iCF()
  3126. *
  3127. * @mfunc
  3128. * Get this range's _iFormat (AddRef'ing, of course)
  3129. *
  3130. * @devnote
  3131. * Get_iCF() is used by the RTF reader
  3132. */
  3133. LONG CTxtRange::Get_iCF ()
  3134. {
  3135. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Get_iCF");
  3136. GetCharFormatCache()->AddRef(_iFormat);
  3137. return _iFormat;
  3138. }
  3139. /*
  3140. * CTxtRange::Set_iCF(iFormat)
  3141. *
  3142. * @mfunc
  3143. * Set range's _iFormat to iFormat, AddRefing and Releasing as required.
  3144. *
  3145. * @rdesc
  3146. * TRUE if _iFormat changed
  3147. */
  3148. BOOL CTxtRange::Set_iCF (
  3149. LONG iFormat) //@parm Index of char format to use
  3150. {
  3151. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::Set_iCF");
  3152. if(iFormat == _iFormat)
  3153. return FALSE;
  3154. ICharFormatCache *pCFC = GetCharFormatCache();
  3155. pCFC->AddRef(iFormat);
  3156. pCFC->Release(_iFormat); // Note: _iFormat = -1 doesn't
  3157. _iFormat = iFormat; // get AddRef'd or Release'd
  3158. AssertSz(GetCF(), "CTxtRange::Set_iCF: illegal format");
  3159. return TRUE;
  3160. }
  3161. /*
  3162. * CTxtRange::IsHidden()
  3163. *
  3164. * @mfunc
  3165. * Return TRUE iff range end is hidden. If nondegenerate, check
  3166. * char at appropriate range end
  3167. *
  3168. * @rdesc
  3169. * TRUE if range active end is hidden
  3170. */
  3171. BOOL CTxtRange::IsHidden()
  3172. {
  3173. if(_cch > 0)
  3174. _rpCF.AdjustBackward();
  3175. BOOL fHidden = CRchTxtPtr::IsHidden();
  3176. if(_cch > 0)
  3177. _rpCF.AdjustForward();
  3178. return fHidden;
  3179. }
  3180. #ifndef NOCOMPLEXSCRIPTS
  3181. /*
  3182. * CTxtRange::BiDiLevelFromFSM(pFSM)
  3183. *
  3184. * @mfunc
  3185. * Run BiDi FSM to generate proper embedding level for runs
  3186. *
  3187. * @rdesc
  3188. * HRESULT
  3189. */
  3190. HRESULT CTxtRange::BiDiLevelFromFSM (
  3191. const CBiDiFSM* pFSM) // in: ptr to FSM
  3192. {
  3193. AssertSz(pFSM && _rpCF.IsValid(), "Not enough information to run BiDi FSM");
  3194. LONG cpMin, cpMost, cp, cchLeft;
  3195. LONG ich, cRunsStart, cRuns = 0;
  3196. HRESULT hr = S_OK;
  3197. GetRange(cpMin, cpMost);
  3198. AssertSz (cpMost - cpMin > 0, "FSM: Invalid range");
  3199. CRchTxtPtr rtp(*this);
  3200. rtp.Move(cpMin - rtp.GetCp()); // initiate position to cpMin
  3201. CFormatRunPtr rpPF(rtp._rpPF); // pointer to current paragraph
  3202. cchLeft = cpMost - cpMin;
  3203. cp = cpMin;
  3204. while (cchLeft > 0 && SUCCEEDED(hr))
  3205. {
  3206. // accumulate runs within the same paragraph level
  3207. cRuns = GetRunsPF(&rtp, &rpPF, cchLeft);
  3208. cRunsStart = 0; // assume no start run
  3209. ich = rtp.Move(-rtp.GetIchRunCF()); // locate preceding run
  3210. rtp._rpCF.AdjustBackward(); // adjusting format backward
  3211. rtp._rpPF.AdjustBackward(); // adjusting format backward
  3212. if(rtp._rpPF.SameLevel(&rpPF))
  3213. {
  3214. // start at the beginning of preceding run
  3215. if (rtp.Move(-rtp.GetCchRunCF()))
  3216. cRunsStart++;
  3217. }
  3218. else
  3219. {
  3220. // preceding run is not at the same paragraph level, resume position
  3221. rtp.Move(-ich);
  3222. }
  3223. rtp._rpCF.AdjustForward(); // make sure we have forward run pointers
  3224. rtp._rpPF.AdjustForward();
  3225. // Run FSM for the number of runs in multiple paragraphs with the same level
  3226. hr = pFSM->RunFSM(&rtp, cRuns, cRunsStart, rtp.IsParaRTL() ? 1 : 0);
  3227. cp = cpMost - cchLeft;
  3228. rtp.Move(cp - rtp.GetCp()); // Advance to next paragraph(s)
  3229. rpPF = rtp._rpPF; // paragraph format at cp
  3230. }
  3231. AssertSz (cp == cpMost , "Running BiDi FSM partially done!");
  3232. _rpCF = rtp._rpCF; // We may have splitted CF runs
  3233. return hr;
  3234. }
  3235. #endif // NOCOMPLEXSCRIPTS
  3236. /*
  3237. * CTxtRange::GetRunsPF(prtp, prpPF, cchLeft)
  3238. *
  3239. * @mfunc
  3240. * Get the number of CF runs within the same paragraph's base level.
  3241. * Its scope could cover multiple paragraphs. As long as they are in the
  3242. * same level, we can run them through the FSM in one go.
  3243. *
  3244. */
  3245. LONG CTxtRange::GetRunsPF(
  3246. CRchTxtPtr* prtp, // in: RichText ptr to the first run in range
  3247. CFormatRunPtr* prpPF, // in: Pointer to current paragraph run
  3248. LONG& cchLeft) // in/out: number of char left
  3249. {
  3250. Assert (prtp && prtp->_rpPF.SameLevel(prpPF) && cchLeft > 0);
  3251. LONG cRuns = 0;
  3252. LONG cchRun, cchText = cchLeft;
  3253. ICharFormatCache* pf = GetCharFormatCache();
  3254. // check if the first CF run is PF bound
  3255. //
  3256. prtp->_rpPF.AdjustBackward();
  3257. if (prtp->GetIchRunCF() > 0 && !prtp->_rpPF.SameLevel(prpPF))
  3258. prtp->_rpCF.SplitFormat(pf); // PF breaks inside a CF run, split the run
  3259. prtp->_rpPF.AdjustForward(); // make sure we are all forward.
  3260. prtp->_rpCF.AdjustForward();
  3261. while (cchText > 0)
  3262. {
  3263. cchRun = min(prtp->GetCchLeftRunPF(), prtp->GetCchLeftRunCF());
  3264. cchRun = min(cchText, cchRun); // find out the nearest hop
  3265. cchText -= cchRun;
  3266. prtp->Move(cchRun); // to the next hop
  3267. if (!prtp->_rpPF.SameLevel(prpPF))
  3268. { // this is a para with different level
  3269. prtp->_rpCF.SplitFormat(pf); // split that CF run
  3270. cRuns++; // count the splitted
  3271. break; // and we're done
  3272. }
  3273. if (!cchText || // this is the last hop -or-
  3274. !prtp->GetIchRunCF() || // we're at the start or the end of a CF run
  3275. !prtp->GetCchLeftRunCF())
  3276. {
  3277. cRuns++; // count this hop
  3278. }
  3279. }
  3280. prtp->Move(cchText - cchLeft); // resume position
  3281. cchLeft = cchText; // update number of char left
  3282. return cRuns;
  3283. }
  3284. /*
  3285. * CTxtRange::SpanSubstring (pusp, prp, pwchString, cchString, &uSubStrLevel,
  3286. * dwInFlags, &dwOutFlags, &wBiDiLangId)
  3287. * @mfunc
  3288. * Span the run of text bound by or contains only block separators
  3289. * and share the same charset directionality.
  3290. *
  3291. * @rdesc
  3292. * number of span'ed text characters
  3293. */
  3294. LONG CTxtRange::SpanSubstring(
  3295. CUniscribe * pusp, //@parm in: Uniscribe interface
  3296. CFormatRunPtr * prp, //@parm in: Format run pointer
  3297. WCHAR * pwchString, //@parm in: Input string
  3298. LONG cchString, //@parm in: String character count
  3299. WORD & uSubStrLevel, //@parm in/out: BiDi substring initial level
  3300. DWORD dwInFlags, //@parm in: Input flags
  3301. CCharFlags* pCharflags, // out:Output charflags
  3302. WORD & wBiDiLangId) //@parm out:Primary language of a BiDi run
  3303. {
  3304. Assert (pusp && cchString > 0 && prp && prp->IsValid());
  3305. LONG cch, cchLeft;
  3306. cch = cchLeft = cchString;
  3307. wBiDiLangId = LANG_NEUTRAL; // assume unknown
  3308. if (dwInFlags & SUBSTR_INSPANCHARSET)
  3309. {
  3310. // span runs with same charset's direction
  3311. CTxtEdit* ped = GetPed();
  3312. CFormatRunPtr rp(*prp);
  3313. const CCharFormat* pCF;
  3314. BOOL fNext;
  3315. BYTE iCharRep1, iCharRep2;
  3316. rp.AdjustForward();
  3317. pCF = ped->GetCharFormat(rp.GetFormat());
  3318. iCharRep1 = iCharRep2 = pCF->_iCharRep;
  3319. while (!(iCharRep1 ^ iCharRep2))
  3320. {
  3321. cch = min(rp.GetCchLeft(), cchLeft);
  3322. cchLeft -= cch;
  3323. if (!(fNext = rp.NextRun()) || !cchLeft)
  3324. break;
  3325. iCharRep1 = iCharRep2;
  3326. pCF = ped->GetCharFormat(rp.GetFormat());
  3327. iCharRep2 = pCF->_iCharRep;
  3328. }
  3329. uSubStrLevel = IsBiDiCharRep(iCharRep1) ? 1 : 0;
  3330. if (uSubStrLevel & 1)
  3331. wBiDiLangId = iCharRep1 == ARABIC_INDEX ? LANG_ARABIC : LANG_HEBREW;
  3332. cchString -= cchLeft;
  3333. cch = cchString;
  3334. dwInFlags |= SUBSTR_INSPANBLOCK;
  3335. }
  3336. if (dwInFlags & SUBSTR_INSPANBLOCK)
  3337. {
  3338. // scan the whole substring to collect information about it
  3339. DWORD dwBS = IsEOP(*pwchString) ? 1 : 0;
  3340. BYTE bCharMask;
  3341. cch = 0;
  3342. if (pCharflags)
  3343. pCharflags->_bFirstStrong = pCharflags->_bContaining = 0;
  3344. while (cch < cchString && !((IsEOP(pwchString[cch]) ? 1 : 0) ^ dwBS))
  3345. {
  3346. if (!dwBS && pCharflags)
  3347. {
  3348. bCharMask = 0;
  3349. switch (MECharClass(pwchString[cch]))
  3350. {
  3351. case CC_ARABIC:
  3352. case CC_HEBREW:
  3353. case CC_RTL:
  3354. bCharMask = SUBSTR_OUTCCRTL;
  3355. break;
  3356. case CC_LTR:
  3357. bCharMask = SUBSTR_OUTCCLTR;
  3358. default:
  3359. break;
  3360. }
  3361. if (bCharMask)
  3362. {
  3363. if (!pCharflags->_bFirstStrong)
  3364. pCharflags->_bFirstStrong |= bCharMask;
  3365. pCharflags->_bContaining |= bCharMask;
  3366. }
  3367. }
  3368. cch++;
  3369. }
  3370. }
  3371. return cch;
  3372. }
  3373. /*
  3374. * CTxtRange::ItemizeRuns(publdr, fUnicodeBidi, fUseCtxLevel)
  3375. *
  3376. * @mfunc
  3377. * Break text range into smaller run(s) containing
  3378. *
  3379. * 1. Script ID for complex script shaping
  3380. * 2. Charset for run internal direction
  3381. * 3. BiDi embedding level
  3382. *
  3383. * @rdesc
  3384. * TRUE iff one or more items found.
  3385. * The range's active end will be at cpMost upon return.
  3386. *
  3387. * @devnote
  3388. * This routine could handle mixed paragraph runs
  3389. */
  3390. BOOL CTxtRange::ItemizeRuns(
  3391. IUndoBuilder *publdr, //@parm Undo context for this operation
  3392. BOOL fUnicodeBiDi, //@parm TRUE: Caller needs Bidi algorithm
  3393. BOOL fUseCtxLevel) //@parm Itemize using context based level (only valid if fUnicodeBiDi is true)
  3394. {
  3395. #ifndef NOCOMPLEXSCRIPTS
  3396. CTxtEdit* ped = GetPed();
  3397. LONG cch, cchString;
  3398. CCharFormat CF;
  3399. CCharFlags charflags = {0};
  3400. int cItems = 0;
  3401. LONG cpMin, cpMost;
  3402. BOOL fBiDi = ped->IsBiDi();
  3403. CFreezeDisplay fd(ped->_pdp); // Freeze display
  3404. BOOL fChangeCharSet = FALSE;
  3405. BOOL fRunUnicodeBiDi;
  3406. BOOL fStreaming = ped->IsStreaming();
  3407. BYTE iCharRepDefault = ped->GetCharFormat(-1)->_iCharRep;
  3408. BYTE pbBufIn[MAX_CLIENT_BUF];
  3409. SCRIPT_ITEM * psi;
  3410. CTxtPtr tp(_rpTX);
  3411. WORD uParaLevel; // Paragraph initial level
  3412. WORD uSubStrLevel; // Substring initial level
  3413. WORD wBiDiLangId;
  3414. #ifdef DEBUG
  3415. LONG cchText = GetTextLength();
  3416. #endif
  3417. // Get range and setup text ptr to the start
  3418. cch = cchString = GetRange(cpMin, cpMost);
  3419. if (!cch)
  3420. return FALSE;
  3421. tp.SetCp(cpMin);
  3422. // Prepare Uniscribe
  3423. CUniscribe *pusp = ped->Getusp();
  3424. if (!pusp)
  3425. return FALSE;
  3426. // Allocate temp buffer for itemization
  3427. PUSP_CLIENT pc = NULL;
  3428. pusp->CreateClientStruc(pbBufIn, MAX_CLIENT_BUF, &pc, cchString, cli_Itemize);
  3429. if(!pc)
  3430. return FALSE;
  3431. CNotifyMgr *pnm = ped->GetNotifyMgr();
  3432. if(pnm)
  3433. pnm->NotifyPreReplaceRange(this, CP_INFINITE, 0, 0, cpMin, cpMost);
  3434. LONG cp = cpMin; // Set cp starting point at cpMin
  3435. Set(cp, 0); // equals to Collapser(tomStart)
  3436. Check_rpCF(); // Make sure _rpCF is valid
  3437. // Always run UnicodeBidi for plain text control
  3438. // (2.1 backward compatible)
  3439. if(!ped->IsRich())
  3440. {
  3441. fUnicodeBiDi = TRUE;
  3442. fUseCtxLevel = FALSE;
  3443. }
  3444. if(!fBiDi)
  3445. fUnicodeBiDi = FALSE;
  3446. uSubStrLevel = uParaLevel = IsParaRTL() ? 1 : 0; // initialize substring level
  3447. WCHAR *pwchString = pc->si->pwchString;
  3448. tp.GetTextForUsp(cchString, pwchString, ped->_fNeutralOverride);
  3449. while ( cchString > 0 &&
  3450. ((cch = SpanSubstring(pusp, &_rpCF, pwchString, cchString, uSubStrLevel,
  3451. fUnicodeBiDi ? SUBSTR_INSPANBLOCK : SUBSTR_INSPANCHARSET,
  3452. (fStreaming || fUseCtxLevel) ? &charflags : NULL,
  3453. wBiDiLangId)) > 0) )
  3454. {
  3455. LONG cchSave = cch;
  3456. BOOL fWhiteChunk = FALSE; // Chunk contains only whitespaces
  3457. if (uSubStrLevel ^ uParaLevel)
  3458. {
  3459. // Handle Bidi spaces when substring level counters paragraph base direction.
  3460. // Span leading spaces
  3461. cch = 0;
  3462. while (cch < cchSave && pwchString[cch] == 0x20)
  3463. cch++;
  3464. if (cch)
  3465. fWhiteChunk = TRUE;
  3466. else
  3467. {
  3468. // Trim out trailing whitespaces (including CR)
  3469. cch = cchSave;
  3470. while (cch > 0 && IsWhiteSpace(pwchString[cch-1]))
  3471. cch--;
  3472. if (!cch)
  3473. cch = cchSave;
  3474. }
  3475. Assert(cch > 0);
  3476. }
  3477. // Itemize with Unicode Bidi algorithm when
  3478. // a. Plain text mode
  3479. // b. Caller wants (fUnicodeBidi != 0)
  3480. // c. Substring is RTL.
  3481. fRunUnicodeBiDi = fUnicodeBiDi || uSubStrLevel;
  3482. fChangeCharSet = fUnicodeBiDi;
  3483. if (!fUnicodeBiDi && uSubStrLevel == 1 && fStreaming)
  3484. {
  3485. // During RTF streaming if the RTL run contains strong LTR,
  3486. // we resolve them using the paragraph base level
  3487. if (charflags._bContaining & SUBSTR_OUTCCLTR)
  3488. uSubStrLevel = uParaLevel;
  3489. fChangeCharSet = TRUE;
  3490. }
  3491. // Caller wants context based level.
  3492. // We want to itemize incoming plain text (into richtext doc) with the base level
  3493. // of the first strong character found in each substrings (wchao - 7/15/99)
  3494. if (fUnicodeBiDi && fUseCtxLevel && charflags._bFirstStrong)
  3495. uSubStrLevel = (WORD)(charflags._bFirstStrong & SUBSTR_OUTCCRTL ? 1 : 0);
  3496. if (fWhiteChunk || pusp->ItemizeString (pc, uSubStrLevel, &cItems, pwchString, cch,
  3497. fRunUnicodeBiDi, wBiDiLangId) > 0)
  3498. {
  3499. const SCRIPT_PROPERTIES *psp;
  3500. DWORD dwMask1;
  3501. psi = pc->si->psi;
  3502. if (fWhiteChunk)
  3503. {
  3504. cItems = 1;
  3505. psi[0].a.eScript = SCRIPT_WHITE;
  3506. psi[0].iCharPos = 0;
  3507. psi[1].iCharPos = cch;
  3508. }
  3509. Assert(cItems > 0);
  3510. // Process items
  3511. for(LONG i = 0; i < cItems; i++)
  3512. {
  3513. cp += psi[i+1].iCharPos - psi[i].iCharPos;
  3514. AssertNr (cp <= cchText);
  3515. SetCp(min(cp, cpMost), TRUE);
  3516. dwMask1 = 0;
  3517. // Associate the script properties
  3518. psp = pusp->GeteProp(psi[i].a.eScript);
  3519. Assert (psp);
  3520. if (!psp->fComplex && !psp->fNumeric &&
  3521. !psi[i].a.fRTL && psi[i].a.eScript < SCRIPT_MAX_COUNT)
  3522. {
  3523. // Note: Value 0 here is a valid script ID (SCRIPT_UNDEFINED),
  3524. // guaranteed by Uniscribe to be available all the time
  3525. // so we're safe using it as our simplified script ID.
  3526. psi[i].a.eScript = 0;
  3527. psp = pusp->GeteProp(0);
  3528. }
  3529. CF._wScript = psi[i].a.eScript;
  3530. // Stamp appropriate charset
  3531. if (pusp->GetComplexCharRep(psp, iCharRepDefault, CF._iCharRep))
  3532. {
  3533. // Complex script that has distinctive charset
  3534. dwMask1 |= CFM_CHARSET;
  3535. }
  3536. else if (fChangeCharSet)
  3537. {
  3538. // We run UnicodeBidi to analyse the whole thing so
  3539. // we need to figure out the proper charset to use as well.
  3540. //
  3541. // Note that we don't want to apply charset in general, say things
  3542. // like East Asia or GREEK_CHARSET should remain unchanged by
  3543. // this effect. But doing charset check is tough since we deal
  3544. // with text in range basis, so we simply call to update it here
  3545. // and let CCharFormat::Apply do the charset test in down level.
  3546. CF._iCharRep = CharRepFromCharSet(psp->bCharSet); // Assume what Uniscribe has given us
  3547. if (psi[i].a.fRTL || psi[i].a.fLayoutRTL)
  3548. {
  3549. // Those of strong RTL and RTL digits need RTL char repertoire
  3550. CF._iCharRep = pusp->GetRtlCharRep(ped, this);
  3551. }
  3552. Assert(CF._iCharRep != DEFAULT_INDEX);
  3553. dwMask1 |= CFM_CHARSET;
  3554. }
  3555. // No publdr for this call so no antievent for itemized CF
  3556. SetCharFormat(&CF, SCF_IGNORENOTIFY, NULL, dwMask1, CFM2_SCRIPT);
  3557. Set(cp, 0);
  3558. #ifdef DEBUG
  3559. if(IN_RANGE(0xDC00, GetPrevChar(), 0xDFFF))
  3560. {
  3561. _rpCF.AdjustBackward();
  3562. if(_rpCF.GetIch() == 1) // Solo trail surrogate in run
  3563. {
  3564. CTxtPtr tp(_rpTX); // If lead surrogate preceeds it,
  3565. tp.Move(-1); // Assert
  3566. AssertSz(!IN_RANGE(0xD800, tp.GetPrevChar(), 0xDBFF),
  3567. "CTxtRange::ItemizeRuns: nonuniform CF for surrogate pair");
  3568. }
  3569. _rpCF.AdjustForward();
  3570. }
  3571. #endif
  3572. }
  3573. }
  3574. else
  3575. {
  3576. // Itemization fails.
  3577. cp += cch;
  3578. SetCp(min(cp, cpMost), TRUE);
  3579. // Reset script id to 0
  3580. CF._wScript = 0;
  3581. SetCharFormat(&CF, SCF_IGNORENOTIFY, NULL, 0, CFM2_SCRIPT);
  3582. Set(cp, 0);
  3583. }
  3584. pwchString = &pc->si->pwchString[cp - cpMin]; // Point at next substring
  3585. cchString -= cch;
  3586. uParaLevel = IsParaRTL() ? 1 : 0; // Paragraph level might have changed
  3587. }
  3588. Assert (cpMost == cp);
  3589. Set(cpMost, cpMost - cpMin); // Restore original range (may change active end)
  3590. if(fBiDi)
  3591. {
  3592. // Retrieve ptr to Bidi FSM
  3593. HRESULT hr = E_FAIL;
  3594. const CBiDiFSM* pFSM = pusp->GetFSM();
  3595. Check_rpPF(); // Be sure PF runs are instantiated
  3596. if (pFSM)
  3597. hr = BiDiLevelFromFSM (pFSM);
  3598. AssertSz(SUCCEEDED(hr), "Unable to run or running BiDi FSM fails! We are in deep trouble,");
  3599. }
  3600. if (pc && pbBufIn != (BYTE *)pc)
  3601. FreePv(pc);
  3602. ped->_fItemizePending = FALSE; // Update flag
  3603. // Notify backing store change to all notification sinks
  3604. if(pnm)
  3605. pnm->NotifyPostReplaceRange(this, CP_INFINITE, 0, 0, cpMin, cpMost);
  3606. return cItems > 0;
  3607. #else
  3608. return TRUE;
  3609. #endif // NOCOMPLEXSCRIPTS
  3610. }
  3611. /*
  3612. * CTxtRange::IsProtected(iDirection)
  3613. *
  3614. * @mfunc
  3615. * Return TRUE if any part of this range is protected (HACK: or
  3616. * if any part of the range contains DBCS text stored in our Unicode
  3617. * backing store). If degenerate,
  3618. * use CCharFormat from run specified by iDirection, that is, use run
  3619. * valid up to, at, or starting at this GetCp() for iDirection less, =,
  3620. * or greater than 0, respectively.
  3621. *
  3622. * @rdesc
  3623. * TRUE iff any part of this range is protected (HACK: or if any part
  3624. * of the range contains DBCS text stored in our Unicode backing store
  3625. * For this to work correctly, GetCharFormat() needs to return dwMask2
  3626. * as well).
  3627. */
  3628. PROTECT CTxtRange::IsProtected (
  3629. CHECKPROTECT chkprot) //@parm Controls which run to check if range is IP
  3630. {
  3631. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::IsProtected");
  3632. CCharFormat CF;
  3633. BOOL fTOM = FALSE;
  3634. LONG iFormat = -1; // Default default CF
  3635. _TEST_INVARIANT_
  3636. if(chkprot == CHKPROT_TOM)
  3637. {
  3638. fTOM = TRUE;
  3639. chkprot = CHKPROT_EITHER;
  3640. }
  3641. if(_rpCF.IsValid()) // Active rich-text runs
  3642. {
  3643. if(_cch) // Range is nondegenerate
  3644. {
  3645. DWORD dwMask = GetCharFormat(&CF);
  3646. if(CF._dwEffects & CFE_RUNISDBCS)
  3647. return PROTECTED_YES;
  3648. if (!(dwMask & CFM_PROTECTED) ||
  3649. (CF._dwEffects & CFE_PROTECTED))
  3650. {
  3651. return PROTECTED_ASK;
  3652. }
  3653. return PROTECTED_NO;
  3654. }
  3655. iFormat = _iFormat; // Degenerate range: default
  3656. if(chkprot != CHKPROT_EITHER) // this range's iFormat
  3657. { // Specific run direction
  3658. CFormatRunPtr rpCF(_rpCF);
  3659. if(chkprot == CHKPROT_BACKWARD) // If at run ambiguous pos,
  3660. rpCF.AdjustBackward(); // use previous run
  3661. else
  3662. rpCF.AdjustForward();
  3663. iFormat = rpCF.GetFormat(); // Get run format
  3664. }
  3665. }
  3666. const CCharFormat *pCF = GetPed()->GetCharFormat(iFormat);
  3667. if(pCF->_dwEffects & CFE_RUNISDBCS)
  3668. return PROTECTED_YES;
  3669. if(!fTOM && !(pCF->_dwEffects & CFE_PROTECTED))
  3670. return PROTECTED_NO;
  3671. if(!_cch && chkprot == CHKPROT_EITHER) // If insertion point and
  3672. { // no directionality, return
  3673. CFormatRunPtr rpCF(_rpCF); // PROTECTED_NO if either
  3674. rpCF.AdjustBackward(); // forward or previous run is
  3675. pCF = GetPed()->GetCharFormat(rpCF.GetFormat());// unprotected
  3676. if(!(pCF->_dwEffects & CFE_PROTECTED))
  3677. return PROTECTED_NO;
  3678. rpCF.AdjustForward();
  3679. pCF = GetPed()->GetCharFormat(rpCF.GetFormat());
  3680. }
  3681. return (pCF->_dwEffects & CFE_PROTECTED) ? PROTECTED_ASK : PROTECTED_NO;
  3682. }
  3683. /*
  3684. * CTxtRange::AdjustEndEOP (NewChars)
  3685. *
  3686. * @mfunc
  3687. * If this range is a selection and ends with an EOP and consists of
  3688. * more than just this EOP and fAdd is TRUE, or this EOP is the final
  3689. * EOP (at the story end), or this selection doesn't begin at the start
  3690. * of a paragraph, then move cpMost just before the end EOP. This
  3691. * function is used by UI methods that delete the selected text, such
  3692. * as PutChar(), Delete(), cut/paste, drag/drop.
  3693. *
  3694. * @rdesc
  3695. * TRUE iff range end has been adjusted
  3696. *
  3697. * @devnote
  3698. * This method leaves the active end at the selection cpMin. It is a
  3699. * CTxtRange method to handle the selection when it mascarades as a
  3700. * range for Cut/Paste.
  3701. */
  3702. BOOL CTxtRange::AdjustEndEOP (
  3703. EOPADJUST NewChars) //@parm NEWCHARS if chars will be added
  3704. {
  3705. TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtRange::AdjustEndEOP");
  3706. LONG cpMin, cpMost;
  3707. LONG cch = GetRange(cpMin, cpMost);
  3708. LONG cchSave = _cch;
  3709. BOOL fRet = FALSE;
  3710. if(cch && (cch < GetTextLength() || NewChars == NEWCHARS))
  3711. {
  3712. CTxtPtr tp(_rpTX);
  3713. if(_cch > 0) // Ensure active end is cpMin
  3714. FlipRange(); // (ReplaceRange() needs to
  3715. else // do this anyhow)
  3716. tp.Move(-_cch); // Ensure tp is at cpMost
  3717. LONG cchEOP = -tp.BackupCRLF();
  3718. if (IsASCIIEOP(tp.GetChar()) && // Don't delete EOP at sel
  3719. !tp.IsAtTRD(ENDFIELD) && // end if EOP isn't end of
  3720. (NewChars == NEWCHARS || // table row and if there're
  3721. cpMin && !_rpTX.IsAfterEOP() && // chars to add, or cpMin
  3722. cch > cchEOP)) // isn't at BOD and more than
  3723. { // EOP is selected
  3724. _cch += cchEOP; // Shorten range before EOP
  3725. Update_iFormat(-1); // negative _cch to make
  3726. fRet = TRUE; // it less negative
  3727. }
  3728. if((_cch ^ cchSave) < 0 && _fSel) // Keep active end the same
  3729. FlipRange(); // for selection undo
  3730. }
  3731. return fRet;
  3732. }
  3733. /*
  3734. * CTxtRange::DeleteTerminatingEOP (publdr)
  3735. *
  3736. * @mfunc
  3737. * If this range is an insertion point that follows an EOP, select
  3738. * and delete that EOP
  3739. */
  3740. void CTxtRange::DeleteTerminatingEOP(
  3741. IUndoBuilder *publdr)
  3742. {
  3743. Assert(!_cch);
  3744. if(_rpTX.IsAfterEOP())
  3745. {
  3746. BackupCRLF(CSC_NORMAL, TRUE);
  3747. if(IN_RANGE(STARTFIELD, CRchTxtPtr::GetChar(), ENDFIELD))
  3748. AdvanceCRLF(CSC_NORMAL, TRUE); // Leave range the way it was
  3749. else
  3750. ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE, NULL, RR_NO_LP_CHECK);
  3751. }
  3752. }
  3753. /*
  3754. * CTxtRange::CheckTextLength(cch)
  3755. *
  3756. * @mfunc
  3757. * Check to see if can add cch characters. If not, notify parent
  3758. *
  3759. * @rdesc
  3760. * TRUE if OK to add cch chars
  3761. */
  3762. BOOL CTxtRange::CheckTextLength (
  3763. LONG cch,
  3764. LONG *pcch)
  3765. {
  3766. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::CheckTextLength");
  3767. _TEST_INVARIANT_
  3768. DWORD cchNew = (DWORD)(CalcTextLenNotInRange() + cch);
  3769. if(cchNew > GetPed()->TxGetMaxLength())
  3770. {
  3771. if (pcch)
  3772. *pcch = cchNew - GetPed()->TxGetMaxLength();
  3773. else
  3774. GetPed()->GetCallMgr()->SetMaxText();
  3775. return FALSE;
  3776. }
  3777. return TRUE;
  3778. }
  3779. /*
  3780. * CTxtRange::InsertTableRow(pPF, publdr)
  3781. *
  3782. * @mfunc
  3783. * Insert empty table row with parameters given by pPF
  3784. *
  3785. * @rdesc
  3786. * Count of CELL and NOTACHAR chars inserted if successful; else 0
  3787. */
  3788. LONG CTxtRange::InsertTableRow(
  3789. const CParaFormat *pPF, //@parm CParaFormat to use for delimiters
  3790. IUndoBuilder *publdr) //@parm If non-NULL, where to put anti-events
  3791. {
  3792. AssertSz(pPF->_bTabCount && pPF->IsTableRowDelimiter(),
  3793. "CTxtRange::InsertTableRow: illegal pPF");
  3794. AssertSz(!_cch, "CTxtRange::InsertTableRow: nondegenerate range");
  3795. if(GetPed()->TxGetPasswordChar()) // Tsk, tsk, no inserting tables
  3796. return 0; // into password controls
  3797. LONG cCell = pPF->_bTabCount;
  3798. LONG cchCells = cCell;
  3799. LONG dul = 0;
  3800. BOOL fIsAtTRED = _rpTX.IsAtTRD(ENDFIELD);
  3801. WCHAR szBlankRow[2*MAX_TABLE_CELLS + 6] = {CR, STARTFIELD, CR};
  3802. WCHAR * pch = szBlankRow + 3;
  3803. CFreezeDisplay fd(GetPed()->_pdp);
  3804. CParaFormat PF1 = *pPF; // Save *pPF, since may move
  3805. const CELLPARMS *prgCellParms = PF1.GetCellParms();
  3806. // due to
  3807. // Get cell info on preceding row
  3808. const CELLPARMS *prgCellParmsPrev = NULL;
  3809. LONG cCellPrev;
  3810. if(fIsAtTRED)
  3811. {
  3812. prgCellParmsPrev = prgCellParms;// Same CParaFormat
  3813. cCellPrev = pPF->_bTabCount;
  3814. }
  3815. else
  3816. {
  3817. _rpPF.AdjustBackward();
  3818. const CParaFormat *pPFPrev = GetPF();
  3819. cCellPrev = pPFPrev->_bTabCount;
  3820. _rpPF.AdjustForward();
  3821. if (GetCp() && pPFPrev->IsTableRowDelimiter() &&
  3822. pPFPrev->_bTableLevel == pPF->_bTableLevel)
  3823. {
  3824. prgCellParmsPrev = pPFPrev->GetCellParms();
  3825. }
  3826. }
  3827. // Define plain text for row (CELLs, NOTACHARs, TRDs)
  3828. for(LONG i = 0; i < cCell; i++)
  3829. {
  3830. LONG uCell = prgCellParms[i].uCell;
  3831. dul += GetCellWidth(uCell);
  3832. if(IsLowCell(uCell))
  3833. {
  3834. if(!prgCellParmsPrev) // No previous row, so cell can't
  3835. return 0; // be merged with one above
  3836. LONG iCell = prgCellParmsPrev->ICellFromUCell(dul, cCellPrev);
  3837. if(iCell < 0 || !IsVertMergedCell(prgCellParmsPrev[iCell].uCell))
  3838. return 0;
  3839. *pch++ = NOTACHAR;
  3840. cchCells++; // Add in extra char
  3841. }
  3842. *pch++ = CELL;
  3843. }
  3844. *pch++ = ENDFIELD;
  3845. *pch++ = CR;
  3846. LONG cch = cchCells + 5; // cch to insert
  3847. pch = szBlankRow;
  3848. if(!GetCp() || _rpTX.IsAfterEOP()) // New row follows CR, so don't
  3849. { // need to insert a leading CR
  3850. pch++;
  3851. cch--;
  3852. if(GetCp()) // Still need to unhide CR if it's
  3853. { // hidden
  3854. _rpCF.AdjustBackward();
  3855. const CCharFormat *pCF = GetCF();
  3856. _rpCF.AdjustForward();
  3857. if(pCF->_dwEffects & CFE_HIDDEN)
  3858. {
  3859. CTxtPtr tp(_rpTX);
  3860. CCharFormat CF = *pCF;
  3861. CF._dwEffects = 0; // Turn off hidden
  3862. _cch = -tp.BackupCRLF(FALSE);
  3863. SetCharFormat(&CF, 0, publdr, CFM_HIDDEN, 0);
  3864. _cch = 0;
  3865. }
  3866. }
  3867. }
  3868. if(_cch || !CheckTextLength(cch)) // If nondegen or empty row can't
  3869. return 0; // fit, fail call
  3870. if(publdr)
  3871. publdr->StopGroupTyping();
  3872. if(fIsAtTRED) // Don't insert new table between
  3873. AdvanceCRLF(CSC_NORMAL, FALSE); // final CELL and table-row end
  3874. // delimiter
  3875. if(_rpTX.IsAfterTRD(ENDFIELD))
  3876. {
  3877. _rpCF.AdjustBackward(); // Use CF of TR end delimiter
  3878. Set_iCF(_rpCF.GetFormat());
  3879. }
  3880. ReplaceRange(cch, pch, publdr, SELRR_REMEMBERRANGE, NULL, RR_UNHIDE);
  3881. _cch = 2; // Select row end marker
  3882. SetParaFormat(&PF1, publdr, PFM_ALL2, PFM2_ALLOWTRDCHANGE);
  3883. CParaFormat PF; // Create PF for cell markers
  3884. PF = *(GetPed()->GetParaFormat(-1));// and possible lead CR
  3885. PF._wEffects |= PFE_TABLE;
  3886. PF._bTableLevel = pPF->_bTableLevel;
  3887. AssertSz(PF._bTableLevel > 0, "CTxtRange::InsertTableRow: invalid table level");
  3888. Set(GetCp() - 2, cchCells); // Select cell markers
  3889. SetParaFormat(&PF, publdr, PFM_ALL2, PFM2_ALLOWTRDCHANGE);
  3890. Set(GetCp() - cchCells, 2); // Select row start marker
  3891. SetParaFormat(&PF1, publdr, PFM_ALL2, PFM2_ALLOWTRDCHANGE);
  3892. if(pch == szBlankRow)
  3893. {
  3894. Set(GetCp() - 2, 1); // Select lead CR
  3895. PF._bTableLevel--;
  3896. if(!PF._bTableLevel)
  3897. PF._wEffects &= ~PFE_TABLE;
  3898. AssertSz(PF._bTableLevel >= 0, "CTxtRange::InsertTableRow: invalid table level");
  3899. SetParaFormat(&PF, publdr, PFM_ALL2, PFM2_ALLOWTRDCHANGE);
  3900. Move(2, FALSE);
  3901. }
  3902. Collapser(FALSE); // Leave in first cell of new row
  3903. _fMoveBack = FALSE;
  3904. Update(TRUE);
  3905. return cchCells;
  3906. }
  3907. /*
  3908. * CTxtRange::FindObject(pcpMin, pcpMost)
  3909. *
  3910. * @mfunc
  3911. * Set *pcpMin = closest embedded object cpMin <lt>= range cpMin
  3912. * Set *pcpMost = closest embedded object cpMost <gt>= range cpMost
  3913. *
  3914. * @rdesc
  3915. * TRUE iff object found
  3916. *
  3917. * @comm
  3918. * An embedded object cpMin points at the first character of an embedded
  3919. * object. For RichEdit, this is the WCH_EMBEDDING character. An
  3920. * embedded object cpMost follows the last character of an embedded
  3921. * object. For RichEdit, this immediately follows the WCH_EMBEDDING
  3922. * character.
  3923. */
  3924. BOOL CTxtRange::FindObject(
  3925. LONG *pcpMin, //@parm Out parm to receive object's cpMin; NULL OK
  3926. LONG *pcpMost) const//@parm Out parm to receive object's cpMost; NULL OK
  3927. {
  3928. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindObject");
  3929. if(!GetObjectCount()) // No objects: can't move, so
  3930. return FALSE; // return FALSE
  3931. BOOL bRet = FALSE; // Default no object
  3932. LONG cpMin, cpMost;
  3933. CTxtPtr tp(_rpTX);
  3934. GetRange(cpMin, cpMost);
  3935. if(pcpMin)
  3936. {
  3937. tp.SetCp(cpMin);
  3938. if(tp.GetChar() != WCH_EMBEDDING)
  3939. {
  3940. cpMin = tp.FindExact(tomBackward, szEmbedding);
  3941. if(cpMin >= 0)
  3942. {
  3943. bRet = TRUE;
  3944. *pcpMin = cpMin;
  3945. }
  3946. }
  3947. }
  3948. if(pcpMost)
  3949. {
  3950. tp.SetCp(cpMost);
  3951. if (tp.PrevChar() != WCH_EMBEDDING &&
  3952. tp.FindExact(tomForward, szEmbedding) >= 0)
  3953. {
  3954. bRet = TRUE;
  3955. *pcpMost = tp.GetCp();
  3956. }
  3957. }
  3958. return bRet;
  3959. }
  3960. /*
  3961. * CTxtRange::FindCell(pcpMin, pcpMost)
  3962. *
  3963. * @mfunc
  3964. * Set *pcpMin = closest cell cpMin <lt>= range cpMin (see comment)
  3965. * Set *pcpMost = closest cell cpMost <gt>= range cpMost
  3966. *
  3967. * @comment
  3968. * This function returns range cpMin and cpMost if the range isn't
  3969. * completely in a table or if the range already selects one or more
  3970. * cells at the same table level.
  3971. */
  3972. void CTxtRange::FindCell (
  3973. LONG *pcpMin, //@parm Out parm for bounding-cell cpMin
  3974. LONG *pcpMost) const //@parm Out parm for bounding-cell cpMost
  3975. {
  3976. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindCell");
  3977. LONG cch;
  3978. LONG cpMin, cpMost;
  3979. LONG Results;
  3980. CPFRunPtr rp(*this);
  3981. LONG Level = rp.GetMinTableLevel(_cch);
  3982. CTxtPtr tp(_rpTX);
  3983. _TEST_INVARIANT_
  3984. GetRange(cpMin, cpMost);
  3985. LONG cp = cpMin;
  3986. if(Level)
  3987. {
  3988. tp.SetCp(cpMin);
  3989. if(tp.IsAtTRD(STARTFIELD) && rp.GetTableLevel() == Level)
  3990. Level--;
  3991. }
  3992. if(pcpMin)
  3993. {
  3994. if(Level && rp.InTable())
  3995. {
  3996. rp.AdjustBackward();
  3997. while(rp.GetTableLevel() >= Level && tp.GetCp())
  3998. {
  3999. if(tp.IsAtStartOfCell() && rp.GetTableLevel() <= Level)
  4000. break;
  4001. while(1) // Ensure at correct table level
  4002. {
  4003. rp.AdjustBackward();
  4004. if(rp.GetTableLevel() <= Level)
  4005. break;
  4006. tp.Move(-rp.GetIch()); // Bypass paraformat runs back
  4007. rp.SetIch(0); // to desired level
  4008. }
  4009. if(tp.IsAfterTRD(STARTFIELD))
  4010. break;
  4011. cch = tp.FindEOP(tomBackward, &Results);
  4012. if(!cch)
  4013. break;
  4014. rp.Move(cch); // Keep rp in sync with tp
  4015. }
  4016. cp = tp.GetCp();
  4017. }
  4018. *pcpMin = cp;
  4019. }
  4020. if(pcpMost)
  4021. {
  4022. rp.Move(cpMost - cp);
  4023. tp.SetCp(cpMost);
  4024. if(Level && rp.InTable())
  4025. {
  4026. if(pcpMin && !_cch && *pcpMin == cpMost)
  4027. rp.Move(tp.FindEOP(tomForward, &Results));
  4028. while(rp.GetTableLevel() >= Level)
  4029. {
  4030. if(tp.GetPrevChar() == CELL)
  4031. {
  4032. rp.AdjustBackward();
  4033. if(rp.GetTableLevel() == Level)
  4034. break;
  4035. }
  4036. do // Ensure at correct table level
  4037. {
  4038. if(rp.GetTableLevel() <= Level)
  4039. break;
  4040. tp.Move(rp.GetCchLeft()); // Bypass paraformats up to
  4041. } while(rp.NextRun()); // desired level
  4042. cch = tp.FindEOP(tomForward, &Results);
  4043. if(!cch)
  4044. break;
  4045. rp.Move(cch); // Keep rp in sync with tp
  4046. }
  4047. }
  4048. *pcpMost = tp.GetCp();
  4049. }
  4050. }
  4051. /*
  4052. * CTxtRange::FindRow(pcpMin, pcpMost, Level)
  4053. *
  4054. * @mfunc
  4055. * Set *pcpMin = closest row cpMin <lt>= range cpMin.
  4056. * Set *pcpMost = closest row cpMost <gt>= range cpMost.
  4057. * In both cases the row(s) chosen correspond to the
  4058. * table level of range.
  4059. */
  4060. void CTxtRange::FindRow (
  4061. LONG *pcpMin, //@parm Out parm for bounding-row cpMin
  4062. LONG *pcpMost, //@parm Out parm for bounding-row cpMost
  4063. LONG Level) const //@parm Table row level to expand to
  4064. {
  4065. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindRow");
  4066. _TEST_INVARIANT_
  4067. LONG cchAdjText = GetAdjustedTextLength();
  4068. WCHAR ch;
  4069. LONG cpMin, cpMost;
  4070. CPFRunPtr rp(*this);
  4071. CTxtPtr tp(_rpTX);
  4072. const CParaFormat *pPF;
  4073. if(Level < 0) // Get range min Level
  4074. Level = rp.GetMinTableLevel(_cch); // and move rp to cpMin
  4075. else if(_cch > 0)
  4076. rp.Move(-_cch);
  4077. GetRange(cpMin, cpMost);
  4078. LONG cp = cpMin;
  4079. if(pcpMin)
  4080. {
  4081. while(1)
  4082. {
  4083. pPF = rp.GetPF();
  4084. if(pPF->_bTableLevel < Level || !pPF->_bTableLevel)
  4085. break;
  4086. cp -= rp.GetIch(); // Go to start of PF run
  4087. rp.SetIch(0);
  4088. if(pPF->IsTableRowDelimiter())
  4089. {
  4090. LONG cchMove = 0;
  4091. if(rp.GetCchLeft() == 4 && cp <= cpMin - 2)
  4092. cchMove = 2; // For end-start pair, setup to
  4093. // advance to start
  4094. tp.SetCp(cp); // Update tp to get char
  4095. ch = tp.GetChar();
  4096. if(ch == STARTFIELD || cchMove)
  4097. {
  4098. AssertSz(!cchMove || ch == ENDFIELD, "CTxtRange::FindRow: illegal table row");
  4099. if(Level == pPF->_bTableLevel)
  4100. {
  4101. if(cchMove)
  4102. {
  4103. cp += cchMove;
  4104. rp.Move(cchMove);
  4105. }
  4106. break;
  4107. }
  4108. }
  4109. }
  4110. if(!rp.PrevRun()) // Go to previous run if there is one
  4111. {
  4112. AssertSz(!cp && !Level, "CTxtRange::FindRow: badly formed table");
  4113. break;
  4114. }
  4115. cp -= rp.GetCchLeft();
  4116. }
  4117. *pcpMin = cp;
  4118. }
  4119. if(pcpMost)
  4120. {
  4121. rp.Move(cpMost - cp); // Advance to cpMost of range
  4122. Assert(!rp.IsValid() || (cp = rp.CalculateCp()) == cpMost);
  4123. cp = cpMost;
  4124. rp.AdjustBackward(); // If cpMost directly follows
  4125. if(rp.IsTableRowDelimiter()) // row-end delimiter, backup over
  4126. { // it to catch case when range
  4127. tp.SetCp(cp); // already selects a row
  4128. if(tp.GetChar() == CR) // In middle of TR delimiter
  4129. { // so move to its start
  4130. cp--;
  4131. rp.Move(-1);
  4132. }
  4133. else if(abs(_cch) > 2 && tp.IsAfterTRD(ENDFIELD))
  4134. {
  4135. cp -= 2;
  4136. rp.Move(-2);
  4137. }
  4138. }
  4139. rp.AdjustForward();
  4140. while(cp < cchAdjText)
  4141. {
  4142. pPF = rp.GetPF();
  4143. if(pPF->_bTableLevel < Level || !pPF->_bTableLevel)
  4144. break;
  4145. if(pPF->IsTableRowDelimiter())
  4146. {
  4147. tp.SetCp(cp);
  4148. ch = tp.GetChar();
  4149. if(ch == ENDFIELD && Level == pPF->_bTableLevel)
  4150. {
  4151. cp += tp.AdvanceCRLF(FALSE);// Bypass row end delimiter
  4152. break;
  4153. }
  4154. }
  4155. cp += rp.GetCchLeft();
  4156. if(!rp.NextRun())
  4157. break;
  4158. }
  4159. *pcpMost = cp;
  4160. }
  4161. }
  4162. /*
  4163. * CTxtRange::FindParagraph(pcpMin, pcpMost)
  4164. *
  4165. * @mfunc
  4166. * Set *pcpMin = closest paragraph cpMin <lt>= range cpMin (see comment)
  4167. * Set *pcpMost = closest paragraph cpMost <gt>= range cpMost
  4168. *
  4169. * @devnote
  4170. * If this range's cpMost follows an EOP, use it for bounding-paragraph
  4171. * cpMost unless 1) the range is an insertion point, and 2) pcpMin and
  4172. * pcpMost are both nonzero, in which case use the next EOP. Both out
  4173. * parameters are nonzero if FindParagraph() is used to expand to full
  4174. * paragraphs (else StartOf or EndOf is all that's requested). This
  4175. * behavior is consistent with the selection/IP UI. Note that FindEOP
  4176. * treats the beginning/end of document (BOD/EOD) as a BOP/EOP,
  4177. * respectively, but IsAfterEOP() does not.
  4178. */
  4179. void CTxtRange::FindParagraph (
  4180. LONG *pcpMin, //@parm Out parm for bounding-paragraph cpMin
  4181. LONG *pcpMost) const //@parm Out parm for bounding-paragraph cpMost
  4182. {
  4183. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindParagraph");
  4184. LONG cpMin, cpMost;
  4185. CTxtPtr tp(_rpTX);
  4186. _TEST_INVARIANT_
  4187. GetRange(cpMin, cpMost);
  4188. if(pcpMin)
  4189. {
  4190. tp.SetCp(cpMin); // tp points at this range's cpMin
  4191. if(!tp.IsAfterEOP()) // Unless tp directly follows an
  4192. tp.FindEOP(tomBackward); // EOP, search backward for EOP
  4193. *pcpMin = cpMin = tp.GetCp();
  4194. }
  4195. if(pcpMost)
  4196. {
  4197. tp.SetCp(cpMost); // If range cpMost doesn't follow
  4198. if (!tp.IsAfterEOP() || // an EOP or else if expanding
  4199. (!cpMost || pcpMin) &&
  4200. cpMin == cpMost) // IP at paragraph beginning,
  4201. {
  4202. tp.FindEOP(tomForward); // search for next EOP
  4203. }
  4204. *pcpMost = tp.GetCp();
  4205. }
  4206. }
  4207. /*
  4208. * CTxtRange::FindSentence(pcpMin, pcpMost)
  4209. *
  4210. * @mfunc
  4211. * Set *pcpMin = closest sentence cpMin <lt>= range cpMin
  4212. * Set *pcpMost = closest sentence cpMost <gt>= range cpMost
  4213. *
  4214. * @devnote
  4215. * If this range's cpMost follows a sentence end, use it for bounding-
  4216. * sentence cpMost unless the range is an insertion point, in which case
  4217. * use the next sentence end. The routine takes care of aligning on
  4218. * sentence beginnings in the case of range ends that fall on whitespace
  4219. * in between sentences.
  4220. */
  4221. void CTxtRange::FindSentence (
  4222. LONG *pcpMin, //@parm Out parm for bounding-sentence cpMin
  4223. LONG *pcpMost) const //@parm Out parm for bounding-sentence cpMost
  4224. {
  4225. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindSentence");
  4226. LONG cpMin, cpMost;
  4227. CTxtPtr tp(_rpTX);
  4228. _TEST_INVARIANT_
  4229. GetRange(cpMin, cpMost);
  4230. if(pcpMin) // Find sentence beginning
  4231. {
  4232. tp.SetCp(cpMin); // tp points at this range's cpMin
  4233. if(!tp.IsAtBOSentence()) // If not at beginning of sentence
  4234. tp.FindBOSentence(tomBackward); // search backward for one
  4235. *pcpMin = cpMin = tp.GetCp();
  4236. }
  4237. if(pcpMost) // Find sentence end
  4238. { // Point tp at this range's cpLim
  4239. tp.SetCp(cpMost); // If cpMost isn't at sentence
  4240. if (!tp.IsAtBOSentence() || // beginning or if at story
  4241. (!cpMost || pcpMin) && // beginning or expanding
  4242. cpMin == cpMost) // IP at sentence beginning,
  4243. { // find next sentence beginning
  4244. if(!tp.FindBOSentence(tomForward))
  4245. tp.SetCp(GetTextLength()); // End of story counts as
  4246. } // sentence end too
  4247. *pcpMost = tp.GetCp();
  4248. }
  4249. }
  4250. /*
  4251. * CTxtRange::FindVisibleRange(pcpMin, pcpMost)
  4252. *
  4253. * @mfunc
  4254. * Set *pcpMin = _pdp->_cpFirstVisible
  4255. * Set *pcpMost = _pdp->_cpLastVisible
  4256. *
  4257. * @rdesc
  4258. * TRUE iff calculated cp's differ from this range's cp's
  4259. *
  4260. * @devnote
  4261. * CDisplay::GetFirstVisible() and GetCliVisible() return the first cp
  4262. * on the first visible line and the last cp on the last visible line.
  4263. * These won't be visible if they are scrolled off the screen.
  4264. * FUTURE: A more general algorithm would CpFromPoint (0,0) and
  4265. * (right, bottom).
  4266. */
  4267. BOOL CTxtRange::FindVisibleRange (
  4268. LONG *pcpMin, //@parm Out parm for cpFirstVisible
  4269. LONG *pcpMost) const //@parm Out parm for cpLastVisible
  4270. {
  4271. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindVisibleRange");
  4272. _TEST_INVARIANT_
  4273. CDisplay * pdp = GetPed()->_pdp;
  4274. if(!pdp)
  4275. return FALSE;
  4276. if(pcpMin)
  4277. *pcpMin = pdp->GetFirstVisibleCp();
  4278. pdp->GetCliVisible(pcpMost);
  4279. return TRUE;
  4280. }
  4281. /*
  4282. * CTxtRange::FindWord(pcpMin, pcpMost, type)
  4283. *
  4284. * @mfunc
  4285. * Set *pcpMin = closest word cpMin <lt>= range cpMin
  4286. * Set *pcpMost = closest word cpMost <gt>= range cpMost
  4287. *
  4288. * @comm
  4289. * There are two interesting cases for finding a word. The first,
  4290. * (FW_EXACT) finds the exact word, with no extraneous characters.
  4291. * This is useful for situations like applying formatting to a
  4292. * word. The second case, FW_INCLUDE_TRAILING_WHITESPACE does the
  4293. * obvious thing, namely includes the whitespace up to the next word.
  4294. * This is useful for the selection double-click semantics and TOM.
  4295. */
  4296. void CTxtRange::FindWord(
  4297. LONG *pcpMin, //@parm Out parm to receive word's cpMin; NULL OK
  4298. LONG *pcpMost, //@parm Out parm to receive word's cpMost; NULL OK
  4299. FINDWORD_TYPE type) const //@parm Type of word to find
  4300. {
  4301. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindWord");
  4302. LONG cch, cch1;
  4303. LONG cpMin, cpMost;
  4304. CTxtPtr tp(_rpTX);
  4305. _TEST_INVARIANT_
  4306. Assert(type == FW_EXACT || type == FW_INCLUDE_TRAILING_WHITESPACE );
  4307. GetRange(cpMin, cpMost);
  4308. if(pcpMin)
  4309. {
  4310. tp.SetCp(cpMin);
  4311. if(!tp.IsAtBOWord()) // cpMin not at BOW:
  4312. cpMin += tp.FindWordBreak(WB_MOVEWORDLEFT); // go there
  4313. *pcpMin = cpMin;
  4314. Assert(cpMin >= 0 && cpMin <= GetTextLength());
  4315. }
  4316. if(pcpMost)
  4317. {
  4318. tp.SetCp(cpMost);
  4319. if (!tp.IsAtBOWord() || // If not at word strt
  4320. (!cpMost || pcpMin) && cpMin == cpMost) // or there but need
  4321. { // to expand IP,
  4322. cch = tp.FindWordBreak(WB_MOVEWORDRIGHT); // move to next word
  4323. if(cch && type == FW_EXACT) // If moved and want
  4324. { // word proper, move
  4325. cch1 = tp.FindWordBreak(WB_LEFTBREAK); // back to end of
  4326. if(cch + cch1 > 0) // preceding word
  4327. cch += cch1; // Only do so if were
  4328. } // not already at end
  4329. cpMost += cch;
  4330. }
  4331. *pcpMost = cpMost;
  4332. Assert(cpMost >= 0 && cpMost <= GetTextLength());
  4333. Assert(cpMin <= cpMost);
  4334. }
  4335. }
  4336. /*
  4337. * CTxtRange::FindAttributes(pcpMin, pcpMost, dwMask)
  4338. *
  4339. * @mfunc
  4340. * Set *pcpMin = closest attribute-combo cpMin <lt>= range cpMin
  4341. * Set *pcpMost = closest attribute-combo cpMost <gt>= range cpMost
  4342. * The attribute combo is given by Unit and is any OR combination of
  4343. * TOM attributes, e.g., tomBold, tomItalic, or things like
  4344. * tomBold | tomItalic. The combo is found if any of the attributes
  4345. * is present.
  4346. *
  4347. * @devnote
  4348. * Plan to add other logical combinations: tomAND, tomExact
  4349. */
  4350. void CTxtRange::FindAttributes (
  4351. LONG *pcpMin, //@parm Out parm for bounding-sentence cpMin
  4352. LONG *pcpMost, //@parm Out parm for bounding-sentence cpMost
  4353. LONG Unit) const //@parm TOM attribute mask
  4354. {
  4355. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CTxtRange::FindAttributes");
  4356. LONG cch;
  4357. LONG cpMin, cpMost;
  4358. DWORD dwMask = Unit & ~0x80000000; // Kill sign bit
  4359. CCFRunPtr rp(*this);
  4360. Assert(Unit < 0);
  4361. GetRange(cpMin, cpMost);
  4362. if(!rp.IsValid()) // No CF runs instantiated
  4363. {
  4364. if(rp.IsMask(dwMask)) // Applies to default CF
  4365. {
  4366. if(pcpMin)
  4367. *pcpMin = 0;
  4368. if(pcpMost)
  4369. *pcpMost = GetTextLength();
  4370. }
  4371. return;
  4372. }
  4373. // Start at cpMin
  4374. if(_cch > 0)
  4375. rp.Move(-_cch);
  4376. // Go backward until we don't match dwMask
  4377. if(pcpMin)
  4378. {
  4379. rp.AdjustBackward();
  4380. while(rp.IsMask(dwMask) && rp.GetIch())
  4381. {
  4382. cpMin -= rp.GetIch();
  4383. rp.Move(-rp.GetIch());
  4384. rp.AdjustBackward();
  4385. }
  4386. *pcpMin = cpMin;
  4387. }
  4388. // Now go forward from cpMost until we don't match dwMask
  4389. if(pcpMost)
  4390. {
  4391. rp.Move(cpMost - cpMin);
  4392. rp.AdjustForward(); // In case cpMin = cpMost
  4393. cch = rp.GetCchLeft();
  4394. while(rp.IsMask(dwMask) && cch)
  4395. {
  4396. cpMost += cch;
  4397. rp.Move(cch);
  4398. cch = rp.GetCchLeft();
  4399. }
  4400. *pcpMost = cpMost;
  4401. }
  4402. }
  4403. /*
  4404. * CTxtRange::CountCells(cCell, cchMax)
  4405. *
  4406. * @mfunc
  4407. * Count characters up to <p cRun> cells away or <p cchMax> chars,
  4408. * whichever comes first. Helper function for CRchTxtPtr::UnitCounter().
  4409. *
  4410. * @rdesc
  4411. * Return the signed cch counted and set <p cRun> equal to count of cells
  4412. * actually counted.
  4413. */
  4414. LONG CTxtRange::CountCells (
  4415. LONG & cCell, //@parm Count of cells to get cch for
  4416. LONG cchMax) //@parm Maximum char count
  4417. {
  4418. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtRange::CountCells");
  4419. LONG cch = 0;
  4420. LONG cpSave = GetCp();
  4421. LONG cp;
  4422. LONG j = cCell;
  4423. Assert(!_cch);
  4424. while(j && cch < cchMax && InTable())
  4425. {
  4426. if(cCell > 0) // Move forward
  4427. {
  4428. if(GetPrevChar() == CELL)
  4429. Move(1, FALSE);
  4430. FindCell(NULL, &cp); // Find cp at end of cell
  4431. j--;
  4432. cch = cp - cpSave;
  4433. }
  4434. else // Move backward
  4435. {
  4436. if(_rpTX.IsAtStartOfCell())
  4437. {
  4438. if(GetPrevChar() == CELL)
  4439. Move(-1, FALSE); // Backup in front of CELL
  4440. else
  4441. {
  4442. Move(-2, FALSE); // Backup in front of TRD
  4443. if(!_rpTX.IsAfterTRD(ENDFIELD))
  4444. {
  4445. Move(2, FALSE); // At start of table: restore
  4446. break; // position and quit
  4447. }
  4448. Move(-3, FALSE); // Backup before CELL TRD
  4449. }
  4450. }
  4451. FindCell(&cp, NULL); // Find cp at start of cell
  4452. j++;
  4453. cch = cpSave - cp;
  4454. }
  4455. SetCp(cp, FALSE); // Move to cell start/end
  4456. }
  4457. cCell -= j; // Subtract any cells not bypassed
  4458. return GetCp() - cpSave;
  4459. }
  4460. /*
  4461. * CTxtRange::CalcTextLenNotInRange()
  4462. *
  4463. * @mfunc
  4464. * Helper function that calculates the total length of text
  4465. * excluding the current range.
  4466. *
  4467. * @comm
  4468. * Used for limit testing. The problem being solved is that
  4469. * the range can contain the final EOP which is not included
  4470. * in the adjusted text length.
  4471. */
  4472. LONG CTxtRange::CalcTextLenNotInRange()
  4473. {
  4474. LONG cchAdjLen = GetPed()->GetAdjustedTextLength();
  4475. LONG cchLen = cchAdjLen - abs(_cch);
  4476. LONG cpMost = GetCpMost();
  4477. if (cpMost > cchAdjLen)
  4478. {
  4479. // Selection extends beyond adjusted length. Put amount back in the
  4480. // selection as it has become too small by the difference.
  4481. cchLen += cpMost - cchAdjLen;
  4482. }
  4483. return cchLen;
  4484. }
  4485. ////////////////////////// Outline Support //////////////////////////////////
  4486. /*
  4487. * CTxtRange::Promote(lparam, publdr)
  4488. *
  4489. * @mfunc
  4490. * Promote selected text according to:
  4491. *
  4492. * LOWORD(lparam) == 0 ==> promote to body-text
  4493. * LOWORD(lparam) != 0 ==> promote/demote current selection by
  4494. * LOWORD(lparam) levels
  4495. * @rdesc
  4496. * TRUE iff promotion occurred
  4497. *
  4498. * @devnote
  4499. * Changes this range
  4500. */
  4501. HRESULT CTxtRange::Promote (
  4502. LPARAM lparam, //@parm 0 to body, < 0 demote, > 0 promote
  4503. IUndoBuilder *publdr) //@parm undo builder to receive antievents
  4504. {
  4505. TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtRange::Promote");
  4506. if(abs(lparam) >= NHSTYLES)
  4507. return E_INVALIDARG;
  4508. if(publdr)
  4509. publdr->StopGroupTyping();
  4510. if(_cch > 0) // Point at cpMin
  4511. FlipRange();
  4512. LONG cchText = GetTextLength();
  4513. LONG cpEnd = GetCpMost();
  4514. LONG cpMin, cpMost;
  4515. BOOL fHeading = TRUE; // Default heading in range
  4516. HRESULT hr;
  4517. LONG Level;
  4518. LONG nHeading = NHSTYLES; // Setup to find any heading
  4519. CParaFormat PF;
  4520. const CParaFormat *pPF;
  4521. CPFRunPtr rp(*this);
  4522. LONG cch = rp.FindHeading(abs(_cch), nHeading);
  4523. WORD wEffects;
  4524. if(!lparam) // Demote to subtext
  4525. {
  4526. if(cch) // Already in subtext so don't
  4527. return S_FALSE; // need to demote
  4528. CTxtPtr tp(_rpTX);
  4529. if(!tp.IsAfterEOP())
  4530. cch = tp.FindEOP(tomBackward);
  4531. nHeading = 1;
  4532. if(tp.GetCp()) // Get previous level and convert
  4533. { // to heading to set up
  4534. rp.Move(cch); // following Level code
  4535. rp.AdjustBackward();
  4536. nHeading = rp.GetOutlineLevel()/2 + 1;
  4537. }
  4538. }
  4539. else if(cch == tomBackward) // No heading in range
  4540. { // Set up to promote to
  4541. nHeading = rp.GetOutlineLevel()/2 // heading
  4542. + (lparam > 0 ? 2 : 1);
  4543. fHeading = FALSE; // Signal no heading in range
  4544. }
  4545. else if(cch) // Range starts in subtext
  4546. Move(cch, TRUE); // Bypass initial nonheading
  4547. Level = 2*(nHeading - 1); // Heading level
  4548. PF._bOutlineLevel = (BYTE)(Level | 1); // Corresponding subtext level
  4549. if (!Level && lparam > 0 || // Can't promote Heading 1
  4550. nHeading == NHSTYLES && lparam < 0) // or demote Heading 9
  4551. {
  4552. return S_FALSE;
  4553. }
  4554. do
  4555. {
  4556. _cch = 0;
  4557. Level -= long(2*lparam); // Promote Level
  4558. pPF = GetPF();
  4559. wEffects = pPF->_wEffects;
  4560. if(pPF->_bOutlineLevel & 1) // Handle contiguous text in
  4561. { // one fell swoop
  4562. cch = fHeading ? _rpPF.GetCchLeft() : cpEnd - GetCp();
  4563. if(cch > 0)
  4564. Move(cch, TRUE);
  4565. }
  4566. Expander(tomParagraph, TRUE, NULL, &cpMin, &cpMost);
  4567. if((unsigned)Level < 2*NHSTYLES)
  4568. { // Promoted Level is valid
  4569. DWORD dwMask = PFM_OUTLINELEVEL;// Default setting subtext level
  4570. if(!(Level & 1) && lparam) // Promoting or demoting heading
  4571. { // Preserve collapse status
  4572. PF._wEffects = Level ? wEffects : 0; // H1 is aways expanded
  4573. PF._sStyle = (SHORT)(-Level/2 + STYLE_HEADING_1);
  4574. PF._bOutlineLevel = (BYTE)(Level | 1);// Set up subtext
  4575. dwMask = PFM_STYLE + PFM_COLLAPSED;
  4576. }
  4577. else if(!lparam) // Changing heading to subtext
  4578. { // or uncollapsing subtext
  4579. PF._wEffects = 0; // Turn off collapsed
  4580. PF._sStyle = STYLE_NORMAL;
  4581. dwMask = PFM_STYLE + PFM_OUTLINELEVEL + PFM_COLLAPSED;
  4582. }
  4583. hr = SetParaStyle(&PF, publdr, dwMask);
  4584. if(hr != NOERROR)
  4585. return hr;
  4586. }
  4587. if(GetCp() >= cchText) // Have handled last PF run
  4588. break;
  4589. Assert(_cch > 0); // Para/run should be selected
  4590. pPF = GetPF(); // Points at next para
  4591. Level = pPF->_bOutlineLevel;
  4592. } // Iterate until past range &
  4593. while((Level & 1) || fHeading && // any subtext that follows
  4594. (GetCp() < cpEnd || pPF->_wEffects & PFE_COLLAPSED));
  4595. return NOERROR;
  4596. }
  4597. /*
  4598. * CTxtRange::ExpandOutline(Level, fWholeDocument)
  4599. *
  4600. * @mfunc
  4601. * Expand outline according to Level and fWholeDocument. Wraps
  4602. * OutlineExpander() helper function and updates selection/view
  4603. *
  4604. * @rdesc
  4605. * NOERROR if success
  4606. */
  4607. HRESULT CTxtRange::ExpandOutline(
  4608. LONG Level, //@parm If < 0, collapse; else expand, etc.
  4609. BOOL fWholeDocument) //@parm If TRUE, whole document
  4610. {
  4611. if (!IsInOutlineView())
  4612. return NOERROR;
  4613. HRESULT hres = OutlineExpander(Level, fWholeDocument);
  4614. if(hres != NOERROR)
  4615. return hres;
  4616. GetPed()->TxNotify(EN_PARAGRAPHEXPANDED, NULL);
  4617. return GetPed()->UpdateOutline();
  4618. }
  4619. /*
  4620. * CTxtRange::OutlineExpander(Level, fWholeDocument)
  4621. *
  4622. * @mfunc
  4623. * Expand/collapse outline for this range according to Level
  4624. * and fWholeDocument. If fWholeDocument is TRUE, then
  4625. * 1 <= Level <= NHSTYLES collapses all headings with numbers
  4626. * greater than Level and collapses all nonheadings. Level = -1
  4627. * expands all.
  4628. *
  4629. * fWholeDocument = FALSE expands/collapses (Level > 0 or < 0)
  4630. * paragraphs depending on whether an EOP and heading are included
  4631. * in the range. If Level = 0, toggle heading's collapsed status.
  4632. *
  4633. * @rdesc
  4634. * (change made) ? NOERROR : S_FALSE
  4635. */
  4636. HRESULT CTxtRange::OutlineExpander(
  4637. LONG Level, //@parm If < 0, collapse; else expand, etc.
  4638. BOOL fWholeDocument) //@parm If TRUE, whole document
  4639. {
  4640. CParaFormat PF;
  4641. if(fWholeDocument) // Apply to whole document
  4642. {
  4643. if (IN_RANGE(1, Level, NHSTYLES) || // Collapse to heading
  4644. Level == -1) // -1 means all
  4645. {
  4646. Set(0, tomBackward); // Select whole document
  4647. PF._sStyle = (SHORT)(STYLE_COMMAND + (BYTE)Level);
  4648. SetParaFormat(&PF, NULL, PFM_STYLE, 0);// No undo
  4649. return NOERROR;
  4650. }
  4651. return S_FALSE; // Nothing happened (illegal
  4652. } // arg)
  4653. // Expand/Collapse for Level positive/negative, respectively
  4654. LONG cpMin, cpMost; // Get range cp's
  4655. LONG cchMax = GetRange(cpMin, cpMost);
  4656. if(_cch > 0) // Ensure cpMin is active
  4657. FlipRange(); // for upcoming rp and tp
  4658. LONG nHeading = NHSTYLES; // Setup to find any heading
  4659. LONG nHeading1;
  4660. CTxtEdit *ped = GetPed();
  4661. CPFRunPtr rp(*this);
  4662. LONG cch = rp.FindHeading(cchMax, nHeading);
  4663. if(cch == tomBackward) // No heading found within range
  4664. return S_FALSE; // Do nothing
  4665. Assert(cch <= cchMax && (Level || !cch)); // cch is count up to heading
  4666. CTxtPtr tp(_rpTX);
  4667. cpMin += cch; // Bypass any nonheading text
  4668. tp.Move(cch); // at start of range
  4669. // If toggle collapse or if range contains an EOP,
  4670. // collapse/expand all subordinates
  4671. cch = tp.FindEOP(tomForward); // Find next para
  4672. if(!cch)
  4673. return NOERROR;
  4674. if(!Level || cch < -_cch) // Level = 0 or EOP in range
  4675. {
  4676. if(!Level) // Toggle collapse status
  4677. {
  4678. LONG cchLeft = rp.GetCchLeft();
  4679. if (cch < cchLeft || !rp.NextRun() ||
  4680. nHeading == STYLE_HEADING_1 - rp.GetStyle() + 1)
  4681. {
  4682. return NOERROR; // Next para has same heading
  4683. }
  4684. Assert(cch == cchLeft);
  4685. Level = rp.IsCollapsed();
  4686. rp.Move(-cchLeft);
  4687. }
  4688. PF._wEffects = Level > 0 ? 0 : PFE_COLLAPSED;
  4689. while(cpMin < cpMost)
  4690. { // We're at a heading
  4691. tp.SetCp(cpMin);
  4692. cch = tp.FindEOP(-_cch);
  4693. cpMin += cch; // Bypass it
  4694. if(!rp.Move(cch)) // Point at next para
  4695. break; // No more, we're done
  4696. nHeading1 = nHeading; // Setup to find heading <= nHeading
  4697. cch = rp.FindHeading(tomForward, nHeading1);
  4698. if(cch == tomBackward) // No more higher headings
  4699. cch = GetTextLength() - cpMin; // Format to end of text
  4700. Set(cpMin, -cch); // Collapse/expand up to here
  4701. SetParaFormat(&PF, NULL, PFM_COLLAPSED, 0);
  4702. cpMin += cch; // Move past formatted area
  4703. nHeading = nHeading1; // Update nHeading to possibly
  4704. } // lower heading #
  4705. return NOERROR;
  4706. }
  4707. // Range contains no EOP: expand/collapse deepest level.
  4708. // If collapsing, collapse all nonheading text too. Expand
  4709. // nonheading text only if all subordinate levels are expanded.
  4710. BOOL fCollapsed;
  4711. LONG nHeadStart, nHeadDeepNC, nHeadDeep;
  4712. LONG nNonHead = -1; // No nonHeading found yet
  4713. const CParaFormat *pPF;
  4714. cpMin = tp.GetCp(); // Point at start of
  4715. cpMost = cpMin; // next para
  4716. pPF = ped->GetParaFormat(_rpPF.GetFormat());
  4717. nHeading = pPF->_bOutlineLevel;
  4718. Assert(!(nHeading & 1) && // Must start with a heading
  4719. !(pPF->_wEffects & PFE_COLLAPSED)); // that isn't collapsed
  4720. nHeadStart = nHeading/2 + 1; // Convert outline level to
  4721. nHeadDeep = nHeadDeepNC = nHeadStart; // heading number
  4722. while(cch) // Determine deepest heading
  4723. { // and deepest collapsed
  4724. rp.Move(cch); // heading
  4725. pPF = ped->GetParaFormat(rp.GetFormat());
  4726. fCollapsed = pPF->_wEffects & PFE_COLLAPSED;
  4727. nHeading = pPF->_bOutlineLevel;
  4728. if(nHeading & 1) // Text found
  4729. { // Set nNonHead > 0 if
  4730. nNonHead = fCollapsed; // collapsed; else 0
  4731. cch = rp.GetCchLeft(); // Zip to end of contiguous
  4732. tp.Move(cch); // text paras
  4733. }
  4734. else // It's a heading
  4735. {
  4736. nHeading = nHeading/2 + 1; // Convert to heading number
  4737. if(nHeading <= nHeadStart) // If same or shallower as
  4738. break; // start heading we're done
  4739. // Update deepest and deepest nonCollapsed heading #'s
  4740. nHeadDeep = max(nHeadDeep, nHeading);
  4741. if(!fCollapsed)
  4742. nHeadDeepNC = max(nHeadDeepNC, nHeading);
  4743. cch = tp.FindEOP(tomForward); // Go to next paragraph
  4744. }
  4745. cpMost = tp.GetCp(); // Include up to it
  4746. }
  4747. PF._sStyle = (SHORT)(STYLE_COMMAND + nHeadDeepNC);
  4748. if(Level > 0) // Expand
  4749. {
  4750. if(nHeadDeepNC < nHeadDeep) // At least one collapsed
  4751. PF._sStyle++; // heading: expand shallowest
  4752. else // All heads expanded: do others
  4753. PF._sStyle = (unsigned short) (STYLE_COMMAND + 0xFF);
  4754. } // In any case, expand nonheading
  4755. else if(nNonHead) // Collapse. If text collapsed
  4756. { // or missing, do headings
  4757. if(nHeadDeepNC == nHeadStart)
  4758. return S_FALSE; // Everything already collapsed
  4759. PF._sStyle--; // Collapse to next shallower
  4760. } // heading
  4761. Set(cpMin, cpMin - cpMost); // Select range to change
  4762. SetParaFormat(&PF, NULL, PFM_STYLE, 0); // No undo
  4763. return NOERROR;
  4764. }
  4765. /*
  4766. * CTxtRange::CheckOutlineLevel(publdr)
  4767. *
  4768. * @mfunc
  4769. * If the paragraph style at this range isn't a heading, make
  4770. * sure its outline level is compatible with the preceeding one
  4771. */
  4772. void CTxtRange::CheckOutlineLevel(
  4773. IUndoBuilder *publdr) //@parm Undo context for this operation
  4774. {
  4775. LONG LevelBackward, LevelForward;
  4776. CPFRunPtr rp(*this);
  4777. Assert(!_cch);
  4778. rp.AdjustBackward();
  4779. LevelBackward = rp.GetOutlineLevel() | 1; // Nonheading level corresponding
  4780. // to previous PF run
  4781. rp.AdjustForward();
  4782. LevelForward = rp.GetOutlineLevel();
  4783. if (!(LevelForward & 1) || // Any heading can follow
  4784. LevelForward == LevelBackward) // any style. Also if
  4785. { // forward level is correct,
  4786. return; // return
  4787. }
  4788. LONG cch; // One or more nonheadings
  4789. LONG lHeading = NHSTYLES; // with incorrect outline
  4790. CParaFormat PF; // levels follow
  4791. PF._bOutlineLevel = (BYTE)LevelBackward; // level
  4792. cch = rp.FindHeading(tomForward, lHeading); // Find next heading
  4793. if(cch == tomBackward)
  4794. cch = tomForward;
  4795. Set(GetCp(), -cch); // Select all nonheading text
  4796. SetParaFormat(&PF, publdr, PFM_OUTLINELEVEL, 0);// Change its outline level
  4797. Set(GetCp(), 0); // Restore range to IP
  4798. }
  4799. #if defined(DEBUG) && !defined(NOFULLDEBUG)
  4800. /*
  4801. * CTxtRange::::DebugFont (void)
  4802. *
  4803. * @mfunc
  4804. * Dump out the character and Font info for current selection.
  4805. */
  4806. void CTxtRange::DebugFont (void)
  4807. {
  4808. LONG ch;
  4809. LONG cpMin, cpMost;
  4810. LONG cch = GetRange(cpMin, cpMost);
  4811. LONG i;
  4812. char szTempBuf[64];
  4813. CTxtEdit *ped = GetPed();
  4814. const WCHAR *wszFontname;
  4815. const CCharFormat *CF; // Temporary CF
  4816. const WCHAR *GetFontName(LONG iFont);
  4817. char szTempPath[MAX_PATH] = "\0";
  4818. DWORD cchLength;
  4819. HANDLE hfileDump;
  4820. DWORD cbWritten;
  4821. LONG cchSave = _cch; // Save this range's cch
  4822. LONG cpSave = GetCp(); // and cp so we can restore in case of error
  4823. SideAssert(cchLength = GetTempPathA(MAX_PATH, szTempPath));
  4824. // append trailing backslash if neccessary
  4825. if(szTempPath[cchLength - 1] != '\\')
  4826. {
  4827. szTempPath[cchLength] = '\\';
  4828. szTempPath[cchLength + 1] = 0;
  4829. }
  4830. strcat(szTempPath, "DumpFontInfo.txt");
  4831. SideAssert(hfileDump = CreateFileA(szTempPath,
  4832. GENERIC_WRITE,
  4833. FILE_SHARE_READ,
  4834. NULL,
  4835. CREATE_ALWAYS,
  4836. FILE_ATTRIBUTE_NORMAL,
  4837. NULL));
  4838. if(_cch > 0) // start from cpMin
  4839. FlipRange();
  4840. CFormatRunPtr rp(_rpCF);
  4841. for (i=0; i <= cch; i++)
  4842. {
  4843. LONG iFormat;
  4844. if (GetChar(&ch) != NOERROR)
  4845. break;
  4846. if (ch <= 0x07f)
  4847. sprintf(szTempBuf, "Char= '%c'\r\n", (char)ch);
  4848. else
  4849. sprintf(szTempBuf, "Char= 0x%x\r\n", ch);
  4850. OutputDebugStringA(szTempBuf);
  4851. if (hfileDump)
  4852. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4853. iFormat = rp.GetFormat();
  4854. CF = ped->GetCharFormat(iFormat);
  4855. Assert(CF);
  4856. sprintf(szTempBuf, "Font iFormat= %d, CharRep = %d, Size= %d\r\nName= ",
  4857. iFormat, CF->_iCharRep, CF->_yHeight);
  4858. OutputDebugStringA(szTempBuf);
  4859. if (hfileDump)
  4860. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4861. wszFontname = GetFontName(CF->_iFont);
  4862. if (wszFontname)
  4863. {
  4864. if (*wszFontname <= 0x07f)
  4865. {
  4866. szTempBuf[0] = '\'';
  4867. WCTMB(CP_ACP, 0,
  4868. wszFontname, -1, &szTempBuf[1], sizeof(szTempBuf)-1,
  4869. NULL, NULL, NULL);
  4870. strcat(szTempBuf,"\'");
  4871. OutputDebugStringA(szTempBuf);
  4872. if (hfileDump)
  4873. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4874. }
  4875. else
  4876. {
  4877. for (; *wszFontname; wszFontname++)
  4878. {
  4879. sprintf(szTempBuf, "0x%x,", *wszFontname);
  4880. OutputDebugStringA(szTempBuf);
  4881. if (hfileDump)
  4882. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4883. }
  4884. }
  4885. }
  4886. OutputDebugStringA("\r\n");
  4887. if (hfileDump)
  4888. WriteFile(hfileDump, "\r\n", 2, &cbWritten, NULL);
  4889. Move(1, FALSE);
  4890. rp.Move(1);
  4891. }
  4892. // Now dump the doc font info
  4893. CF = ped->GetCharFormat(-1);
  4894. Assert(CF);
  4895. sprintf(szTempBuf, "Default Font iFormat= -1, CharRep= %d, Size= %d\r\nName= ",
  4896. CF->_iCharRep, CF->_yHeight);
  4897. OutputDebugStringA(szTempBuf);
  4898. if (hfileDump)
  4899. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4900. wszFontname = GetFontName(CF->_iFont);
  4901. if (wszFontname)
  4902. {
  4903. if (*wszFontname <= 0x07f)
  4904. {
  4905. szTempBuf[0] = '\'';
  4906. WCTMB(CP_ACP, 0,
  4907. wszFontname, -1, &szTempBuf[1], sizeof(szTempBuf),
  4908. NULL, NULL, NULL);
  4909. strcat(szTempBuf,"\'");
  4910. OutputDebugStringA(szTempBuf);
  4911. if (hfileDump)
  4912. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4913. }
  4914. else
  4915. {
  4916. for (; *wszFontname; wszFontname++)
  4917. {
  4918. sprintf(szTempBuf, "0x%x,", *wszFontname);
  4919. OutputDebugStringA(szTempBuf);
  4920. if (hfileDump)
  4921. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4922. }
  4923. }
  4924. }
  4925. OutputDebugStringA("\r\n");
  4926. if (hfileDump)
  4927. WriteFile(hfileDump, "\r\n", 2, &cbWritten, NULL);
  4928. if (ped->IsRich())
  4929. {
  4930. if (ped->fUseUIFont())
  4931. sprintf(szTempBuf, "Rich Text with UI Font");
  4932. else
  4933. sprintf(szTempBuf, "Rich Text Control");
  4934. }
  4935. else
  4936. sprintf(szTempBuf, "Plain Text Control");
  4937. OutputDebugStringA(szTempBuf);
  4938. if (hfileDump)
  4939. WriteFile(hfileDump, szTempBuf, strlen(szTempBuf), &cbWritten, NULL);
  4940. OutputDebugStringA("\r\n");
  4941. if (hfileDump)
  4942. WriteFile(hfileDump, "\r\n", 2, &cbWritten, NULL);
  4943. if (hfileDump)
  4944. CloseHandle(hfileDump);
  4945. Set(cpSave, cchSave); // Restore previous selection
  4946. }
  4947. #endif