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.

1718 lines
46 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module RTEXT.CPP - Rich-text ptr class |
  5. *
  6. * This text ptr consists of a plain text ptr (_rpTX), a CCharFormat
  7. * run ptr (_rpCF), and a CParaFormat run ptr (_rpPF). This module
  8. * contains the methods to manipulate this combination of run ptrs
  9. * consistently.
  10. *
  11. * Authors:<nl>
  12. * RichEdit 1.0 code: David R. Fulmer
  13. * Main implementation: Murray Sargent <nl>
  14. * Undo and notification implementations: Alex Gounares <nl>
  15. *
  16. * Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
  17. */
  18. #include "_common.h"
  19. #include "_edit.h"
  20. #include "_frunptr.h"
  21. #include "_rtext.h"
  22. #include "_disp.h"
  23. #include "_select.h"
  24. #include "_m_undo.h"
  25. #include "_antievt.h"
  26. #include "_objmgr.h"
  27. #include "_txtbrk.h"
  28. ASSERTDATA
  29. #define DEBUG_CLASSNAME CRchTxtPtr
  30. #include "_invar.h"
  31. #ifdef DEBUG
  32. /*
  33. * CRchTxtPtr::Invariant
  34. */
  35. BOOL CRchTxtPtr::Invariant( void ) const
  36. {
  37. if (m_InvariantCheckInterval < 1 || m_InvariantCheckInterval > 10)
  38. const_cast<CRchTxtPtr *>(this)->m_InvariantCheckInterval = 10;
  39. const_cast<CRchTxtPtr *>(this)->m_InvariantCheckInterval--;
  40. if (m_InvariantCheckInterval)
  41. return TRUE;
  42. unsigned ch;
  43. LONG cch;
  44. LONG cchLength = GetTextLength();
  45. LONG cp;
  46. _rpTX.Invariant();
  47. _rpCF.Invariant();
  48. _rpPF.Invariant();
  49. if(_rpCF.IsValid())
  50. {
  51. cp = _rpCF.CalculateCp();
  52. cch = _rpCF.CalcTextLength();
  53. Assert(GetCp() == cp && cchLength == cch);
  54. Assert(!_rpCF._iRun || GetPed()->IsBiDi() || _rpCF.GetRun(0)->_iFormat != _rpCF.GetRun(-1)->_iFormat);
  55. }
  56. if(_rpPF.IsValid())
  57. {
  58. cp = _rpPF.CalculateCp();
  59. cch = _rpPF.CalcTextLength();
  60. Assert(GetCp() == cp && cchLength == cch);
  61. CTxtPtr tp(_rpTX);
  62. tp.AdvanceCp(_rpPF.GetCchLeft() - 1);
  63. ch = tp.GetChar();
  64. if(!IsASCIIEOP(ch))
  65. {
  66. _rpTX.MoveGapToEndOfBlock(); // Make it easier to see
  67. AssertSz(FALSE, // what's going on
  68. "CRchTxtPtr::Invariant: PF run doesn't end with EOP");
  69. }
  70. #ifdef EXTREME_CHECKING
  71. // We don't do this check normally as it is _extremely_ slow.
  72. // However, it's very useful for catching para-format run problems
  73. // Make sure each para format run ends on a paragraph mark!
  74. CFormatRunPtr rpPF(_rpPF);
  75. rpPF.BindToCp(0);
  76. tp.BindToCp(0);
  77. do
  78. {
  79. tp.AdvanceCp(rpPF.GetRun(0)->_cch);
  80. if(!tp.IsAfterEOP())
  81. {
  82. AssertSz(0, "ParaFormat Run not aligned along paragraphs!");
  83. }
  84. } while( rpPF.NextRun() );
  85. #endif // EXTREME_CHECKING
  86. }
  87. return TRUE;
  88. }
  89. #endif // DEBUG
  90. //======================= CRchTxtPtr constructors ========================================
  91. CRchTxtPtr::CRchTxtPtr(CTxtEdit *ped) :
  92. _rpTX(ped, 0), _rpCF(NULL), _rpPF(NULL)
  93. {
  94. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::CRchTxtPtr");
  95. InitRunPtrs();
  96. }
  97. CRchTxtPtr::CRchTxtPtr(CTxtEdit *ped, LONG cp) :
  98. _rpTX(ped, cp), _rpCF(NULL), _rpPF(NULL)
  99. {
  100. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::CRchTxtPtr");
  101. InitRunPtrs();
  102. }
  103. CRchTxtPtr::CRchTxtPtr (const CRchTxtPtr& rtp) :
  104. _rpTX(rtp._rpTX), _rpCF(rtp._rpCF), _rpPF(rtp._rpPF)
  105. {
  106. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::CRchTxtPtr");
  107. _rpCF.AdjustForward(); // In case rtp is adjusted backward...
  108. _rpPF.AdjustForward();
  109. }
  110. CRchTxtPtr::CRchTxtPtr (const CDisplay * pdp) :
  111. _rpTX(pdp->GetPed(), 0), _rpCF(NULL), _rpPF(NULL)
  112. {
  113. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::CRchTxtPtr");
  114. InitRunPtrs();
  115. }
  116. /*
  117. * CRchTxtPtr::Advance(cch)
  118. *
  119. * @mfunc
  120. * Move this rich-text ptr forward <p cch> characters. If <p cch>
  121. * <lt> 0, move backward by -<p cch> characters.
  122. *
  123. * @rdesc
  124. * cch actually moved
  125. *
  126. */
  127. LONG CRchTxtPtr::Advance(
  128. LONG cch) // @parm count of characters to move - may be <lt> 0
  129. {
  130. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::Advance");
  131. if( cch != 0 )
  132. {
  133. cch = _rpTX.AdvanceCp(cch);
  134. _rpCF.AdvanceCp(cch);
  135. _rpPF.AdvanceCp(cch);
  136. _TEST_INVARIANT_
  137. }
  138. return cch;
  139. }
  140. /*
  141. * CRchTxtPtr::AdvanceCRLF()
  142. *
  143. * @mfunc
  144. * Advance this text ptr one char, treating CRLF as a single char.
  145. *
  146. * @rdesc
  147. * cch actually moved
  148. */
  149. LONG CRchTxtPtr::AdvanceCRLF()
  150. {
  151. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CRchTxtPtr::AdvanceCRLF");
  152. LONG cch = _rpTX.AdvanceCpCRLF();
  153. _rpPF.AdvanceCp(cch);
  154. _rpCF.AdvanceCp(cch);
  155. return cch;
  156. }
  157. /*
  158. * CRchTxtPtr::SnapToCluster(INT iDirection)
  159. *
  160. * @mfunc
  161. * If this text ptr is not at cluster boundary, move it to the closest one.
  162. *
  163. * @rdesc
  164. * cch actually moved
  165. */
  166. LONG CRchTxtPtr::SnapToCluster(INT iDirection)
  167. {
  168. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CRchTxtPtr::SnapToCluster");
  169. LONG cch = 0;
  170. LONG cp;
  171. if (GetPed()->_pbrk)
  172. {
  173. if (iDirection >= 0)
  174. {
  175. LONG cpEnd = GetPed()->GetAdjustedTextLength();
  176. while ((cp = GetCp()) < cpEnd && !GetPed()->_pbrk->CanBreakCp(BRK_CLUSTER, cp))
  177. cch += AdvanceCRLF();
  178. }
  179. else
  180. {
  181. while ((cp = GetCp()) > 0 && !GetPed()->_pbrk->CanBreakCp(BRK_CLUSTER, cp))
  182. cch += BackupCRLF();
  183. }
  184. }
  185. return cch;
  186. }
  187. /*
  188. * CRchTxtPtr::BackupCRLF()
  189. *
  190. * @mfunc
  191. * Backup this text ptr one char, treating CRLF as a single char.
  192. *
  193. * @rdesc
  194. * cch actually moved
  195. */
  196. LONG CRchTxtPtr::BackupCRLF(
  197. BOOL fDiacriticCheck)
  198. {
  199. TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEINTERN, "CRchTxtPtr::BackupCRLF");
  200. LONG cch = _rpTX.BackupCpCRLF(fDiacriticCheck);
  201. _rpPF.AdvanceCp(cch);
  202. _rpCF.AdvanceCp(cch);
  203. return cch;
  204. }
  205. /*
  206. * CRchTxtPtr::ValidateCp(&cp)
  207. *
  208. * @mfunc
  209. * If <p cp> <lt> 0, set it to 0; if it's <gt> text length, set it to
  210. * text length.
  211. */
  212. void CRchTxtPtr::ValidateCp(
  213. LONG &cp) const // @parm new cp for this text ptr
  214. {
  215. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::ValidateCp");
  216. LONG cchT = GetTextLength();
  217. cp = min(cp, cchT); // Be sure cp is valid
  218. cp = max(cp, 0);
  219. }
  220. /*
  221. * CRchTxtPtr::SetCp(cp)
  222. *
  223. * @mfunc
  224. * Set this rich text ptr's cp to cp
  225. */
  226. LONG CRchTxtPtr::SetCp(
  227. LONG cp) // @parm new cp for this text ptr
  228. {
  229. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::SetCp");
  230. CRchTxtPtr::Advance(cp - GetCp());
  231. return GetCp();
  232. }
  233. /* CRchTxtPtr::GetIchRunXX() and CRchTxtPtr::GetCchRunXX()
  234. *
  235. * @mfunc
  236. * Text-run management to retrieve current text run cch and offset
  237. *
  238. * @rdesc
  239. * current run ich or cch
  240. *
  241. * @devnote
  242. * Use of queries like _rpCF.IsValid() instead of an inclusive fRich
  243. * allows rich-text formatting to be applied per rich-text category,
  244. * e.g., CHARFORMATs, but not necessarily PARAFORMATs. If the rp isn't
  245. * valid, _cp is used for ich and the document length is used for cch,
  246. * i.e., the values for a document describable by a single plain-text run
  247. */
  248. LONG CRchTxtPtr::GetIchRunCF()
  249. {
  250. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetIchRunCF");
  251. return _rpCF.IsValid() ? _rpCF.GetIch() : GetCp();
  252. }
  253. LONG CRchTxtPtr::GetIchRunPF()
  254. {
  255. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetIchRunPF");
  256. return _rpPF.IsValid() ? _rpPF.GetIch() : GetCp();
  257. }
  258. LONG CRchTxtPtr::GetCchRunCF()
  259. {
  260. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetCchRunCF");
  261. return _rpCF.IsValid() ? _rpCF.GetRun(0)->_cch : GetTextLength();
  262. }
  263. /* CRchTxtPtr::GetCchLeftRunCF() / GetCchLeftRunPF()
  264. *
  265. * @mfunc
  266. * Return cch left in run, i.e., cchRun - ich
  267. *
  268. * @rdesc
  269. * cch left in run
  270. */
  271. LONG CRchTxtPtr::GetCchLeftRunCF()
  272. {
  273. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetCchLeftRunCF");
  274. return _rpCF.IsValid()
  275. ? _rpCF.GetCchLeft() : GetTextLength() - GetCp();
  276. }
  277. LONG CRchTxtPtr::GetCchLeftRunPF()
  278. {
  279. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetCchLeftRunPF");
  280. return _rpPF.IsValid()
  281. ? _rpPF.GetCchLeft() : GetTextLength() - GetCp();
  282. }
  283. /*
  284. * CRchTxtPtr::FindText(cpMost, dwFlags, pch, cchToFind)
  285. *
  286. * @mfunc
  287. * Find text in a range starting at this text pointer;
  288. * if found, moves this text pointer to that position.
  289. *
  290. * @rdesc
  291. * character position of first match
  292. * <lt> 0 if no match
  293. *
  294. * @devnote
  295. * Would be easy to match a single format (like Word 6) provided
  296. * cchToFind is nonzero. Else need to search runs (also pretty easy).
  297. * For format-sensitive searches, might be easier to search for matching
  298. * format run first and then within that run search for text.
  299. */
  300. LONG CRchTxtPtr::FindText (
  301. LONG cpMost, // @parm Limit of search; <lt> 0 for end of text
  302. DWORD dwFlags, // @parm FR_MATCHCASE case must match
  303. // FR_WHOLEWORD match must be a whole word
  304. TCHAR const *pch, // @parm Text to search for
  305. LONG cchToFind) // @parm Length of text to search for
  306. {
  307. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::FindText");
  308. _TEST_INVARIANT_
  309. LONG cpSave = GetCp();
  310. LONG cpMatch = _rpTX.FindText(cpMost, dwFlags, pch, cchToFind);
  311. if(cpMatch >= 0) // cpMatch = -1 means "not found"
  312. SetRunPtrs(GetCp(), cpSave);
  313. // possible code for format-dependent Finds
  314. return cpMatch;
  315. }
  316. /*
  317. * CRchTxtPtr::GetCF()/GetPF()
  318. *
  319. * @mfunc
  320. * Return ptr to CCharFormat/CParaFormat at this text ptr. If no CF/PF runs
  321. * are allocated, then return ptr to default format
  322. *
  323. * @rdesc
  324. * Ptr to CCharFormat/CParaFormat at this text ptr
  325. */
  326. const CCharFormat* CRchTxtPtr::GetCF() const
  327. {
  328. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetCF");
  329. return ((CTxtArray *)_rpTX._pRuns)->GetCharFormat(_rpCF.GetFormat());
  330. }
  331. const CParaFormat* CRchTxtPtr::GetPF() const
  332. {
  333. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetPF");
  334. return ((CTxtArray *)_rpTX._pRuns)->GetParaFormat(_rpPF.GetFormat());
  335. }
  336. /*
  337. * CRchTxtPtr::ReplaceRange(cchOld, cchNew, *pch, pcpFirstRecalc, publdr,
  338. * iFormat, pcchMove, dwFlags)
  339. * @mfunc
  340. * Replace a range of text at this text pointer using CCharFormat iFormat
  341. * and updating other text runs as needed
  342. *
  343. * @rdesc
  344. * Count of new characters added
  345. *
  346. * @devnote
  347. * Moves this text pointer to end of replaced text.
  348. * May move text block and formatting arrays.
  349. */
  350. LONG CRchTxtPtr::ReplaceRange(
  351. LONG cchOld, //@parm length of range to replace
  352. // (<lt> 0 means to end of text)
  353. LONG cchNew, //@parm length of replacement text
  354. TCHAR const *pch, //@parm replacement text
  355. IUndoBuilder *publdr, //@parm Undo bldr to receive antievents
  356. LONG iFormat, //@parm CCharFormat iFormat to use for cchNew
  357. LONG * pcchMove, //@parm Out parm returning cch moved if paradir change
  358. DWORD dwFlags) //@parm Special flags
  359. {
  360. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::ReplaceRange");
  361. LONG cch;
  362. LONG cchEndEOP = 0; // Default 0 final EOP fixup
  363. LONG cchAdvance = 0;
  364. LONG cchBackup = 0;
  365. LONG cchMove = 0; // Default nothing to move
  366. LONG cchNextEOP = cchOld; // cch to next EOP
  367. LONG cchPrevEOP = 0; // cch back to previous EOP
  368. LONG cpFR; // between PF runs
  369. LONG cpSave = GetCp();
  370. LONG cpFormatMin = cpSave; // Used for notifications
  371. LONG cpFormat = cpSave; // Will add cchOld, maybe cchMove
  372. BOOL fParaDirChange = FALSE;
  373. CTxtEdit * ped = GetPed();
  374. IAntiEvent * paeCF = NULL;
  375. IAntiEvent * paePF = NULL;
  376. CNotifyMgr * pnm;
  377. CObjectMgr * pobjmgr;
  378. CFreezeDisplay fd(ped->_pdp); // freeze until itemization is done
  379. _TEST_INVARIANT_
  380. LONG cchEnd = GetTextLength() - GetCp();
  381. if(cchOld < 0 || cchOld > cchEnd)
  382. cchOld = cchEnd;
  383. if(IsRich() && cchOld == cchEnd) // Attempting to delete up
  384. { // thru final EOP
  385. cchEndEOP = (ped->fUseCRLF()) // Calc cch of final EOP
  386. ? CCH_EOD_10 : CCH_EOD_20;
  387. if(cchEndEOP <= cchOld) // Don't delete it unless
  388. cchOld -= cchEndEOP; // converting from 2.0
  389. if(_rpPF.IsValid())
  390. {
  391. _rpPF.AdjustBackward(); // If previous para is a
  392. if(GetPF()->InTable()) // table row, don't delete
  393. cchEndEOP = 0; // final para formatting
  394. }
  395. }
  396. else if(_rpPF.IsValid()) // If PARAFORMATs are enabled,
  397. {
  398. _rpPF.AdjustForward();
  399. if (cchOld)
  400. { // get tp and rp at end of
  401. CFormatRunPtr rp(_rpPF); // range. Need bounding para
  402. CTxtPtr tp(_rpTX); // counts to save valid PF
  403. BOOL fIsAtBOP; // for undo
  404. tp.AdvanceCp(cchOld);
  405. rp.AdvanceCp(cchOld);
  406. cch = 0;
  407. if(tp.IsAfterEOP()) // Range ends with an EOP:
  408. { // get EOP length by
  409. cch = -tp.BackupCpCRLF(); // backing up over it
  410. tp.AdvanceCp(cch); // Advance past EOP
  411. }
  412. cchNextEOP = tp.FindEOP(tomForward); // Get cch up to next EOP
  413. fIsAtBOP = !GetCp() || _rpTX.IsAfterEOP();
  414. if (!fIsAtBOP && cch == cchOld) // Deleting EOP alone before
  415. { // new PARAFORMAT run start
  416. // in para with more than EOP
  417. //bug fix #4978
  418. if (!(dwFlags & RR_NO_EOR_CHECK) &&
  419. (ped->GetParaFormat(rp.GetFormat())->_wEffects |
  420. ped->GetParaFormat(_rpPF.GetFormat())->_wEffects) & PFE_TABLE)
  421. return 0;
  422. if (!rp.GetIch())
  423. {
  424. cchMove = cchNextEOP; // Need to move chars up to
  425. cpFormat += cchMove; // end of next para for
  426. }
  427. }
  428. cchNextEOP += cchOld; // Count from GetCp() to EOP
  429. tp.SetCp(GetCp()); // Back to this ptr's _cp
  430. if(!fIsAtBOP)
  431. cchPrevEOP = tp.FindEOP(tomBackward);// Get cch to start of para
  432. // If deleting from within one format run up to or into another, set
  433. // up to move last para in starting format run into the run following
  434. // the deleted text
  435. LONG iPF1 = rp.GetFormat();
  436. LONG iPF2 = _rpPF.GetFormat();
  437. if(iPF1 != iPF2) // Change of format during
  438. { // deleted text not starting
  439. if(!fIsAtBOP && !cchMove) // at BOP
  440. {
  441. cchMove = cchPrevEOP; // Get cch to start of para
  442. cpFormatMin += cchMove; // in this ptr's run for
  443. } // moving into rp's run
  444. if ((ped->GetParaFormat(iPF1)->_wEffects ^
  445. ped->GetParaFormat(iPF2)->_wEffects) & PFE_RTLPARA)
  446. {
  447. fParaDirChange = TRUE; // Note that para direction
  448. Assert(ped->IsBiDi()); // changed
  449. }
  450. }
  451. }
  452. else if (((ped->GetParaFormat(_rpPF.GetFormat())->_wEffects) & PFE_TABLE) && _rpTX.IsAtEOP() &&
  453. !(dwFlags & RR_NO_EOR_CHECK) /*bug fix #5752*/)
  454. {
  455. // bug fix #5669
  456. // Don't allow pasting at end of row
  457. return 0;
  458. }
  459. }
  460. Assert(cchNew >= 0 && cchOld >= 0);
  461. if(!(cchNew + cchOld)) // Nothing to do (note: all
  462. { // these cch's are >= 0)
  463. if(pcchMove)
  464. *pcchMove = 0;
  465. return 0;
  466. }
  467. // Handle pre-replace range notifications. This method is very
  468. // useful for delayed rendering of data copied to the clipboard.
  469. pnm = ped->GetNotifyMgr();
  470. if(pnm)
  471. {
  472. pnm->NotifyPreReplaceRange((ITxNotify *)this, cpSave, cchOld,
  473. cchNew, cpFormatMin, cpFormat + cchOld);
  474. }
  475. if(iFormat >= 0)
  476. Check_rpCF();
  477. // Get rid of objects first. This let's us guarantee that when we
  478. // insert the objects as part of an undo, the objects themselves are
  479. // restored _after_ their corresponding WCH_EMBEDDINGs have been
  480. // added to the backing store.
  481. if(GetObjectCount())
  482. {
  483. pobjmgr = ped->GetObjectMgr();
  484. Assert(pobjmgr);
  485. pobjmgr->ReplaceRange(cpSave, cchOld, publdr);
  486. }
  487. // If BiDi doc, expand the range to cover the boundaries that guarantee
  488. // the valid state of the BiDi level so we can undo it properly. (wchao)
  489. if(ped->IsBiDi())
  490. {
  491. cchBackup = ExpandRangeFormatting (cchOld + cchEndEOP,
  492. fParaDirChange ? cchMove : 0, cchAdvance);
  493. Assert (cchBackup >= 0);
  494. }
  495. // The anti-events used below are a bit tricky (paeCF && paePF).
  496. // Essentially, this call, CRchTxtPtr::ReplaceRange generates one
  497. // 'combo' anti-event composed of up to two formatting AE's plus
  498. // the text anti-event. These anti-events are combined together
  499. // to prevent ordering problems during undo/redo.
  500. cpFR = ReplaceRangeFormatting(cchOld + cchEndEOP, cchNew + cchEndEOP,
  501. iFormat, publdr, &paeCF, &paePF, cchMove, cchPrevEOP,
  502. cchNextEOP, cchBackup, cchAdvance);
  503. if(cchEndEOP)
  504. {
  505. // If we added in the EOP we need to back up by the EOP so
  506. // that the invariants don't get annoyed and the richtext object
  507. // doesn't get out of sync.
  508. _rpCF.AdvanceCp(-cchEndEOP);
  509. _rpPF.AdvanceCp(-cchEndEOP);
  510. }
  511. if(cpFR < 0)
  512. {
  513. Tracef(TRCSEVERR, "ReplaceRangeFormatting(%ld, %ld, %ld) failed", GetCp(), cchOld, cchNew);
  514. cch = 0;
  515. goto Exit;
  516. }
  517. // As noted above in the call to ReplaceRangeFormatting, the anti-events
  518. // paeCF and paePF, if non-NULL, were generated by ReplaceRangeFormatting.
  519. // In order to solve ordering problems, the anti-event generated by this
  520. // method is actually a combo anti-event of text && formatting AE's.
  521. cch = _rpTX.ReplaceRange(cchOld, cchNew, pch, publdr, paeCF, paePF);
  522. if(cch != cchNew)
  523. {
  524. Tracef(TRCSEVERR, "_rpTX.ReplaceRange(%ld, %ld, ...) failed", cchOld, cchNew);
  525. #ifndef NODUMPFORMATRUNS
  526. // Boy, out of memory or something bad. Dump our formatting and hope
  527. // for the best.
  528. //
  529. // FUTURE: (alexgo) degrade more gracefully than losing formatting
  530. // info.
  531. // Notify every interested party that they should dump their formatting
  532. if(pnm)
  533. pnm->NotifyPreReplaceRange(NULL, CONVERT_TO_PLAIN, 0, 0, 0, 0);
  534. // Tell document to dump its format runs
  535. ped->GetTxtStory()->DeleteFormatRuns();
  536. #endif
  537. goto Exit;
  538. }
  539. AssertSz(!_rpPF.IsValid() || _rpPF.GetIch() || !GetCp() || _rpTX.IsAfterEOP(),
  540. "CRchTxtPtr::ReplaceRange: EOP not at end of PF run");
  541. // BUGBUG!! (alexgo) doesn't handle correctly the case where things fail
  542. // (due to out of memory or whatever). See also notes in CTxtPtr::HandleReplaceRange
  543. // Undo. The assert below is therefore somewhat bogus, but if it fires,
  544. // then our floating ranges are going to be in trouble until we fix
  545. // up the logic here.
  546. Assert(cch == cchNew);
  547. Exit:
  548. #ifdef DEBUG
  549. // Test invariant again before calling out to replace range notification.
  550. // In this way, we can catch bugs earlier. The invariant has its own
  551. // scope for convenience.
  552. if( 1 )
  553. {
  554. _TEST_INVARIANT_
  555. }
  556. #endif
  557. if (ped->IsBiDi() && cpSave <= (LONG) ped->GetCpFirstStrong()
  558. && ((cchOld != 0) || (cch != 0)))
  559. {
  560. // Remember whether formatting is valid before we set context direction
  561. BOOL fCFValidBeforeSetContextDirection = _rpCF.IsValid();
  562. // Need to check the direction of the control if the input characters
  563. // control the direction.
  564. ped->SetContextDirection();
  565. // Did SetContextDirection make the formatting valid?
  566. if (!fCFValidBeforeSetContextDirection && _rpCF.IsValid())
  567. {
  568. // Our invariant is that cps should be equal if formatting is valid
  569. // so make it so!
  570. _rpCF.BindToCp(GetCp());
  571. }
  572. }
  573. if(pnm)
  574. {
  575. pnm->NotifyPostReplaceRange((ITxNotify *)this, cpSave, cchOld, cch,
  576. cpFormatMin, cpFormat + cchOld);
  577. }
  578. ped->GetCallMgr()->SetChangeEvent(CN_TEXTCHANGED);
  579. if(pcchMove) // Only return non0 cchMove if para
  580. { // direction changed, i.e., it's
  581. *pcchMove = fParaDirChange // a "BOOL" with a useful value,
  582. ? cchMove : 0; // namely the count of chars with
  583. } // changed direction
  584. if (ped->IsComplexScript())
  585. {
  586. if (dwFlags & RR_ITMZ_NONE || (ped->IsStreaming() && (!pch || *pch != WCH_EMBEDDING)))
  587. ped->_fItemizePending = TRUE;
  588. else
  589. ItemizeReplaceRange(cchNew, fParaDirChange? cchMove : 0, publdr);
  590. }
  591. return cch;
  592. }
  593. /*
  594. * CRchTxtPtr::InitRunPtrs()
  595. *
  596. * @mfunc
  597. * Initialize Run Ptrs of this rich-text ptr to correspond to
  598. * document given by ped and to cp given by cp.
  599. */
  600. void CRchTxtPtr::InitRunPtrs()
  601. {
  602. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::InitRunPtrs");
  603. AssertSz(GetPed(), "RTP::InitRunPtrs: illegal GetPed()");
  604. LONG cp = GetCp();
  605. CTxtStory *pStory = GetPed()->GetTxtStory();// If there's RichData,
  606. if(pStory->_pCFRuns) // initialize format-run ptrs
  607. {
  608. _rpCF.SetRunArray((CRunArray *)pStory->_pCFRuns);
  609. _rpCF.BindToCp(cp);
  610. }
  611. if(IsRich() && pStory->_pPFRuns)
  612. {
  613. _rpPF.SetRunArray((CRunArray *)pStory->_pPFRuns);
  614. _rpPF.BindToCp(cp);
  615. }
  616. }
  617. /*
  618. * CRchTxtPtr::SetRunPtrs(cp, cpFrom)
  619. *
  620. * @mfunc set Run Ptrs of this rich-text ptr to correspond to cp
  621. *
  622. * @rdesc
  623. * TRUE unless cp is outside of doc (in which case RunPtrs are
  624. * set to nearest document end).
  625. */
  626. void CRchTxtPtr::SetRunPtrs(
  627. LONG cp, // @parm character position to move RunPtrs to
  628. LONG cpFrom) // @parm cp to start with
  629. {
  630. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::SetRunPtrs");
  631. if(cpFrom && 2*cp >= cpFrom)
  632. {
  633. _rpCF.AdvanceCp(cp - cpFrom);
  634. _rpPF.AdvanceCp(cp - cpFrom);
  635. }
  636. else
  637. {
  638. _rpCF.BindToCp(cp);
  639. _rpPF.BindToCp(cp);
  640. }
  641. }
  642. /*
  643. * CRchTxtPtr::ExpandRangeFormatting(cchRange, cchMove, cchAdvance, fSavePara)
  644. *
  645. * @rdesc
  646. * In BiDi scenario, it's possible that updating a character affects the level of
  647. * the others. Such case should only happen when number being involved.
  648. *
  649. * Example: (AN)"11:30" changing '3' to 'x' will change the level of colon from 2 to 1.
  650. *
  651. */
  652. LONG CRchTxtPtr::ExpandRangeFormatting(
  653. LONG cchRange, // in: original length
  654. LONG cchMove, // in: number of chars moved after the replacement
  655. LONG& cchAdvance) // out: the extra chars added to the range after expanding
  656. {
  657. LONG cchBackup = 0;
  658. cchAdvance = 0;
  659. if (_rpCF.IsValid())
  660. {
  661. CTxtPtr tp(_rpTX);
  662. if (!IsRich())
  663. {
  664. cchBackup = -tp.FindEOP(tomBackward);
  665. tp.AdvanceCp(cchBackup + cchRange);
  666. cchAdvance = tp.FindEOP(tomForward);
  667. }
  668. else
  669. {
  670. CFormatRunPtr rp(_rpCF);
  671. LONG cp = GetCp();
  672. if (cchMove < 0)
  673. {
  674. // <cchMove> number of text to be moved down to the next paragraph
  675. cchBackup = -cchMove;
  676. }
  677. else if (cchMove > 0)
  678. {
  679. // <cchMove> number of text to be moved up to the previous paragraph
  680. cchAdvance = cchMove;
  681. }
  682. // Advancing/Backing up 2 adjacent runs seems to be sufficient for now.
  683. if (cchBackup == 0)
  684. {
  685. rp.AdjustBackward();
  686. cchBackup += rp.GetIch();
  687. if (rp.PrevRun())
  688. cchBackup += rp.GetCchLeft();
  689. rp.AdvanceCp(cchBackup);
  690. }
  691. // move the run pointer to the end of range
  692. rp.AdvanceCp(cchRange);
  693. tp.SetCp(cp + cchRange);
  694. if (cchAdvance == 0 && !tp.IsAtEOP())
  695. {
  696. rp.AdjustForward();
  697. cchAdvance += rp.GetCchLeft();
  698. if (rp.NextRun())
  699. cchAdvance += rp.GetCchLeft();
  700. }
  701. }
  702. }
  703. return cchBackup;
  704. }
  705. /*
  706. * CRchTxtPtr::ItemizeReplaceRange(cchUpdate, cchMove, publdr, fUnicodeBidi)
  707. *
  708. * @mfunc
  709. * Find out the exact range to be itemized after calling :ReplaceRange
  710. *
  711. * @rdesc
  712. * result from ItemizeRuns.
  713. * Guarantee *this* pointer wont move.
  714. */
  715. BOOL CRchTxtPtr::ItemizeReplaceRange(
  716. LONG cchUpdate,
  717. LONG cchMove, // Count of chars moved after replacing
  718. IUndoBuilder* publdr, // (they need reitemizing)
  719. BOOL fUnicodeBidi)
  720. {
  721. BOOL fr = FALSE;
  722. if (GetPed()->IsComplexScript())
  723. {
  724. Assert (cchUpdate >= 0); // the range after ReplaceRange must be degenerate
  725. CTxtPtr tp(_rpTX);
  726. LONG cp = GetCp();
  727. LONG cpStart, cpEnd;
  728. BOOL fNonUnicodeBidiRecurse = FALSE;
  729. BOOL fUseCtxLevel = FALSE;
  730. tp.AdvanceCp(-cchUpdate);
  731. if (cchUpdate > 0 && GetPed()->IsRich() && fUnicodeBidi)
  732. {
  733. cpStart = cpEnd = cp;
  734. cpStart -= cchUpdate;
  735. if (GetPed()->IsBiDi())
  736. {
  737. // RAID bug 7094 : We wnat to use the IP to set the context for
  738. // incoming text.
  739. // fUseCtxLevel = TRUE;
  740. // Recurse with non-BiDi, so the run preceding/succeeding this chunk get updated
  741. fNonUnicodeBidiRecurse = TRUE;
  742. }
  743. }
  744. else
  745. {
  746. tp.FindWhiteSpaceBound(cchUpdate, cpStart, cpEnd,
  747. !GetPed()->IsRich() ? FWS_BOUNDTOPARA : 0);
  748. }
  749. if (cchMove < 0)
  750. {
  751. // <cchMove> number of text -before- the replaced range
  752. // moves down to the next paragraph.
  753. cpStart = max(cp - cchUpdate + cchMove, 0);
  754. }
  755. else if (cchMove > 0)
  756. {
  757. // <cchMove> number of text -after- the replaced range
  758. // moves up to the previous paragraph.
  759. cpEnd = min(cp + cchMove, GetPed()->GetTextLength());
  760. }
  761. {
  762. CTxtRange rg(*this, 0);
  763. rg.Set(cpEnd, cpEnd - cpStart);
  764. fr = rg.ItemizeRuns(publdr, fUnicodeBidi, fUseCtxLevel);
  765. // set pointer back to original cp
  766. // We cant use copy operator here since itemization changes format run.
  767. // It would cause invariant failure in _rpCF.
  768. cp -= rg.GetCp();
  769. _rpCF = rg._rpCF;
  770. _rpCF.AdvanceCp(cp);
  771. // ItemizeRuns invalidates rg._rpPF so that the paraformat run becomes valid
  772. // and we need to advance it to the current cp.
  773. _rpPF = rg._rpPF;
  774. _rpPF.AdvanceCp(cp);
  775. // Perf note: We dont want the range to be around when we recurse
  776. // since a range is a notification sink.
  777. }
  778. // Run itemization to the same range, this time forces it to be non-Bidi.
  779. if (fr && fNonUnicodeBidiRecurse)
  780. fr = ItemizeReplaceRange(cchUpdate, 0, publdr, FALSE);
  781. }
  782. return fr;
  783. }
  784. /*
  785. * CRchTxtPtr::ReplaceRangeFormatting(cchOld, cchNew, iFormat, publdr,
  786. * ppaeCF, ppaePF, cchMove)
  787. * @mfunc
  788. * replace character and paragraph formatting at this text pointer
  789. * using CCharFormat with index iFormat
  790. *
  791. * @rdesc
  792. * count of new characters added
  793. *
  794. * @devnote
  795. * moves _rpCF and _rpPF to end of replaced text
  796. * moves format run arrays
  797. * CCharFormat for iFormat is fully configured, i.e., no NINCHes
  798. */
  799. LONG CRchTxtPtr::ReplaceRangeFormatting(
  800. LONG cchOld, //@parm length of range to replace
  801. LONG cchNew, //@parm length of replacement text
  802. LONG iFormat, //@parm Char format to use
  803. IUndoBuilder *publdr, //@parm UndoBuilder to receive antievents
  804. IAntiEvent **ppaeCF, //@parm where to return 'extra' CF anti-events
  805. IAntiEvent **ppaePF, //@parm where to return extra PF anti-events
  806. LONG cchMove, //@parm cch to move between PF runs
  807. LONG cchPrevEOP, //@parm cch from _cp back to prev EOP
  808. LONG cchNextEOP, //@parm cch from _cp up to next EOP
  809. LONG cchSaveBefore,
  810. LONG cchSaveAfter)
  811. {
  812. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::ReplaceRangeFormatting");
  813. LONG cp = GetCp();
  814. ICharFormatCache * pcfc = GetCharFormatCache();
  815. IParaFormatCache * ppfc = GetParaFormatCache();
  816. LONG iRunMerge = 0;
  817. AssertSz(cchOld >= 0,
  818. "CRchTxtPtr::ReplaceRangeFormatting: Illegal cchOld");
  819. if(_rpCF.IsValid())
  820. {
  821. iRunMerge = _rpCF._iRun;
  822. if(iRunMerge > 0)
  823. iRunMerge--;
  824. Assert (cchSaveBefore >= 0 && cchSaveAfter >= 0);
  825. if(cchOld + cchSaveAfter + cchSaveBefore > 0)
  826. { // add the soon-to-be deleted
  827. if(publdr) // formats to the undo list
  828. {
  829. // Include previous cchSaveBefore chars
  830. _rpCF.AdvanceCp(-cchSaveBefore);
  831. *ppaeCF = gAEDispenser.CreateReplaceFormattingAE(
  832. GetPed(), _rpCF, cchSaveAfter + cchOld + cchSaveBefore, pcfc, CharFormat);
  833. // Restore _rpCF (we just want to save value not delete it)
  834. _rpCF.AdvanceCp(cchSaveBefore);
  835. }
  836. if(cchOld) // Delete/modify CF runs <-->
  837. _rpCF.Delete(cchOld, pcfc, 0); // to cchOld chars
  838. }
  839. // If we deleted all of text in story, don't bother adding a new
  840. // run. Else insert/modify CF runs corresponding to cchNew chars
  841. //
  842. // In a plain-text control, there is no final EOP; hence the test
  843. // for equality.
  844. if(cchNew > 1 || cchNew && cchOld <= GetTextLength())
  845. _rpCF.InsertFormat(cchNew, iFormat, pcfc);
  846. if((cchOld || cchNew) && _rpCF.IsValid())// Deleting all text
  847. { // invalidates _rpCF
  848. _rpCF.AdjustForward();
  849. _rpCF.MergeRuns(iRunMerge, pcfc);
  850. _rpCF.BindToCp(cp + cchNew);
  851. }
  852. }
  853. if(_rpPF.IsValid())
  854. {
  855. _rpPF.AdjustForward(); // Be absolutely sure that
  856. // PF runs end with EOPs
  857. iRunMerge = _rpPF._iRun;
  858. if(iRunMerge > 0)
  859. iRunMerge--;
  860. if(cchOld) // Delete cchOld from PF runs
  861. { // add the soon-to-be deleted
  862. if(publdr) // formats to the undo list
  863. {
  864. CFormatRunPtr rp(_rpPF);
  865. rp.AdvanceCp(cchPrevEOP);
  866. *ppaePF = gAEDispenser.CreateReplaceFormattingAE(GetPed(),
  867. rp, cchNextEOP - cchPrevEOP, ppfc, ParaFormat);
  868. }
  869. _rpPF.Delete(cchOld, ppfc, cchMove);
  870. }
  871. if(_rpPF.IsValid()) // Deleting all text
  872. { // invalidates _rpPF
  873. _rpPF.AdjustForward();
  874. _rpPF.GetRun(0)->_cch += cchNew; // Insert cchNew into current
  875. _rpPF._ich += cchNew; // PF run
  876. if(cchOld || cchNew)
  877. {
  878. _rpPF.MergeRuns(iRunMerge, ppfc);
  879. _rpPF.BindToCp(cp + cchNew);
  880. }
  881. }
  882. }
  883. return cchNew;
  884. }
  885. /*
  886. * CRchTxtPtr::ExtendFormattingCRLF()
  887. *
  888. * @mfunc
  889. * Use the same CCharFormat and CParaFormat indices for the EOP at
  890. * this text ptr as those immediately preceding it.
  891. *
  892. * @devnote
  893. * Leaves this text ptr's format ptrs at run you get from AdjustBackward
  894. * since this run ends up including the new text.
  895. */
  896. void CRchTxtPtr::ExtendFormattingCRLF()
  897. {
  898. LONG cch = GetTextLength() - GetPed()->GetAdjustedTextLength();
  899. CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
  900. _rpCF.AdjustFormatting(cch, GetCharFormatCache());
  901. if(_rpPF.IsValid())
  902. {
  903. _rpPF.AdjustBackward();
  904. if(!InTable())
  905. _rpPF.AdjustFormatting(cch, GetParaFormatCache());
  906. _rpPF.AdjustForward();
  907. }
  908. if(pnm)
  909. {
  910. // We assume that Cch is positive (or zero) here
  911. Assert(cch >= 0);
  912. pnm->NotifyPostReplaceRange((ITxNotify *)this, CP_INFINITE, 0, 0,
  913. GetCp(), GetCp() + cch);
  914. }
  915. }
  916. /*
  917. * CRchTxtPtr::IsRich()
  918. *
  919. * @mfunc
  920. * Determine whether rich-text operation is operable
  921. *
  922. * @rdesc
  923. * TRUE if associated CTxtEdit::_fRich = 1, i.e., control is allowed
  924. * to be rich.
  925. */
  926. BOOL CRchTxtPtr::IsRich()
  927. {
  928. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::IsRich");
  929. return GetPed()->IsRich();
  930. }
  931. /*
  932. * CRchTxtPtr::Check_rpCF()
  933. *
  934. * @mfunc
  935. * enable _rpCF if it's not already enabled
  936. *
  937. * @rdesc
  938. * TRUE if _rpCF is enabled
  939. */
  940. BOOL CRchTxtPtr::Check_rpCF()
  941. {
  942. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::Check_rpCF");
  943. if(_rpCF.IsValid())
  944. return TRUE;
  945. if(!_rpCF.InitRuns (GetCp(), GetTextLength(),
  946. &(GetPed()->GetTxtStory()->_pCFRuns)))
  947. {
  948. return FALSE;
  949. }
  950. CNotifyMgr *pnm = GetPed()->GetNotifyMgr(); // For notifying of changes
  951. if(pnm)
  952. pnm->NotifyPostReplaceRange( // Notify interested parties
  953. (ITxNotify *)this, CP_INFINITE, // that
  954. 0, 0, CP_INFINITE, CP_INFINITE);
  955. return TRUE;
  956. }
  957. /*
  958. * CRchTxtPtr::Check_rpPF()
  959. *
  960. * @mfunc
  961. * enable _rpPF if it's not already enabled
  962. *
  963. * @rdesc
  964. * TRUE if _rpPF is enabled
  965. */
  966. BOOL CRchTxtPtr::Check_rpPF()
  967. {
  968. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::Check_rpPF");
  969. if(_rpPF.IsValid())
  970. return TRUE;
  971. if(!IsRich())
  972. return FALSE;
  973. if(!_rpPF.InitRuns (GetCp(), GetTextLength(),
  974. &(GetPed()->GetTxtStory()->_pPFRuns)))
  975. {
  976. return FALSE;
  977. }
  978. if (IsParaRTL())
  979. _rpPF.GetRun(0)->_level._value = 1; // Set default paragraph base level
  980. CNotifyMgr *pnm = GetPed()->GetNotifyMgr(); // For notifying of changes
  981. if(pnm)
  982. pnm->NotifyPostReplaceRange( // Notify interested parties
  983. (ITxNotify *)this, CP_INFINITE, // of the change.
  984. 0, 0, CP_INFINITE, CP_INFINITE);
  985. return TRUE;
  986. }
  987. /*
  988. * CRchTxtPtr::FindWordBreak(action, cpMost)
  989. *
  990. * @mfunc
  991. * Same as CTxtPtr::FindWordBreak(), but moves the whole rich text ptr
  992. *
  993. * @rdesc
  994. * cch this rich text ptr is moved
  995. */
  996. LONG CRchTxtPtr::FindWordBreak(
  997. INT action, //@parm Kind of word break to find
  998. LONG cpMost) //@parm Limiting character position
  999. {
  1000. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::FindWordBreak");
  1001. LONG cch = _rpTX.FindWordBreak(action, cpMost);
  1002. _rpCF.AdvanceCp(cch);
  1003. _rpPF.AdvanceCp(cch);
  1004. return cch;
  1005. }
  1006. /*
  1007. * CRchTxtPtr::BindToCp(dwNewCp)
  1008. *
  1009. * @mfunc
  1010. * Set cp to new value and recalculate that new position.
  1011. */
  1012. void CRchTxtPtr::BindToCp(
  1013. LONG cp) // @parm new cp for rich text
  1014. {
  1015. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::BindToCp");
  1016. _rpTX.BindToCp(cp); // Recalculate cp for plain text
  1017. // Use the InitRunPtrs routine so that the run pointers will get
  1018. // re-initialized and rebound with the correct run array. The
  1019. // run array formerly used (if any at all) is not necessarily valid
  1020. // when this function is called.
  1021. InitRunPtrs();
  1022. // Do invariant testing at end because this fixes up the rich text
  1023. // pointer in the face of backing store changes.
  1024. _TEST_INVARIANT_
  1025. }
  1026. /*
  1027. * CRchTxtPtr::CheckFormatRuns ()
  1028. *
  1029. * @mfunc
  1030. * Check the format runs against what's in CTxtStory. If
  1031. * different, forces a rebind to <p cp>
  1032. */
  1033. void CRchTxtPtr::CheckFormatRuns()
  1034. {
  1035. CTxtStory *pStory = GetPed()->GetTxtStory();
  1036. if (pStory->GetCFRuns() != (CFormatRuns *)_rpCF._pRuns ||
  1037. pStory->GetPFRuns() != (CFormatRuns *)_rpPF._pRuns)
  1038. {
  1039. InitRunPtrs();
  1040. }
  1041. _TEST_INVARIANT_
  1042. }
  1043. /*
  1044. * CRchTxtPtr::ChangeCase(cch, Type, publdr)
  1045. *
  1046. * @mfunc
  1047. * Change case of cch chars starting at this text ptr according to Type,
  1048. * which has the possible values:
  1049. *
  1050. * tomSentenceCase = 0: capitalize first letter of each sentence
  1051. * tomLowerCase = 1: change all letters to lower case
  1052. * tomUpperCase = 2: change all letters to upper case
  1053. * tomTitleCase = 3: capitalize the first letter of each word
  1054. * tomToggleCase = 4: toggle the case of each letter
  1055. *
  1056. * @rdesc
  1057. * TRUE iff a change occurred
  1058. *
  1059. * @devnote
  1060. * Since this routine only changes the case of characters, it has no
  1061. * effect on rich-text formatting. However it is part of the CRchTxtPtr
  1062. * class in order to notify the display of changes. CTxtRanges are also
  1063. * notified just in case the text blocks are modified.
  1064. */
  1065. BOOL CRchTxtPtr::ChangeCase (
  1066. LONG cch, //@parm # chars to change case for
  1067. LONG Type, //@parm Type of change case command
  1068. IUndoBuilder *publdr) //@parm UndoBuilder to receive anti-event
  1069. // for any replacements
  1070. {
  1071. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::ChangeCase");
  1072. _TEST_INVARIANT_
  1073. #define BUFFERLEN 256
  1074. LONG cchChunk, cchFirst, cchFormat, cchGet, cchLast;
  1075. BOOL fAlpha, fToUpper, fUpper; // Flags controling case change
  1076. BOOL fChange = FALSE; // No change yet
  1077. BOOL fStart = TRUE; // Start of Word/Sentence
  1078. LONG iCF;
  1079. TCHAR * pch; // Ptr to walk rgCh with
  1080. WORD * pType; // Ptr to walk rgType with
  1081. WCHAR rgCh[BUFFERLEN]; // Char buffer to work in
  1082. WORD rgType[BUFFERLEN]; // C1_TYPE array for rgCh
  1083. if( GetCp() )
  1084. {
  1085. if( Type == tomSentenceCase )
  1086. {
  1087. fStart = _rpTX.IsAtBOSentence();
  1088. }
  1089. else if( Type == tomTitleCase )
  1090. {
  1091. // Check to see if we are at the beginning of
  1092. // a word. This is the case if the character preceeding
  1093. // our current position is white space.
  1094. fStart = IsWhiteSpace(GetPrevChar());
  1095. }
  1096. }
  1097. while(cch > 0) // Do 'em all (or as many as
  1098. { // in story)
  1099. cchChunk = min(BUFFERLEN, cch); // Get next bufferful
  1100. // FUTURE: it's too bad that we have to do all this format stuff
  1101. // when the formatting isn't even going to change. It would be
  1102. // faster and simpler to use _rpTX.ReplaceRange() and just send
  1103. // appropriate notifications.
  1104. if(_rpCF.IsValid()) // Make sure it stays within
  1105. { // current char/paraformat
  1106. cchFormat = _rpCF.GetCchLeft(); // runs to simplify changing
  1107. cchChunk = min(cchChunk, cchFormat);// text with undo
  1108. }
  1109. if(_rpPF.IsValid())
  1110. {
  1111. cchFormat = _rpPF.GetCchLeft();
  1112. cchChunk = min(cchChunk, cchFormat);
  1113. }
  1114. cch -= cchChunk; // Decrement the count
  1115. cchGet = _rpTX.GetText(cchChunk, rgCh); // Manipulate chars in buffer
  1116. if(cchGet < cchChunk) // (for undo, need to use
  1117. { // ReplaceRange())
  1118. cch = 0; // No more chars in story,
  1119. if(!cchGet) // so we'll be done
  1120. break; // We're done already
  1121. cchChunk = cchGet; // Something in this chunk
  1122. }
  1123. W32->GetStringTypeEx(0, CT_CTYPE1, rgCh,// Find out whether chars are
  1124. cchChunk, rgType); // UC, LC, or neither
  1125. cchLast = 0; // Default nothing to replace
  1126. cchFirst = -1;
  1127. for(pch = rgCh, pType = rgType; // Process buffered chars
  1128. cchChunk;
  1129. cchChunk--, pch++, pType++)
  1130. {
  1131. fAlpha = *pType & (C1_UPPER | C1_LOWER); // Nonzero if UC or LC
  1132. fUpper = (*pType & C1_UPPER) != 0; // TRUE if UC
  1133. fToUpper = fStart ? TRUE : fUpper; // capitalize first letter of a
  1134. // sentence
  1135. switch(Type)
  1136. { // Decide whether to change
  1137. case tomLowerCase: // case and determine start
  1138. fToUpper = FALSE; // of word/sentence for title
  1139. break; // and sentence cases
  1140. case tomUpperCase:
  1141. fToUpper = TRUE;
  1142. break;
  1143. case tomToggleCase:
  1144. fToUpper = !fUpper;
  1145. break;
  1146. case tomSentenceCase:
  1147. if(*pch == TEXT('.')) // If sentence terminator,
  1148. fStart = TRUE; // capitalize next alpha
  1149. if(fAlpha) // If this char is alpha, next
  1150. fStart = FALSE; // char can't start a
  1151. break; // sentence
  1152. case tomTitleCase: // If this char is alpha, next
  1153. fStart = (fAlpha == 0); // char can't start a word
  1154. break;
  1155. default:
  1156. return FALSE;
  1157. }
  1158. if(fAlpha && (fToUpper ^ fUpper)) // Only change case if it
  1159. { // makes a difference (saves
  1160. if(fToUpper) // on system calls and undos)
  1161. CharUpperBuff(pch, 1);
  1162. else
  1163. CharLowerBuff(pch, 1);
  1164. fChange = TRUE; // Return value: change made
  1165. if( cchFirst == -1 ) // Save cch of unchanged
  1166. cchFirst = cchGet-cchChunk; // leading string
  1167. cchLast = cchChunk - 1; // Save cch of unchanged
  1168. } // trailing string
  1169. }
  1170. if( cchFirst == -1 )
  1171. {
  1172. Assert(cchLast == 0);
  1173. cchFirst = cchGet;
  1174. }
  1175. Advance(cchFirst); // Skip unchanged leading
  1176. cchGet -= cchFirst + cchLast; // string. cchGet = cch of
  1177. _rpCF.AdjustForward(); // changed span. Adjust in
  1178. // case cchFirst = 0
  1179. iCF = _rpCF.GetFormat();
  1180. GetCharFormatCache()->AddRef(iCF);
  1181. ReplaceRange(cchGet, cchGet, rgCh + cchFirst, publdr, iCF);
  1182. ReleaseFormats(iCF, -1);
  1183. Advance(cchLast); // Skip unchanged trailing
  1184. } // string
  1185. return fChange;
  1186. }
  1187. // The following defines a mask for Units implemented by UnitCounter()
  1188. #define IMPL ((1 << tomCharacter) + (1 << tomWord) + (1 << tomSentence) + \
  1189. (1 << tomParagraph) + (1 << tomLine) + (1 << tomStory) + \
  1190. (1 << tomCharFormat) + (1 << tomParaFormat) + (1 << tomObject))
  1191. /*
  1192. * CRchTxtPtr::UnitCounter (Unit, &cUnit, cchMax)
  1193. *
  1194. * @mfunc
  1195. * Helper function to count chars in <p cUnit> Units defined by <p Unit>
  1196. * <p cUnit> is a signed count. If it extends beyond either end of the
  1197. * story, count up to that end and update <p cUnit> accordingly. If
  1198. * <p cchMax> is nonzero, stop counting when the count exceeds <p cchMax>
  1199. * in magnitude.
  1200. *
  1201. * @rdesc
  1202. * If unit is implemented, return cch corresponding to the units counted
  1203. * (up to a maximum magnitude of <p cchMax>) and update cUnit;
  1204. * else return tomForward to signal unit not implemented and cUnit = 0.
  1205. * If unit is implemented but unavailable, e.g., tomObject with no
  1206. * embedded objects, return tomBackward.
  1207. *
  1208. * @devnote
  1209. * This is the basic engine used by the TOM CTxtRange::Move() and Index()
  1210. * methods.
  1211. */
  1212. LONG CRchTxtPtr::UnitCounter (
  1213. LONG Unit, //@parm Type of unit to count
  1214. LONG & cUnit, //@parm Count of units to count chars for
  1215. LONG cchMax) //@parm Maximum character count
  1216. {
  1217. TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CRchTxtPtr::UnitCounter");
  1218. LONG action; // Gives direction and tomWord commands
  1219. LONG cch; // Collects cch counted
  1220. LONG cchText = GetTextLength();
  1221. LONG cp = GetCp();
  1222. LONG iDir = cUnit > 0 ? 1 : -1;
  1223. LONG j; // For-loop index
  1224. CDisplay *pdp; // Used for tomLine case
  1225. if(!cUnit) // Nothing to count
  1226. {
  1227. return ((DWORD)Unit > tomObject || !((IMPL >> Unit) & 1))
  1228. ? tomForward : 0; // Indicate Unit not
  1229. } // implemented
  1230. if(cchMax <= 0)
  1231. cchMax = tomForward; // No cch limit
  1232. switch(Unit)
  1233. {
  1234. case tomCharacter: // Smallest Unit
  1235. cp += cUnit; // Requested new cp
  1236. ValidateCp(cp); // Make sure it's OK
  1237. cch = cUnit = cp - GetCp(); // How many cch, cUnits
  1238. break; // actually moved
  1239. case tomStory: // Largest Unit
  1240. cch = (cUnit > 0) ? cchText - cp : -cp; // cch to start of story
  1241. cUnit = cch ? iDir : 0; // If already at end/start,
  1242. break; // of story, no count
  1243. case tomCharFormat: // Constant CHARFORMAT
  1244. cch = _rpCF.CountRuns(cUnit, cchMax, cp, cchText);
  1245. break;
  1246. case tomParaFormat: // Constant PARAFORMAT
  1247. cch = _rpPF.CountRuns(cUnit, cchMax, cp, cchText);
  1248. break;
  1249. case tomObject:
  1250. if(!GetObjectCount()) // No objects: can't move, so
  1251. {
  1252. cUnit = 0; // set cUnit = 0 and
  1253. return tomBackward; // signal Unit unavailable
  1254. }
  1255. cch = GetPed()->_pobjmgr->CountObjects(cUnit, GetCp());
  1256. break;
  1257. case tomLine:
  1258. pdp = GetPed()->_pdp;
  1259. if(pdp) // If this story has a display
  1260. { // use a CLinePtr
  1261. CLinePtr rp(pdp);
  1262. pdp->WaitForRecalc(cp, -1);
  1263. rp.RpSetCp(cp, FALSE);
  1264. cch = rp.CountRuns(cUnit, cchMax, cp, cchText);
  1265. break;
  1266. } // Else fall thru to treat as
  1267. // tomPara
  1268. default: // tp dependent cases
  1269. { // Block to contain tp() which
  1270. CTxtPtr tp(_rpTX); // takes time to construct
  1271. if (cUnit < 0) // Counting backward
  1272. {
  1273. action = (Unit == tomWord)
  1274. ? WB_MOVEWORDLEFT : tomBackward;
  1275. }
  1276. else // Counting forward
  1277. {
  1278. action = (Unit == tomWord)
  1279. ? WB_MOVEWORDRIGHT : tomForward;
  1280. }
  1281. for (cch = 0, j = cUnit; j && abs(cch) < cchMax; j -= iDir)
  1282. {
  1283. cp = tp.GetCp(); // Save starting cp for
  1284. switch (Unit) // calculating cch for this
  1285. { // Unit
  1286. case tomWord:
  1287. tp.FindWordBreak(action);
  1288. break;
  1289. case tomSentence:
  1290. tp.FindBOSentence(action);
  1291. break;
  1292. case tomLine: // Story has no line array:
  1293. case tomParagraph: // treat as tomParagraph
  1294. tp.FindEOP(action);
  1295. break;
  1296. default:
  1297. cUnit = 0;
  1298. return tomForward; // Return error
  1299. }
  1300. if(tp.GetCp() - cp == 0) // No count:
  1301. break; // don't decrement cUnit
  1302. cch += tp.GetCp() - cp;
  1303. }
  1304. cUnit -= j; // Discount any runs not
  1305. } // counted if |cch| >= cchMax
  1306. }
  1307. if(abs(cch) > cchMax) // Keep cch within requested
  1308. { // limit
  1309. cch = cch > 0 ? cchMax : -cchMax;
  1310. if(Unit == tomCharacter)
  1311. cUnit = cch;
  1312. }
  1313. Advance(cch); // Move to new position
  1314. return cch; // Total cch counted
  1315. }
  1316. /*
  1317. * CRchTxtPtr::GetParaNumber ()
  1318. *
  1319. * @mfunc
  1320. * Return number of current paragraph in a numbered list. This is
  1321. * 0 if the current paragraph isn't part of a list. It's 1 if it's
  1322. * the first paragraph in a list, 2 if it's the second, etc.
  1323. *
  1324. * @rdesc
  1325. * paragraph number active at this rich text ptr
  1326. *
  1327. * @devnote
  1328. * When the display is calc'd from the beginning or recalc'd from
  1329. * a previous valid position, the list number can be determined from
  1330. * the display. But if CDisplayPrinter::FormatRange() works without
  1331. * a display, it needs to know the number. This routine can be so used
  1332. * for this purpose and for debugging the display choices.
  1333. */
  1334. LONG CRchTxtPtr::GetParaNumber() const
  1335. {
  1336. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::GetParaNumber");
  1337. LONG ch;
  1338. LONG cPara = 0;
  1339. LONG n;
  1340. const CParaFormat *pPF, *pPFLast = NULL;
  1341. CRchTxtPtr rtp(*this);
  1342. while(1)
  1343. {
  1344. pPF = rtp.GetPF();
  1345. // CParaFormat::UpdateNumber(2, pPFLast) returns:
  1346. // 0 -- not a numbered list
  1347. // 1 -- new numbered list or pPFLast = NULL
  1348. // 2 -- list number suppressed
  1349. // 3 -- different number in same list
  1350. n = pPF->UpdateNumber(2, pPFLast);
  1351. if(n == 0 || n == 1 && pPFLast && cPara)
  1352. break;
  1353. ch = rtp.GetPrevChar();
  1354. if((!ch || IsASCIIEOP(ch) && ch != VT) && n != 2)
  1355. cPara++;
  1356. if(!ch)
  1357. break;
  1358. rtp._rpPF.AdvanceCp(rtp._rpTX.FindEOP(tomBackward));
  1359. pPFLast = pPF; // Don't need to update _rpCF
  1360. } // for this calculation
  1361. return cPara;
  1362. }
  1363. /*
  1364. * Notes on RichEdit 1.0 mode:
  1365. *
  1366. * CF_UNICODETEXT should not be used in RichEdit 1.0 mode. \uN should use
  1367. * the alternative.
  1368. *
  1369. * CleanseAndReplaceRange() and the RTF reader need to ensure that any
  1370. * Unicode chars entered belong to a CharSet and stamp it accordingly.
  1371. * If no CharSet exists for the character, then blank should be used.
  1372. */
  1373. /*
  1374. * CRchTxtPtr::GetCachFromCch(cch)
  1375. *
  1376. * @mfunc
  1377. * Return count of A chars corresponding to cch W chars starting at
  1378. * this text ptr. On first call start with this text ptr at cp = 0.
  1379. *
  1380. * @rdesc
  1381. * Count of A chars between this text ptr and cp
  1382. *
  1383. * @comm
  1384. * The algorithm assumes that for a DBCS charset any character
  1385. * above 128 has two bytes, except for the halfwidth KataKana,
  1386. * which are single bytes in ShiftJis.
  1387. */
  1388. LONG CRchTxtPtr::GetCachFromCch(
  1389. LONG cch) //@parm Count of chars to check
  1390. {
  1391. BYTE bCharSet;
  1392. LONG cach = 0; // No ach counted yet
  1393. LONG cch1;
  1394. LONG cchRun; // CF run count
  1395. LONG cchValid; // Text run count
  1396. WCHAR ch;
  1397. const WCHAR *pch; // Ptr to text run
  1398. const CCharFormat *pCF;
  1399. while(cch > 0)
  1400. {
  1401. cchRun = _rpCF.IsValid()
  1402. ? _rpCF.GetCchLeft()
  1403. : GetTextLength() - GetCp();
  1404. if(!cchRun)
  1405. break; // No more text
  1406. pCF = GetCF();
  1407. bCharSet = pCF->_bCharSet;
  1408. if (!IsFECharSet(bCharSet) ||
  1409. (pCF->_dwEffects & CFE_RUNISDBCS))
  1410. {
  1411. cchRun = min(cchRun, cch);
  1412. cach += cchRun; // SBCS run or DBCS stored as
  1413. cch -= cchRun; // one byte per char
  1414. Advance(cchRun);
  1415. continue;
  1416. }
  1417. pch = GetPch(cchValid);
  1418. Assert(pch);
  1419. cchValid = min(cchValid, cchRun);
  1420. for(cch1 = 0; cch > 0 && cchValid--; cch1++)
  1421. {
  1422. cch--;
  1423. ch = *pch++;
  1424. if(ch >= 128 && ch != WCH_EMBEDDING &&
  1425. (bCharSet != SHIFTJIS_CHARSET || !IN_RANGE(0xFF61, ch, 0xFF9F)))
  1426. {
  1427. cach++;
  1428. }
  1429. }
  1430. cach += cch1;
  1431. Advance(cch1);
  1432. }
  1433. return cach;
  1434. }
  1435. /*
  1436. * CRchTxtPtr::GetCchFromCach(cach)
  1437. *
  1438. * @mfunc
  1439. * Return count of W chars corresponding to cach A chars starting at this
  1440. * text ptr. On first call start with this text ptr at cp = 0.
  1441. *
  1442. * @rdesc
  1443. * Count of W chars corresponding to cach A chars starting at this tp.
  1444. *
  1445. * @comm
  1446. * The algorithm assumes that for a DBCS charset any character
  1447. * above 128 has two bytes, except for the halfwidth KataKana,
  1448. * which are single bytes in ShiftJis.
  1449. */
  1450. LONG CRchTxtPtr::GetCchFromCach(
  1451. LONG cach) //@parm Count of ach's starting at this text ptr
  1452. {
  1453. BYTE bCharSet;
  1454. LONG cch = 0; // No ch's yet
  1455. LONG cch1;
  1456. LONG cchRun; // CF run count
  1457. LONG cchValid; // Text run count
  1458. WCHAR ch;
  1459. const WCHAR *pch; // Ptr to text run
  1460. const CCharFormat *pCF;
  1461. while(cach > 0)
  1462. {
  1463. cchRun = _rpCF.IsValid()
  1464. ? _rpCF.GetCchLeft()
  1465. : GetTextLength() - GetCp();
  1466. if(!cchRun)
  1467. break; // No more text
  1468. pCF = GetCF();
  1469. bCharSet = pCF->_bCharSet;
  1470. if (!IsFECharSet(bCharSet) ||
  1471. (pCF->_dwEffects & CFE_RUNISDBCS))
  1472. {
  1473. cchRun = min(cchRun, cach); // SBCS run or DBCS stored as
  1474. cach -= cchRun; // one byte per char
  1475. cch += cchRun;
  1476. Advance(cchRun);
  1477. continue;
  1478. }
  1479. pch = GetPch(cchValid);
  1480. Assert(pch);
  1481. cchValid = min(cchValid, cchRun);
  1482. for(cch1 = 0; cach > 0 && cchValid--; cch1++)
  1483. {
  1484. cach--;
  1485. ch = *pch++;
  1486. if(ch >= 128 && ch != WCH_EMBEDDING &&
  1487. (bCharSet != SHIFTJIS_CHARSET || !IN_RANGE(0xFF61, ch, 0xFF9F)))
  1488. {
  1489. cach--;
  1490. }
  1491. }
  1492. cch += cch1;
  1493. Advance(cch1);
  1494. }
  1495. return cch;
  1496. }
  1497. /*
  1498. * CRchTxtPtr::Zombie ()
  1499. *
  1500. * @mfunc
  1501. * Turn this object into a zombie by NULLing out its _ped member
  1502. */
  1503. void CRchTxtPtr::Zombie ()
  1504. {
  1505. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRchTxtPtr::Zombie");
  1506. _rpTX.Zombie();
  1507. _rpCF.SetToNull();
  1508. _rpPF.SetToNull();
  1509. }