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.

750 lines
18 KiB

  1. #include "stdafx.h"
  2. #include "global.h"
  3. #include "pbrush.h"
  4. #include "pbrusdoc.h"
  5. #include "bmobject.h"
  6. #include "undo.h"
  7. #include "props.h"
  8. #ifdef _DEBUG
  9. #undef THIS_FILE
  10. static CHAR BASED_CODE THIS_FILE[] = __FILE__;
  11. #endif
  12. IMPLEMENT_DYNAMIC(CUndoBmObj, CBitmapObj)
  13. #include "memtrace.h"
  14. CUndoBmObj NEAR theUndo;
  15. static BOOL m_bFlushAtEnd;
  16. /////////////////////////////////////////////////////////////////////////////
  17. //
  18. // A CBmObjSequence is a packed array of slob property changes or custom
  19. // actions. Each record contains a property or action id, a pointer to
  20. // a slob, a property type, and a value (depending on the type).
  21. //
  22. // These sequences are used to store undo/redo information in theUndo.
  23. // Each undo/redo-able thing is contained in one CBmObjSequence.
  24. //
  25. CBmObjSequence::CBmObjSequence() : CByteArray(), m_strDescription()
  26. {
  27. SetSize(0, 100); // increase growth rate
  28. m_nCursor = 0;
  29. }
  30. CBmObjSequence::~CBmObjSequence()
  31. {
  32. Cleanup();
  33. }
  34. // Pull an array of bytes out of the sequence.
  35. //
  36. void CBmObjSequence::Retrieve( BYTE* rgb, int cb )
  37. {
  38. for (int ib = 0; ib < cb; ib += 1)
  39. *rgb++ = GetAt(m_nCursor++);
  40. }
  41. // Pull a string out the sequence.
  42. void CBmObjSequence::RetrieveStr( CString& str )
  43. {
  44. int nStrLen;
  45. RetrieveInt(nStrLen);
  46. if (nStrLen == 0)
  47. {
  48. str.Empty();
  49. }
  50. else
  51. {
  52. BYTE* pb = (BYTE*)str.GetBufferSetLength(nStrLen);
  53. for (int nByte = 0; nByte < nStrLen; nByte += 1)
  54. *pb++ = GetAt(m_nCursor++);
  55. str.ReleaseBuffer(nStrLen);
  56. }
  57. }
  58. // Traverse the sequence and remove any slobs that are contained within.
  59. //
  60. void CBmObjSequence::Cleanup()
  61. {
  62. m_nCursor = 0;
  63. while (m_nCursor < GetSize())
  64. {
  65. BYTE op;
  66. CBitmapObj* pSlob;
  67. int nPropID;
  68. RetrieveByte(op);
  69. RetrievePtr(pSlob);
  70. RetrieveInt(nPropID);
  71. switch (op)
  72. {
  73. default:
  74. TRACE1("Illegal undo opcode (%d)\n", op);
  75. ASSERT(FALSE);
  76. case CUndoBmObj::opAction:
  77. {
  78. int cbUndoRecord;
  79. RetrieveInt(cbUndoRecord);
  80. int ib = m_nCursor;
  81. pSlob->DeleteUndoAction(this, nPropID);
  82. m_nCursor = ib + cbUndoRecord;
  83. }
  84. break;
  85. case CUndoBmObj::opIntProp:
  86. case CUndoBmObj::opBoolProp:
  87. {
  88. int val;
  89. RetrieveInt(val);
  90. }
  91. break;
  92. case CUndoBmObj::opLongProp:
  93. {
  94. long val;
  95. RetrieveLong(val);
  96. }
  97. break;
  98. case CUndoBmObj::opDoubleProp:
  99. {
  100. double num;
  101. RetrieveNum(num);
  102. }
  103. break;
  104. case CUndoBmObj::opStrProp:
  105. {
  106. CString str;
  107. RetrieveStr(str);
  108. }
  109. break;
  110. case CUndoBmObj::opSlobProp:
  111. {
  112. CBitmapObj* pSlobVal;
  113. RetrievePtr(pSlobVal);
  114. }
  115. break;
  116. case CUndoBmObj::opRectProp:
  117. {
  118. CRect rcVal;
  119. RetrieveRect(rcVal);
  120. }
  121. break;
  122. case CUndoBmObj::opPointProp:
  123. {
  124. CPoint ptVal;
  125. RetrievePoint(ptVal);
  126. }
  127. break;
  128. }
  129. }
  130. }
  131. // Start looking right after the begin op for ops we really need to keep.
  132. // If none are found, the entire record is discarded below. (For now, we
  133. // only throw away records that are empty or consist only of selection
  134. // change ops.)
  135. //
  136. BOOL CBmObjSequence::IsUseful(CBitmapObj*& pLastSlob, int& nLastPropID)
  137. {
  138. m_nCursor = 0;
  139. while (m_nCursor < GetSize() && GetAt(m_nCursor) == CUndoBmObj::opAction)
  140. {
  141. BYTE op;
  142. int nAction, cbActionRecord;
  143. CBitmapObj* pSlob;
  144. RetrieveByte(op);
  145. ASSERT(op == CUndoBmObj::opAction);
  146. RetrievePtr(pSlob);
  147. RetrieveInt(nAction);
  148. RetrieveInt(cbActionRecord);
  149. if (nAction != A_PreSel && nAction != A_PostSel)
  150. {
  151. // Back cursor up to the opcode...
  152. m_nCursor -= sizeof (int) * 2 + sizeof (CBitmapObj*) + 1;
  153. break;
  154. }
  155. m_nCursor += cbActionRecord;
  156. }
  157. if (m_nCursor == GetSize())
  158. return FALSE; // sequnce consists only of selection changes
  159. // Now check if we should throw this away because it's just
  160. // modifying the same string or rectangle property as the last
  161. // undoable operation... This is an incredible hack to implement
  162. // a "poor man's" Multiple-Consecutive-Changes-to-a-Property-as-
  163. // One-Operation feature.
  164. BYTE op;
  165. RetrieveByte(op);
  166. if (op == CUndoBmObj::opStrProp || op == CUndoBmObj::opRectProp)
  167. {
  168. CBitmapObj* pSlob;
  169. int nPropID;
  170. RetrievePtr(pSlob);
  171. RetrieveInt(nPropID);
  172. nLastPropID = nPropID;
  173. pLastSlob = pSlob;
  174. }
  175. m_nCursor = 0;
  176. return TRUE;
  177. }
  178. // Perform the property changes and actions listed in the sequence.
  179. //
  180. void CBmObjSequence::Apply()
  181. {
  182. m_nCursor = 0;
  183. while (m_nCursor < GetSize())
  184. {
  185. BYTE op;
  186. CBitmapObj* pSlob;
  187. int nPropID;
  188. RetrieveByte(op);
  189. RetrievePtr(pSlob);
  190. RetrieveInt(nPropID);
  191. switch (op)
  192. {
  193. default:
  194. TRACE1("Illegal undo opcode (%d)\n", op);
  195. ASSERT(FALSE);
  196. case CUndoBmObj::opAction:
  197. pSlob->UndoAction(this, nPropID);
  198. break;
  199. case CUndoBmObj::opIntProp:
  200. case CUndoBmObj::opBoolProp:
  201. {
  202. int val;
  203. RetrieveInt(val);
  204. pSlob->SetIntProp(nPropID, val);
  205. }
  206. break;
  207. }
  208. }
  209. }
  210. /////////////////////////////////////////////////////////////////////////////
  211. CUndoBmObj::CUndoBmObj() : m_seqs()
  212. {
  213. ASSERT(this == &theUndo); // only one of these is allowed!
  214. m_nRecording = 0;
  215. m_cbUndo = 0;
  216. m_nMaxLevels = 2;
  217. m_pLastSlob = NULL;
  218. m_nLastPropID = 0;
  219. m_nPauseLevel = 0;
  220. m_nRedoSeqs = 0;
  221. }
  222. CUndoBmObj::~CUndoBmObj()
  223. {
  224. Flush();
  225. }
  226. // Set the maximum number of sequences that can be held at once.
  227. //
  228. void CUndoBmObj::SetMaxLevels(int nLevels)
  229. {
  230. if (nLevels < 1)
  231. return;
  232. m_nMaxLevels = nLevels;
  233. Truncate();
  234. }
  235. // Returns the maximum number of sequences that can be held at once.
  236. //
  237. int CUndoBmObj::GetMaxLevels() const
  238. {
  239. return m_nMaxLevels;
  240. }
  241. // Call this to after a sequence is recorded to prevent the next
  242. // sequence from being coalesced with it.
  243. //
  244. void CUndoBmObj::FlushLast()
  245. {
  246. m_pLastSlob = NULL;
  247. m_nLastPropID = 0;
  248. }
  249. // Call this at the start of an undoable user action. Calls may be nested
  250. // as long as each call to BeginUndo is balanced with a call to EndUndo.
  251. // Only the "outermost" calls actually have any affect on the undo buffer.
  252. //
  253. // The szCmd parameter should contain the text that you want to appear
  254. // after "Undo" in the Edit menu.
  255. //
  256. // The bResetCursor parameter is only used internally to modify behaviour
  257. // when recording redo sequences and you should NOT pass anything for this
  258. // parameter.
  259. //
  260. void CUndoBmObj::BeginUndo(const TCHAR* szCmd, BOOL bResetCursor)
  261. {
  262. #ifdef _DEBUG
  263. if (theApp.m_bLogUndo)
  264. TRACE2("BeginUndo: %s (%d)\n", szCmd, m_nRecording);
  265. #endif
  266. // Handle nesting
  267. m_nRecording += 1;
  268. if (m_nRecording != 1)
  269. return;
  270. if (bResetCursor) // this is the default case
  271. {
  272. // Disable Redo for non-Undo/Redo commands...
  273. while (m_nRedoSeqs > 0)
  274. {
  275. delete m_seqs.GetHead();
  276. m_seqs.RemoveHead();
  277. m_nRedoSeqs -= 1;
  278. }
  279. }
  280. m_pCurSeq = new CBmObjSequence;
  281. m_pCurSeq->m_strDescription = szCmd;
  282. m_bFlushAtEnd = FALSE;
  283. }
  284. // In most cases, this overloaded function will be called. It takes a
  285. // resource ID instead of a char*, allowing easier internationalization
  286. //
  287. void CUndoBmObj::BeginUndo(const UINT idCmd, BOOL bResetCursor)
  288. {
  289. CString strCmd;
  290. VERIFY(strCmd.LoadString(idCmd));
  291. BeginUndo(strCmd, bResetCursor);
  292. }
  293. // Call this at the end of an undoable user action to cause the sequence
  294. // since the BeginUndo to be stored in the undo buffer.
  295. //
  296. void CUndoBmObj::EndUndo()
  297. {
  298. #ifdef _DEBUG
  299. if (theApp.m_bLogUndo)
  300. TRACE1("EndUndo: %d\n", m_nRecording - 1);
  301. #endif
  302. ASSERT(m_nRecording > 0);
  303. // Handle nesting
  304. m_nRecording -= 1;
  305. if (m_nRecording != 0)
  306. return;
  307. if (!m_pCurSeq->IsUseful(m_pLastSlob, m_nLastPropID))
  308. {
  309. // Remove empty or otherwise useless undo records!
  310. delete m_pCurSeq;
  311. m_pCurSeq = NULL;
  312. return;
  313. }
  314. // We'll keep it, add it to the list...
  315. if (m_nRedoSeqs > 0)
  316. {
  317. // Add AFTER any redo sequences we have but before any undo's
  318. POSITION pos = m_seqs.FindIndex(m_nRedoSeqs - 1);
  319. ASSERT(pos != NULL);
  320. m_seqs.InsertAfter(pos, m_pCurSeq);
  321. }
  322. else
  323. {
  324. // Just add before any other undo sequences
  325. m_seqs.AddHead(m_pCurSeq);
  326. }
  327. m_pCurSeq = NULL;
  328. Truncate(); // Make sure the undo buffer doesn't get too big!
  329. if (m_bFlushAtEnd)
  330. Flush();
  331. }
  332. // This functions ensures there aren't too many levels in the buffer.
  333. //
  334. void CUndoBmObj::Truncate()
  335. {
  336. POSITION pos = m_seqs.FindIndex(m_nRedoSeqs + m_nMaxLevels);
  337. while (pos != NULL)
  338. {
  339. #ifdef _DEBUG
  340. if (theApp.m_bLogUndo)
  341. TRACE(TEXT("Undo record fell off the edge...\n"));
  342. #endif
  343. POSITION posRemove = pos;
  344. delete m_seqs.GetNext(pos);
  345. m_seqs.RemoveAt(posRemove);
  346. }
  347. }
  348. // Call this to perform an undo command.
  349. //
  350. void CUndoBmObj::DoUndo()
  351. {
  352. CWaitCursor waitCursor;
  353. if (m_nRedoSeqs == m_seqs.GetCount())
  354. return; // nothing to undo!
  355. m_bPerformingUndoRedo = TRUE;
  356. POSITION pos = m_seqs.FindIndex(m_nRedoSeqs);
  357. ASSERT(pos != NULL);
  358. CBmObjSequence* pSeq = (CBmObjSequence*)m_seqs.GetAt(pos);
  359. BeginUndo(pSeq->m_strDescription, FALSE); // Setup Redo
  360. // Remove this sequence after BeginUndo so the one inserted
  361. // there goes to the right place...
  362. m_seqs.RemoveAt(pos);
  363. pSeq->Apply();
  364. FlushLast();
  365. EndUndo();
  366. FlushLast();
  367. m_bPerformingUndoRedo = FALSE;
  368. delete pSeq;
  369. // Do not bump the redo count if the undo flushed the buffer! (This
  370. // happens when a resource is pasted/dropped, then opened, then a
  371. // property in it changes, and the user undoes back to before the
  372. // paste.)
  373. if (m_seqs.GetCount() != 0)
  374. m_nRedoSeqs += 1;
  375. }
  376. // Call this to perform a redo command.
  377. //
  378. void CUndoBmObj::DoRedo()
  379. {
  380. if (m_nRedoSeqs == 0)
  381. return; // nothing in redo buffer
  382. m_nRedoSeqs -= 1;
  383. DoUndo();
  384. // Do not drop the redo count if the undo flushed the buffer! (This
  385. // happens when a resource is pasted/dropped, then opened, then a
  386. // property in it changes, and the user undoes back to before the
  387. // paste.)
  388. if (m_seqs.GetCount() != 0)
  389. m_nRedoSeqs -= 1;
  390. }
  391. // Generate a string appropriate for the undo menu command.
  392. //
  393. void CUndoBmObj::GetUndoString(CString& strUndo)
  394. {
  395. static CString NEAR strUndoTemplate;
  396. if (strUndoTemplate.IsEmpty())
  397. VERIFY(strUndoTemplate.LoadString(IDS_UNDO));
  398. CString strUndoCmd;
  399. if (CanUndo())
  400. {
  401. POSITION pos = m_seqs.FindIndex(m_nRedoSeqs);
  402. strUndoCmd = ((CBmObjSequence*)m_seqs.GetAt(pos))->m_strDescription;
  403. }
  404. int cchUndo = strUndoTemplate.GetLength() - 2; // less 2 for "%s"
  405. wsprintf(strUndo.GetBufferSetLength(cchUndo + strUndoCmd.GetLength()),
  406. strUndoTemplate, (const TCHAR*)strUndoCmd);
  407. }
  408. // Generate a string appropriate for the redo menu command.
  409. //
  410. void CUndoBmObj::GetRedoString(CString& strRedo)
  411. {
  412. static CString NEAR strRedoTemplate;
  413. if (strRedoTemplate.IsEmpty())
  414. VERIFY(strRedoTemplate.LoadString(IDS_REDO));
  415. CString strRedoCmd;
  416. if (CanRedo())
  417. {
  418. POSITION pos = m_seqs.FindIndex(m_nRedoSeqs - 1);
  419. strRedoCmd = ((CBmObjSequence*)m_seqs.GetAt(pos))->m_strDescription;
  420. }
  421. int cchRedo = strRedoTemplate.GetLength() - 2; // less 2 for "%s"
  422. wsprintf(strRedo.GetBufferSetLength(cchRedo + strRedoCmd.GetLength()),
  423. strRedoTemplate, (const TCHAR*)strRedoCmd);
  424. }
  425. // Call this to completely empty the undo buffer.
  426. //
  427. void CUndoBmObj::Flush()
  428. {
  429. PreTerminateList(&m_seqs);
  430. m_cbUndo = 0;
  431. m_nRedoSeqs = 0;
  432. m_bFlushAtEnd = TRUE;
  433. }
  434. void CUndoBmObj::OnInform(CBitmapObj* pChangedSlob, UINT idChange)
  435. {
  436. if (idChange == SN_DESTROY)
  437. {
  438. // When a slob we have a reference to is deleted (for real), we
  439. // have no choice but to flush the whole buffer... This normally
  440. // only happens when a resource editor window is closed... (If
  441. // the slob's container is the undo buffer, then we are already
  442. // in the process of flushing, so don't recurse!)
  443. Flush();
  444. }
  445. CBitmapObj::OnInform(pChangedSlob, idChange);
  446. }
  447. //
  448. // The following functions are used by the CBitmapObj code to insert commands
  449. // into the undo/redo sequence currently being recorded. All of the On...
  450. // functions are used to record changes to the various types of properties
  451. // and are called by the CBitmapObj::Set...Prop functions exclusively.
  452. //
  453. // Insert an array of bytes.
  454. //
  455. UINT CUndoBmObj::Insert(const void* pv, int cb)
  456. {
  457. ASSERT(m_pCurSeq != NULL);
  458. BYTE* rgb = (BYTE*)pv;
  459. m_pCurSeq->InsertAt(0, 0, cb);
  460. for (int ib = 0; ib < cb; ib += 1)
  461. m_pCurSeq->SetAt(ib, *rgb++);
  462. return cb;
  463. }
  464. // Insert a string.
  465. //
  466. UINT CUndoBmObj::InsertStr(const TCHAR* sz)
  467. {
  468. ASSERT(m_pCurSeq != NULL);
  469. BYTE* pb = (BYTE*)sz;
  470. int nStrLen = lstrlen(sz);
  471. InsertInt(nStrLen);
  472. if (nStrLen > 0)
  473. {
  474. m_pCurSeq->InsertAt(sizeof (int), 0, nStrLen);
  475. for (int nByte = 0; nByte < nStrLen; nByte += 1)
  476. m_pCurSeq->SetAt(sizeof (int) + nByte, *pb++);
  477. }
  478. return nStrLen + sizeof (int);
  479. }
  480. void CUndoBmObj::OnSetIntProp(CBitmapObj* pChangedSlob, UINT nPropID, UINT nOldVal)
  481. {
  482. ASSERT(m_nRecording != 0);
  483. CIntUndoRecord undoRecord;
  484. undoRecord.m_op = opIntProp;
  485. undoRecord.m_pBitmapObj = pChangedSlob;
  486. undoRecord.m_nPropID = nPropID;
  487. undoRecord.m_nOldVal = nOldVal;
  488. Insert(&undoRecord, sizeof (undoRecord));
  489. pChangedSlob->AddDependant(this);
  490. }
  491. #ifdef _DEBUG
  492. /////////////////////////////////////////////////////////////////////////////
  493. //
  494. // Undo related debugging aids
  495. //
  496. void CBmObjSequence::Dump()
  497. {
  498. m_nCursor = 0;
  499. while (m_nCursor < GetSize())
  500. {
  501. BYTE op;
  502. CBitmapObj* pSlob;
  503. int nPropID;
  504. RetrieveByte(op);
  505. RetrievePtr(pSlob);
  506. RetrieveInt(nPropID);
  507. switch (op)
  508. {
  509. default:
  510. TRACE1("Illegal undo opcode (%d)\n", op);
  511. ASSERT(FALSE);
  512. case CUndoBmObj::opAction:
  513. {
  514. int cbUndoRecord;
  515. RetrieveInt(cbUndoRecord);
  516. m_nCursor += cbUndoRecord;
  517. TRACE3("opAction: pSlob = 0x%08lx, nActionID = %d, "
  518. TEXT("nBytes = %d\n"), pSlob, nPropID, cbUndoRecord);
  519. }
  520. break;
  521. case CUndoBmObj::opIntProp:
  522. case CUndoBmObj::opBoolProp:
  523. {
  524. int val;
  525. RetrieveInt(val);
  526. TRACE3("opInt: pSlob = 0x%08lx, nPropID = %d, val = %d\n",
  527. pSlob, nPropID, val);
  528. }
  529. break;
  530. case CUndoBmObj::opLongProp:
  531. {
  532. long val;
  533. RetrieveLong(val);
  534. TRACE3("opInt: pSlob = 0x%08lx, nPropID = %d, val = %ld\n",
  535. pSlob, nPropID, val);
  536. }
  537. break;
  538. case CUndoBmObj::opDoubleProp:
  539. {
  540. double num;
  541. RetrieveNum(num);
  542. TRACE3("opInt: pSlob = 0x%08lx, nPropID = %d, val = %f\n",
  543. pSlob, nPropID, num);
  544. }
  545. break;
  546. case CUndoBmObj::opStrProp:
  547. {
  548. CString str;
  549. RetrieveStr(str);
  550. if (str.GetLength() > 80)
  551. {
  552. str = str.Left(80);
  553. str += TEXT("...");
  554. }
  555. TRACE3("opStr: pSlob = 0x%08lx, nPropID = %d, val = %s\n",
  556. pSlob, nPropID, (const TCHAR*)str);
  557. }
  558. break;
  559. case CUndoBmObj::opSlobProp:
  560. {
  561. CBitmapObj* pSlobVal;
  562. RetrievePtr(pSlobVal);
  563. TRACE3("opInt: pSlob = 0x%08lx, nPropID = %d, "
  564. TEXT("val = 0x%08lx\n"), pSlob, nPropID, pSlobVal);
  565. }
  566. break;
  567. case CUndoBmObj::opRectProp:
  568. {
  569. CRect rcVal;
  570. RetrieveRect(rcVal);
  571. TRACE3("opRect: pSlob = 0x%08lx, nPropID = %d, "
  572. TEXT("val = %d,%d,%d,%d\n"), pSlob, nPropID, rcVal);
  573. }
  574. break;
  575. case CUndoBmObj::opPointProp:
  576. {
  577. CPoint ptVal;
  578. RetrievePoint(ptVal);
  579. TRACE3("opPoint: pSlob = 0x%08lx, nPropID = %d, "
  580. TEXT("val = %d,%d,%d,%d\n"), pSlob, nPropID, ptVal);
  581. }
  582. break;
  583. }
  584. }
  585. }
  586. void CUndoBmObj::Dump()
  587. {
  588. int nRecord = 0;
  589. POSITION pos = m_seqs.GetHeadPosition();
  590. while (pos != NULL)
  591. {
  592. CBmObjSequence* pSeq = (CBmObjSequence*)m_seqs.GetNext(pos);
  593. TRACE2("Record (%d) %s:\n", nRecord,
  594. nRecord < m_nRedoSeqs ? TEXT("redo") : TEXT("undo"));
  595. pSeq->Dump();
  596. nRecord += 1;
  597. }
  598. }
  599. extern "C" void DumpUndo()
  600. {
  601. theUndo.Dump();
  602. }
  603. #endif