Leaked source code of windows server 2003
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.

1276 lines
31 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module M_UNDO.C |
  5. *
  6. * Purpose:
  7. * Implementation of the global mutli-undo stack
  8. *
  9. * Author:
  10. * alexgo 3/25/95
  11. *
  12. * Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved.
  13. */
  14. #include "_common.h"
  15. #include "_m_undo.h"
  16. #include "_edit.h"
  17. #include "_disp.h"
  18. #include "_urlsup.h"
  19. #include "_antievt.h"
  20. ASSERTDATA
  21. //
  22. // PUBLIC METHODS
  23. //
  24. /*
  25. * CUndoStack::CUndoStack (ped, cUndoLim, flags)
  26. *
  27. * @mfunc Constructor
  28. */
  29. CUndoStack::CUndoStack(
  30. CTxtEdit *ped, //@parm CTxtEdit parent
  31. LONG & cUndoLim, //@parm Initial limit
  32. USFlags flags) //@parm Flags for this undo stack
  33. {
  34. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::CUndoStack");
  35. _ped = ped;
  36. _prgActions = NULL;
  37. _index = 0;
  38. _cUndoLim = 0;
  39. // We should be creating an undo stack if there's nothing to put in it!
  40. Assert(cUndoLim);
  41. SetUndoLimit(cUndoLim);
  42. if(flags & US_REDO)
  43. _fRedo = TRUE;
  44. }
  45. /*
  46. * CUndoStack::~CUndoStack()
  47. *
  48. * @mfunc Destructor
  49. *
  50. * @comm
  51. * Deletes any remaining antievents. The antievent dispenser
  52. * should *not* clean up because of this!!
  53. */
  54. CUndoStack::~CUndoStack()
  55. {
  56. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::~CUndoStack");
  57. // Clear out any remaining antievents
  58. ClearAll();
  59. delete _prgActions;
  60. }
  61. /*
  62. * CUndoStack::Destroy ()
  63. *
  64. * @mfunc
  65. * Deletes this instance
  66. */
  67. void CUndoStack::Destroy()
  68. {
  69. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::Destroy");
  70. delete this;
  71. }
  72. /*
  73. * CUndoStack::SetUndoLimit (cUndoLim)
  74. *
  75. * @mfunc
  76. * Allows the undo stack to be enlarged or reduced
  77. *
  78. * @rdesc
  79. * Size to which the stack is actually set.
  80. *
  81. * @comm
  82. * The algorithm we use is the following: <nl>
  83. *
  84. * Try to allocate space for the requested size.
  85. * If there's insufficient memory, try to recover
  86. * with the largest block possible.
  87. *
  88. * If the requested size is bigger than the default,
  89. * and the current size is less than the default, go
  90. * ahead and try to allocate the default.
  91. *
  92. * If that fails then just stick with the existing stack
  93. */
  94. LONG CUndoStack::SetUndoLimit(
  95. LONG cUndoLim) //@parm New undo limit. May not be zero
  96. {
  97. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::SetUndoLimit");
  98. // If the undo limit is zero, we should get rid of the entire
  99. // undo stack instead.
  100. Assert(cUndoLim);
  101. if(_fSingleLevelMode)
  102. {
  103. // If fSingleLevelMode is on, we can't be the redo stack
  104. Assert(_fRedo == FALSE);
  105. if(cUndoLim != 1)
  106. {
  107. TRACEERRORSZ("Trying to grow/shrink the undo buffer while in"
  108. "single level mode");
  109. cUndoLim = 1;
  110. }
  111. }
  112. UndoAction *prgnew = new UndoAction[cUndoLim];
  113. if(prgnew)
  114. TransferToNewBuffer(prgnew, cUndoLim);
  115. else if(cUndoLim > DEFAULT_UNDO_SIZE && _cUndoLim < DEFAULT_UNDO_SIZE)
  116. {
  117. // We are trying to grow past the default but failed. So
  118. // try to allocate the default
  119. prgnew = new UndoAction[DEFAULT_UNDO_SIZE];
  120. if(prgnew)
  121. TransferToNewBuffer(prgnew, DEFAULT_UNDO_SIZE);
  122. }
  123. // In either success or failure, _cUndoLim will be set correctly.
  124. return _cUndoLim;
  125. }
  126. /*
  127. * CUndoStack::GetUndoLimit()
  128. *
  129. * @mfunc
  130. * Get current limit size
  131. *
  132. * @rdesc
  133. * Current undo limit
  134. */
  135. LONG CUndoStack::GetUndoLimit()
  136. {
  137. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetUndoLimit");
  138. return _cUndoLim;
  139. }
  140. /*
  141. * CUndoStack::PushAntiEvent (idName, pae)
  142. *
  143. * @mfunc
  144. * Adds an undoable event to the event stack
  145. *
  146. * @rdesc HRESULT
  147. *
  148. * @comm
  149. * Algorithm: if merging is set, then we merge the given antievent
  150. * list *into* the current list (assuming it's a typing undo action).
  151. */
  152. HRESULT CUndoStack::PushAntiEvent(
  153. UNDONAMEID idName, //@parm Name for this AE collection
  154. IAntiEvent *pae) //@parm AE collection
  155. {
  156. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::PushAntiEvent");
  157. // _index should be at next available position
  158. if(!_fMerge)
  159. {
  160. // clear out any existing event
  161. if(_prgActions[_index].pae != NULL)
  162. {
  163. DestroyAEList(_prgActions[_index].pae);
  164. _prgActions[_index].pae = NULL;
  165. }
  166. if(_fRedo)
  167. _ped->GetCallMgr()->SetNewRedo();
  168. else
  169. _ped->GetCallMgr()->SetNewUndo();
  170. }
  171. if(_fMerge)
  172. {
  173. IAntiEvent *paetemp = pae, *paeNext;
  174. DWORD i = GetPrev();
  175. // If these asserts fail, then somebody did not call
  176. // StopGroupTyping
  177. Assert(_prgActions[i].id == idName);
  178. Assert(idName == UID_TYPING);
  179. // Put existing antievent chain onto *end* of current one
  180. while((paeNext = paetemp->GetNext()) != NULL)
  181. paetemp = paeNext;
  182. paetemp->SetNext(_prgActions[i].pae);
  183. _index = i;
  184. }
  185. else if(_fGroupTyping)
  186. {
  187. // In this case, we are *starting* a group typing session.
  188. // Any subsequent push'es of anti events should be merged
  189. _fMerge = TRUE;
  190. }
  191. _prgActions[_index].pae = pae;
  192. _prgActions[_index].id = idName;
  193. Next();
  194. return NOERROR;
  195. }
  196. /*
  197. * CUndoStack::PopAndExecuteAntiEvent(pAE)
  198. *
  199. * @mfunc
  200. * Undo! Takes the most recent antievent and executes it
  201. *
  202. * @rdesc
  203. * HRESULT from invoking the antievents (AEs)
  204. */
  205. HRESULT CUndoStack::PopAndExecuteAntiEvent(
  206. void *pAE) //@parm If non-NULL, undo up to this point.
  207. {
  208. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::PopAndExecuteAntiEvent");
  209. HRESULT hresult = NOERROR;
  210. IAntiEvent *pae, *paeDoTo;
  211. LONG i, j;
  212. CCallMgr * pcallmgr = _ped->GetCallMgr();
  213. // We need to check to see if there are any non-empty undo builders
  214. // higher on the stack. In this case, we have been reentered
  215. if(pcallmgr->IsReEntered())
  216. {
  217. // There are two cases to handle: we are invoking redo or we
  218. // are invoking undo. If we are invoking undo and there are
  219. // existing undo actions in the undo builder, then simply commit
  220. // those actions and undo them. We can assert in this case
  221. // that the redo stack is empty.
  222. //
  223. // In the second case if we are invoking redo while there are
  224. // undo actions in progress, simply cancel the call. When the
  225. // undo actions are added, they will clear the redo stack.
  226. //
  227. // We never need to check for a redo builder as that _only_
  228. // gets created in this routine and it's use is carefully guarded.
  229. // Commit the antievents to this undo stack, so that we will simply
  230. // undo them first.
  231. IUndoBuilder *publdr = (CGenUndoBuilder *)pcallmgr->GetComponent(COMP_UNDOBUILDER);
  232. if(publdr)
  233. {
  234. TRACEWARNSZ("Undo/Redo Invoked with uncommitted antievents");
  235. TRACEWARNSZ(" Recovering....");
  236. if(_fRedo)
  237. {
  238. // If we are the redo stack, simply fail the redo call
  239. return NOERROR;
  240. }
  241. // Just commit the antievents; the routine below takes care of the rest
  242. publdr->Done();
  243. }
  244. }
  245. // If we are in single level mode, check to see if our current buffer is
  246. // empty. If so, simply delegate to the redo stack if it exists. We only
  247. // support this mode for dwDoToCookies being NULL. Note that we can't call
  248. // CanUndo here as it will consider the redo stack as well
  249. if(_fSingleLevelMode && !_prgActions[GetPrev()].pae)
  250. {
  251. Assert(_fRedo == FALSE);
  252. Assert(pAE == 0);
  253. if(_ped->GetRedoMgr())
  254. return _ped->GetRedoMgr()->PopAndExecuteAntiEvent(0);
  255. // Nothing to redo && nothing to do here; don't bother continuing
  256. return NOERROR;
  257. }
  258. // This next bit of logic is tricky. What is says is create
  259. // an undo builder for the stack *opposite* of the current one
  260. // (namely, undo actions go on the redo stack and vice versa).
  261. // Also, if we are the redo stack, then we don't want to flush
  262. // the redo stack as antievents are added to the undo stack.
  263. CGenUndoBuilder undobldr(_ped,
  264. (!_fRedo ? UB_REDO : UB_DONTFLUSHREDO) | UB_AUTOCOMMIT);
  265. // Obviously, we can't be grouping typing if we're undoing!
  266. StopGroupTyping();
  267. // _index by default points to the next available slot
  268. // so we need to back up to the previous one.
  269. Prev();
  270. // Do some verification on the cookie--make sure it's one of ours
  271. paeDoTo = (IAntiEvent *)pAE;
  272. if(paeDoTo)
  273. {
  274. for(i = 0, j = _index; i < _cUndoLim; i++)
  275. {
  276. if(IsCookieInList(_prgActions[j].pae, (IAntiEvent *)paeDoTo))
  277. {
  278. paeDoTo = _prgActions[j].pae;
  279. break;
  280. }
  281. // Go backwards through ring buffer; typically
  282. // paeDoTo will be "close" to the top
  283. j--;
  284. if(j < 0)
  285. j = _cUndoLim - 1;
  286. }
  287. if(i == _cUndoLim)
  288. {
  289. TRACEERRORSZ("Invalid Cookie passed into Undo; cookie ignored");
  290. hresult = E_INVALIDARG;
  291. paeDoTo = NULL;
  292. }
  293. }
  294. else
  295. paeDoTo = _prgActions[_index].pae;
  296. undobldr.SetNameID(_prgActions[_index].id);
  297. while(paeDoTo)
  298. {
  299. CUndoStackGuard guard(_ped);
  300. pae = _prgActions[_index].pae;
  301. Assert(pae);
  302. // Fixup our state _before_ calling Undo, so
  303. // that we can handle being reentered.
  304. _prgActions[_index].pae = NULL;
  305. hresult = guard.SafeUndo(pae, &undobldr);
  306. DestroyAEList(pae);
  307. if(pae == paeDoTo || guard.WasReEntered())
  308. paeDoTo = NULL;
  309. Prev();
  310. }
  311. // Put _index at the next unused slot
  312. Next();
  313. return hresult;
  314. }
  315. /*
  316. * CUndoStack::GetNameIDFromTopAE(pAE)
  317. *
  318. * @mfunc
  319. * Retrieve the name of the most recent undo-able operation
  320. *
  321. * @rdesc the name ID of the most recent collection of antievents
  322. */
  323. UNDONAMEID CUndoStack::GetNameIDFromAE(
  324. void *pAE) //@parm Antievent whose name is desired;
  325. // 0 for the top
  326. {
  327. IAntiEvent *pae = (IAntiEvent *)pAE;
  328. LONG i, j = GetPrev(); // _index by default points to next
  329. // available slot
  330. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetNameIDFromTopAE");
  331. if(pae == NULL)
  332. pae = _prgActions[j].pae;
  333. if(_fSingleLevelMode && !pae)
  334. {
  335. // If fSingleLevelMode is on, we can't be the redo stack
  336. Assert(_fRedo == FALSE);
  337. // If pae is NULL, our answer may be on the redo stack. Note that
  338. // if somebody tries to pass in a cookie while in SingleLevelMode,
  339. // they won't be able to get actions off the redo stack.
  340. if(_ped->GetRedoMgr())
  341. return _ped->GetRedoMgr()->GetNameIDFromAE(0);
  342. }
  343. for(i = 0; i < _cUndoLim; i++)
  344. {
  345. if(_prgActions[j].pae == pae)
  346. return _prgActions[j].id;
  347. j--;
  348. if(j < 0)
  349. j = _cUndoLim - 1;
  350. }
  351. return UID_UNKNOWN;
  352. }
  353. /*
  354. * CUndoStack::GetMergeAntiEvent ()
  355. *
  356. * @mfunc If we are in merge typing mode, then return the topmost
  357. * antievent
  358. *
  359. * @rdesc NULL or the current antievent if in merge mode
  360. */
  361. IAntiEvent *CUndoStack::GetMergeAntiEvent()
  362. {
  363. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetMergeAntiEvent");
  364. if(_fMerge)
  365. {
  366. LONG i = GetPrev(); // _index by default points to
  367. // next available slot
  368. Assert(_prgActions[i].pae); // Can't be in merge-antievent mode
  369. return _prgActions[i].pae; // if no antievent to merge with!!
  370. }
  371. return NULL;
  372. }
  373. /*
  374. * CUndoStack::GetTopAECookie()
  375. *
  376. * @mfunc Returns a cookie to the topmost antievent.
  377. *
  378. * @rdesc A cookie value. Note that this cookie is just the antievent
  379. * pointer, but clients shouldn't really know that.
  380. */
  381. void* CUndoStack::GetTopAECookie()
  382. {
  383. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetTopAECookie");
  384. return _prgActions[GetPrev()].pae;
  385. }
  386. /*
  387. * CUndoStack::ClearAll ()
  388. *
  389. * @mfunc
  390. * Removes any antievents that are currently in the undo stack
  391. */
  392. void CUndoStack::ClearAll()
  393. {
  394. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::ClearAll");
  395. for(LONG i = 0; i < _cUndoLim; i++)
  396. {
  397. if(_prgActions[i].pae)
  398. {
  399. DestroyAEList(_prgActions[i].pae);
  400. _prgActions[i].pae = NULL;
  401. }
  402. }
  403. // Just in case we've been grouping typing; clear the state.
  404. StopGroupTyping();
  405. }
  406. /*
  407. * CUndoStack::CanUndo()
  408. *
  409. * @mfunc
  410. * Indicates whether or not can undo operation can be performed
  411. * (in other words, are there any antievents in our buffer)
  412. *
  413. * @rdesc
  414. * TRUE -- antievents exist <nl>
  415. * FALSE -- no antievents <nl>
  416. */
  417. BOOL CUndoStack::CanUndo()
  418. {
  419. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::CanUndo");
  420. if(_prgActions[GetPrev()].pae) // _index by default points
  421. return TRUE; // to next available slot
  422. if(_fSingleLevelMode)
  423. {
  424. // If fSingleLevelMode is on, we can't be the redo stack
  425. Assert(_fRedo == FALSE);
  426. // If we are in single level mode, we are the undo stack.
  427. // Check to see if the redo stack can do something here.
  428. if(_ped->GetRedoMgr())
  429. return _ped->GetRedoMgr()->CanUndo();
  430. }
  431. return FALSE;
  432. }
  433. /*
  434. * CUndoStack::StartGroupTyping ()
  435. *
  436. * @mfunc
  437. * TOGGLES the group typing flag on. If fGroupTyping is set, then
  438. * all *typing* events will be merged together
  439. *
  440. * @comm
  441. * Algorithm:
  442. *
  443. * There are three interesting states: <nl>
  444. * -no group merge; every action just gets pushed onto the stack <nl>
  445. * -group merge started; the first action is pushed onto the stack<nl>
  446. * -group merge in progress; every action (as long as it's "typing")
  447. * is merged into the prior state <nl>
  448. *
  449. * See the state diagram in the implemenation doc for more details
  450. */
  451. void CUndoStack::StartGroupTyping()
  452. {
  453. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::StartGroupTyping");
  454. if(_fGroupTyping)
  455. _fMerge = TRUE;
  456. else
  457. {
  458. Assert(_fMerge == FALSE);
  459. _fGroupTyping = TRUE;
  460. }
  461. }
  462. /*
  463. * CUndoStack::StopGroupTyping ()
  464. *
  465. * @mfunc
  466. * TOGGLES the group typing flag off. If fGroupTyping is not set,
  467. * then no merging of typing antievents will be done
  468. */
  469. void CUndoStack::StopGroupTyping()
  470. {
  471. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::StopGroupTyping");
  472. _fGroupTyping = FALSE;
  473. _fMerge = FALSE;
  474. }
  475. /*
  476. * CUndoStack::EnableSingleLevelMode()
  477. *
  478. * @mfunc Turns on single level undo mode; in this mode, we behave just like
  479. * RichEdit 1.0 w.r.t. to Undo.
  480. *
  481. * @rdesc
  482. * HRESULT
  483. *
  484. * @comm This special mode means that undo is 1 level deep and everything
  485. * is accessed via UNDO messages. Thus, instead of redo to undo an
  486. * undo action, you simply use another undo message.
  487. *
  488. * @devnote This call is _ONLY_ allowed for the UndoStack; the redo
  489. * stack simply tags along. Note that caller is responsible for
  490. * ensuring that we are in an empty state.
  491. */
  492. HRESULT CUndoStack::EnableSingleLevelMode()
  493. {
  494. Assert(_ped->GetRedoMgr() == NULL ||
  495. _ped->GetRedoMgr()->CanUndo() == FALSE);
  496. Assert(CanUndo() == FALSE && _fRedo == FALSE);
  497. _fSingleLevelMode = TRUE;
  498. // For single level undo mode, it is very important to get
  499. // just 1 entry in the undo stack. If we can't do that,
  500. // then we better just fail.
  501. if(SetUndoLimit(1) != 1)
  502. {
  503. _fSingleLevelMode = FALSE;
  504. return E_OUTOFMEMORY;
  505. }
  506. if(_ped->GetRedoMgr())
  507. {
  508. // Doesn't matter if the redo manager fails to reset
  509. _ped->GetRedoMgr()->SetUndoLimit(1);
  510. }
  511. return NOERROR;
  512. }
  513. /*
  514. * CUndoStack::DisableSingleLevelMode()
  515. *
  516. * @mfunc This turns off the 1.0 undo compatibility mode and restores us
  517. * to the RichEdit 2.0 default undo state
  518. */
  519. void CUndoStack::DisableSingleLevelMode()
  520. {
  521. Assert(_ped->GetRedoMgr() == NULL ||
  522. _ped->GetRedoMgr()->CanUndo() == FALSE);
  523. Assert(CanUndo() == FALSE && _fRedo == FALSE);
  524. // We don't care about failures here; multi-level undo mode
  525. // can handle any sized undo stack
  526. _fSingleLevelMode = FALSE;
  527. SetUndoLimit(DEFAULT_UNDO_SIZE);
  528. if(_ped->GetRedoMgr())
  529. {
  530. // Doesn't matter if the redo manager can't grow back in
  531. // size; it just means that we won't have full redo capability.
  532. _ped->GetRedoMgr()->SetUndoLimit(DEFAULT_UNDO_SIZE);
  533. }
  534. }
  535. //
  536. // PRIVATE METHODS
  537. //
  538. /*
  539. * CUndoStack::Next()
  540. *
  541. * @mfunc
  542. * Sets _index to the next available slot
  543. */
  544. void CUndoStack::Next()
  545. {
  546. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::Next");
  547. _index++;
  548. if(_index == _cUndoLim)
  549. _index = 0;
  550. }
  551. /*
  552. * CUndoStack::Prev()
  553. *
  554. * @mfunc
  555. * Sets _index to the previous slot
  556. */
  557. void CUndoStack::Prev()
  558. {
  559. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::Prev");
  560. _index = GetPrev();
  561. }
  562. /*
  563. * CUndoStack::GetPrev()
  564. *
  565. * @mfunc
  566. * Figures out what the index to the previous slot
  567. * *should* be (but does not set it)
  568. *
  569. * @rdesc
  570. * Index of what the previous slot would be
  571. */
  572. LONG CUndoStack::GetPrev()
  573. {
  574. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetPrev");
  575. LONG i = _index - 1;
  576. if(i < 0)
  577. i = _cUndoLim - 1;
  578. return i;
  579. }
  580. /*
  581. * CUndoStack::IsCookieInList (pae, paeCookie)
  582. *
  583. * @mfunc
  584. * Determines whether or not the given DoTo cookie is in
  585. * the list of antievents.
  586. *
  587. * @rdesc TRUE/FALSE
  588. */
  589. BOOL CUndoStack::IsCookieInList(
  590. IAntiEvent *pae, //@parm List to check
  591. IAntiEvent *paeCookie) //@parm Cookie to check
  592. {
  593. while(pae)
  594. {
  595. if(pae == paeCookie)
  596. return TRUE;
  597. pae = pae->GetNext();
  598. }
  599. return FALSE;
  600. }
  601. /*
  602. * CUndoStack::TransferToNewBuffer (prgnew, cUndoLim)
  603. *
  604. * @mfunc
  605. * Transfers existing antievents to the given buffer and
  606. * swaps this undo stack to use the new buffer
  607. *
  608. * @comm
  609. * The algorithm is very straightforward; go backwards in
  610. * the ring buffer copying antievents over until either there
  611. * are no more antievents or the new buffer is full. Discard
  612. * any remaining antievents.
  613. */
  614. void CUndoStack::TransferToNewBuffer(
  615. UndoAction *prgnew,
  616. LONG cUndoLim)
  617. {
  618. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::TransferToNewBuffer");
  619. LONG iOld = 0,
  620. iNew = 0,
  621. iCopyStart = 0;
  622. // First clear new buffer.
  623. FillMemory(prgnew, 0, cUndoLim * sizeof(UndoAction));
  624. // If there is nothing to copy, don't bother
  625. if(!_prgActions || !_prgActions[GetPrev()].pae)
  626. goto SetState;
  627. // This is a bit counter-intuitive, but since the stack is really
  628. // a ring buffer, go *forwards* until you hit a non-NULL slot.
  629. // This will be the _end_ of the existing antievents.
  630. //
  631. // However, we need to make sure that if cUndoLim is
  632. // _smaller_ than _cUndoLim we only copy the final cUndoLim
  633. // antievents. We'll set iCopyStart to indicate when
  634. // we can start copying stuff.
  635. if(cUndoLim < _cUndoLim)
  636. iCopyStart = _cUndoLim - cUndoLim;
  637. for(; iOld < _cUndoLim; iOld++, Next())
  638. {
  639. if(!_prgActions[_index].pae)
  640. continue;
  641. if(iOld >= iCopyStart)
  642. {
  643. Assert(iNew < cUndoLim);
  644. prgnew[iNew] = _prgActions[_index]; // Copy over antievents
  645. iNew++;
  646. }
  647. else
  648. {
  649. // Otherwise, get rid of them
  650. DestroyAEList(_prgActions[_index].pae);
  651. _prgActions[_index].pae = NULL;
  652. }
  653. }
  654. SetState:
  655. // Start at index iNew
  656. _index = (iNew == cUndoLim) ? 0 : iNew;
  657. Assert(iNew <= cUndoLim);
  658. _cUndoLim = cUndoLim;
  659. if(_prgActions)
  660. delete _prgActions;
  661. _prgActions = prgnew;
  662. }
  663. //
  664. // CGenUndoBuilder implementation
  665. //
  666. //
  667. // Public methods
  668. //
  669. /*
  670. * CGenUndoBuilder::CGenUndoBuilder (ped, flags, ppubldr)
  671. *
  672. * @mfunc Constructor
  673. *
  674. * @comm
  675. * This is a *PUBLIC* constructor
  676. */
  677. CGenUndoBuilder::CGenUndoBuilder(
  678. CTxtEdit * ped, //@parm Edit context
  679. DWORD flags, //@parm flags (usually UB_AUTOCOMMIT)
  680. IUndoBuilder ** ppubldr) //@parm Ptr to undobldr interface
  681. {
  682. // Set everthing to NULL because instances can go on the stack.
  683. // _pundo is set below
  684. _publdrPrev = NULL;
  685. _idName = UID_UNKNOWN;
  686. _pfirstae = NULL;
  687. _fAutoCommit = (flags & UB_AUTOCOMMIT) != 0;
  688. _fStartGroupTyping = FALSE;
  689. _fDontFlushRedo = FALSE;
  690. _fInactive = FALSE;
  691. _ped = ped;
  692. CompName name;
  693. if(flags & UB_REDO)
  694. {
  695. _fRedo = TRUE;
  696. name = COMP_REDOBUILDER;
  697. _pundo = ped->GetRedoMgr();
  698. }
  699. else
  700. {
  701. _fRedo = FALSE;
  702. name = COMP_UNDOBUILDER;
  703. _pundo = ped->GetUndoMgr();
  704. }
  705. // If undo is on, set *ppubldr to be this undo builder; else NULL
  706. // TODO: do we need to link in inactive undo builders?
  707. if(ppubldr)
  708. {
  709. if(!ped->_fUseUndo) // Undo is disabled or suspended
  710. { // Still have undobldrs since stack
  711. *ppubldr = NULL; // alloc is efficient. Flag this
  712. _fInactive = TRUE; // one as inactive
  713. return;
  714. }
  715. *ppubldr = this;
  716. }
  717. if(flags & UB_DONTFLUSHREDO)
  718. _fDontFlushRedo = TRUE;
  719. // Now link ourselves to any undobuilders that are higher up on
  720. // the stack. Note that is is legal for multiple undo builders
  721. // to live within the same call context.
  722. _publdrPrev = (CGenUndoBuilder *)_ped->GetCallMgr()->GetComponent(name);
  723. // If we are in the middle of an undo, then we'll have two undo stacks
  724. // active, the undo stack and the redo stack. Don't like the two
  725. // together.
  726. if(_fDontFlushRedo)
  727. _publdrPrev = NULL;
  728. _ped->GetCallMgr()->RegisterComponent((IReEntrantComponent *)this,
  729. name);
  730. }
  731. /*
  732. * CGenUndoBuilder::~CGenUndoBuilder()
  733. *
  734. * @mfunc Destructor
  735. *
  736. * @comm
  737. * This is a *PUBLIC* destructor
  738. *
  739. * Algorithm:
  740. * If this builder hasn't been committed to an undo stack
  741. * via ::Done, then we must be sure to free up any resources
  742. * (antievents) we may be hanging onto
  743. */
  744. CGenUndoBuilder::~CGenUndoBuilder()
  745. {
  746. if(!_fInactive)
  747. _ped->GetCallMgr()->RevokeComponent((IReEntrantComponent *)this);
  748. if(_fAutoCommit)
  749. {
  750. Done();
  751. return;
  752. }
  753. // Free resources
  754. if(_pfirstae)
  755. DestroyAEList(_pfirstae);
  756. }
  757. /*
  758. * CGenUndoBuilder::SetNameID (idName)
  759. *
  760. * @mfunc
  761. * Allows a name to be assigned to this antievent collection.
  762. * The ID should be an index that can be used to retrieve a
  763. * language specific string (like "Paste"). This string is
  764. * typically composed into undo menu items (i.e. "Undo Paste").
  765. */
  766. void CGenUndoBuilder::SetNameID(
  767. UNDONAMEID idName) //@parm the name ID for this undo operation
  768. {
  769. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::SetNameID");
  770. // Don't delegate to the higher undobuilder, even if it exists. The
  771. // original name should win in reentrancy cases.
  772. _idName = idName;
  773. }
  774. /*
  775. * CGenUndoBuilder::AddAntiEvent (pae)
  776. *
  777. * @mfunc
  778. * Adds an antievent to the end of the list
  779. *
  780. * @rdesc NOERROR
  781. */
  782. HRESULT CGenUndoBuilder::AddAntiEvent(
  783. IAntiEvent *pae) //@parm Antievent to add
  784. {
  785. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::AddAntiEvent");
  786. if(_publdrPrev)
  787. return _publdrPrev->AddAntiEvent(pae);
  788. pae->SetNext(_pfirstae);
  789. _pfirstae = pae;
  790. return NOERROR;
  791. }
  792. /*
  793. * CGenUndoBuilder::GetTopAntiEvent()
  794. *
  795. * @mfunc Gets the top antievent for this context.
  796. *
  797. * @comm The current context can be either the current
  798. * operation *or* to a previous operation if we are in
  799. * merge typing mode.
  800. *
  801. * @rdesc top antievent
  802. */
  803. IAntiEvent *CGenUndoBuilder::GetTopAntiEvent()
  804. {
  805. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::GetTopAntiEvent");
  806. if(_publdrPrev)
  807. {
  808. Assert(_pfirstae == NULL);
  809. return _publdrPrev->GetTopAntiEvent();
  810. }
  811. if(!_pfirstae && _pundo)
  812. return _pundo->GetMergeAntiEvent();
  813. return _pfirstae;
  814. }
  815. /*
  816. * CGenUndoBuilder::Done ()
  817. *
  818. * @mfunc
  819. * Puts the combined antievents (if any) into the undo stack
  820. *
  821. * @rdesc
  822. * HRESULT
  823. */
  824. HRESULT CGenUndoBuilder::Done()
  825. {
  826. HRESULT hr = NOERROR;
  827. if(_publdrPrev)
  828. {
  829. Assert(_pfirstae == NULL);
  830. return NOERROR;
  831. }
  832. // If nothing changed, discard any selection antievents
  833. // or other no-op actions.
  834. if(!_ped->GetCallMgr()->GetChangeEvent())
  835. {
  836. Discard();
  837. return NOERROR;
  838. }
  839. if(_ped->GetDetectURL())
  840. _ped->GetDetectURL()->ScanAndUpdate(_pundo && _ped->_fUseUndo ? this : NULL);
  841. if(_pfirstae)
  842. {
  843. if(!_pundo)
  844. {
  845. // Yikes! There's no undo stack; better create one.
  846. // If we are a redo guy, we should create a redo
  847. // stack the size of the undo stack
  848. LONG cUndoLim = DEFAULT_UNDO_SIZE;
  849. if(_fRedo)
  850. {
  851. Assert(_ped->GetUndoMgr());
  852. cUndoLim = _ped->GetUndoMgr()->GetUndoLimit();
  853. }
  854. // FUTURE: A NULL ptr returned from CreateUndoMgr means either
  855. // we are out of memory, or the undo limit is set to 0. For the
  856. // latter case, we have collected AE's to push onto a non-existent
  857. // undo stack. It may be more efficient to not generate
  858. // the AE's at all when the undo limit is 0.
  859. _pundo = _ped->CreateUndoMgr(cUndoLim, _fRedo ? US_REDO : US_UNDO);
  860. if(!_pundo)
  861. goto CleanUp;
  862. }
  863. // We may need to flush the redo stack if we are adding
  864. // more antievents to the undo stack *AND* we haven't been
  865. // told not to flush the redo stack. The only time we won't
  866. // flush the redo stack is if it's the redo stack itself
  867. // adding antievents to undo.
  868. if(!_fRedo)
  869. {
  870. // If our destination is the undo stack, then check
  871. // to see if we should flush
  872. if(!_fDontFlushRedo)
  873. {
  874. IUndoMgr *predo = _ped->GetRedoMgr();
  875. if(predo)
  876. predo->ClearAll();
  877. }
  878. }
  879. else
  880. Assert(!_fDontFlushRedo);
  881. // If we should enter into the group typing state, inform
  882. // the undo manager. Note that we only do this *iff*
  883. // there is actually some antievent to put in the undo
  884. // manager. This makes the undo manager easier to implement.
  885. if(_fStartGroupTyping)
  886. _pundo->StartGroupTyping();
  887. hr = _pundo->PushAntiEvent(_idName, _pfirstae);
  888. // The change event flag should be set if we're adding
  889. // undo items! If this test is true, it probably means
  890. // the somebody earlier in the call stack sent change
  891. // notifications, e.g., via SendAllNotifications _before_
  892. // this undo context was committed _or_ it means that we
  893. // were reentered in some way that was not handled properly.
  894. // Needless to say, this is not an ideal state.
  895. CleanUp:
  896. Assert(_ped->GetCallMgr()->GetChangeEvent());
  897. IAntiEvent *paetemp = _pfirstae;
  898. _pfirstae = NULL;
  899. CommitAEList(_ped, paetemp);
  900. if(!_pundo || hr != NOERROR)
  901. {
  902. // Either we failed to add the AE's to the undo stack
  903. // or the undo limit is 0 in which case there won't be
  904. // an undo stack to push the AE's onto.
  905. DestroyAEList(paetemp);
  906. }
  907. }
  908. return hr;
  909. }
  910. /*
  911. * CGenUndoBuilder::Discard ()
  912. *
  913. * @mfunc
  914. * Gets rid of any antievents that we may be hanging onto without
  915. * executing or committing them. Typically used for recovering
  916. * from certain failure or reentrancy scenarios. Note that
  917. * an _entire_ antievent chain will be removed in this fashion.
  918. */
  919. void CGenUndoBuilder::Discard()
  920. {
  921. if(_pfirstae)
  922. {
  923. DestroyAEList(_pfirstae);
  924. _pfirstae = NULL;
  925. }
  926. else if(_publdrPrev)
  927. _publdrPrev->Discard();
  928. }
  929. /*
  930. * CGenUndoBuilder::StartGroupTyping ()
  931. *
  932. * @mfunc
  933. * Hangs onto the the fact that group typing should start.
  934. * We'll forward the the state transition to the undo manager
  935. * only if an antievent is actually added to the undo manager.
  936. *
  937. * @devnote
  938. * Group typing is disabled for redo stacks.
  939. */
  940. void CGenUndoBuilder::StartGroupTyping()
  941. {
  942. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::StartGroupTyping");
  943. _fStartGroupTyping = TRUE;
  944. }
  945. /*
  946. * CGenUndoBuilder::StopGroupTyping ()
  947. *
  948. * @mfunc
  949. * Forwards a stop grouped typing to the undo manager
  950. */
  951. void CGenUndoBuilder::StopGroupTyping()
  952. {
  953. TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::StopGroupTyping");
  954. if(_pundo)
  955. _pundo->StopGroupTyping();
  956. }
  957. //
  958. // CUndoStackGuard IMPLEMENTATION
  959. //
  960. /*
  961. * CUndoStackGuard::CUndoStackGuard(ped)
  962. *
  963. * @mfunc Constructor. Registers this object with the call manager
  964. */
  965. CUndoStackGuard::CUndoStackGuard(
  966. CTxtEdit *ped) //@parm the edit context
  967. {
  968. _ped = ped;
  969. _fReEntered = FALSE;
  970. _hr = NOERROR;
  971. ped->GetCallMgr()->RegisterComponent(this, COMP_UNDOGUARD);
  972. }
  973. /*
  974. * CUndoStackGuard::~CUndoStackGuard()
  975. *
  976. * @mfunc Destructor. Revokes the registration of this object
  977. * with the call manager
  978. */
  979. CUndoStackGuard::~CUndoStackGuard()
  980. {
  981. _ped->GetCallMgr()->RevokeComponent(this);
  982. }
  983. /*
  984. * CUndoStackGuard::SafeUndo (pae, publdr)
  985. *
  986. * @mfunc Loops through the given list of antievents, invoking
  987. * undo on each.
  988. *
  989. * @rdesc HRESULT, from the undo actions
  990. *
  991. * @devnote This routine is coded so that OnEnterContext can pick up
  992. * and continue the undo operation should we become reentered
  993. */
  994. HRESULT CUndoStackGuard::SafeUndo(
  995. IAntiEvent * pae, //@parm Start of antievent list
  996. IUndoBuilder *publdr) //@parm Undo builder to use
  997. {
  998. _publdr = publdr;
  999. while(pae)
  1000. {
  1001. _paeNext = pae->GetNext();
  1002. HRESULT hr = pae->Undo(_ped, publdr);
  1003. // Save first returned error
  1004. if(hr != NOERROR && _hr == NOERROR)
  1005. _hr = hr;
  1006. pae = (IAntiEvent *)_paeNext;
  1007. }
  1008. return _hr;
  1009. }
  1010. /*
  1011. * CUndoStackGuard::OnEnterContext
  1012. *
  1013. * @mfunc Handle reentrancy during undo operations.
  1014. *
  1015. * @devnote If this method is called, it's pretty serious. In general,
  1016. * we shoud never be reentered while processing undo stuff.
  1017. * However, to ensure that, block the incoming call and process
  1018. * the remaining actions.
  1019. */
  1020. void CUndoStackGuard::OnEnterContext()
  1021. {
  1022. TRACEWARNSZ("ReEntered while processing undo. Blocking call and");
  1023. TRACEWARNSZ(" attempting to recover.");
  1024. _fReEntered = TRUE;
  1025. SafeUndo((IAntiEvent *)_paeNext, _publdr);
  1026. }
  1027. //
  1028. // PUBLIC helper functions
  1029. //
  1030. /*
  1031. * DestroyAEList(pae)
  1032. *
  1033. * @func
  1034. * Destroys a list of antievents
  1035. */
  1036. void DestroyAEList(
  1037. IAntiEvent *pae) //@parm Antievent from which to start
  1038. {
  1039. IAntiEvent *pnext;
  1040. while(pae)
  1041. {
  1042. pnext = pae->GetNext();
  1043. pae->Destroy();
  1044. pae = pnext;
  1045. }
  1046. }
  1047. /*
  1048. * CommitAEList(ped, pae)
  1049. *
  1050. * @func
  1051. * Calls OnCommit to commit the given list of antievents
  1052. */
  1053. void CommitAEList(
  1054. CTxtEdit * ped, //@parm Edit context
  1055. IAntiEvent *pae) //@parm Antievent from which to start
  1056. {
  1057. IAntiEvent *pnext;
  1058. while(pae)
  1059. {
  1060. pnext = pae->GetNext();
  1061. pae->OnCommit(ped);
  1062. pae = pnext;
  1063. }
  1064. }
  1065. /*
  1066. * HandleSelectionAEInfo(ped, publdr, cp, cch, cpNext, cchNext, flags)
  1067. *
  1068. * @func HandleSelectionAEInfo | Tries to merge the given info with
  1069. * the existing undo context; if that fails, then it allocates
  1070. * a new selection antievent to handle the info
  1071. *
  1072. * @rdesc
  1073. * HRESULT
  1074. */
  1075. HRESULT HandleSelectionAEInfo(
  1076. CTxtEdit * ped, //@parm Edit context
  1077. IUndoBuilder *publdr, //@parm Undo context
  1078. LONG cp, //@parm cp to use for the sel ae
  1079. LONG cch, //@parm Signed selection extension
  1080. LONG cpNext, //@parm cp to use for the AE of the AE
  1081. LONG cchNext, //@parm cch to use for the AE of the AE
  1082. SELAE flags) //@parm Controls how to interpret the info
  1083. {
  1084. Assert(publdr);
  1085. // First see if we can merge the selection info into any existing
  1086. // antievents. Note that the selection antievent may be anywhere
  1087. // in the list, so go through them all
  1088. IAntiEvent *pae = publdr->GetTopAntiEvent();
  1089. if(pae)
  1090. {
  1091. SelRange sr;
  1092. sr.cp = cp;
  1093. sr.cch = cch;
  1094. sr.cpNext = cpNext;
  1095. sr.cchNext = cchNext;
  1096. sr.flags = flags;
  1097. while(pae)
  1098. {
  1099. if(pae->MergeData(MD_SELECTIONRANGE, (void *)&sr) == NOERROR)
  1100. break;
  1101. pae = pae->GetNext();
  1102. }
  1103. if(pae)
  1104. return NOERROR;
  1105. }
  1106. // Oops; can't do a merge. Go ahead and create a new antievent.
  1107. Assert(!pae);
  1108. pae = gAEDispenser.CreateSelectionAE(ped, cp, cch, cpNext, cchNext);
  1109. if(pae)
  1110. {
  1111. publdr->AddAntiEvent(pae);
  1112. return NOERROR;
  1113. }
  1114. return E_OUTOFMEMORY;
  1115. }