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.

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