riched20/tests: Make test_TxGetNaturalSize() static.
[wine] / dlls / riched20 / undo.c
1 /*
2  * RichEdit - functions dealing with editor object
3  *
4  * Copyright 2004 by Krzysztof Foltman
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "editor.h"
22
23 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
24
25 void ME_EmptyUndoStack(ME_TextEditor *editor)
26 {
27   ME_DisplayItem *p, *pNext;
28   
29   if (editor->nUndoMode == umIgnore)
30     return;
31   
32   TRACE("Emptying undo stack\n");
33
34   p = editor->pUndoStack;
35   editor->pUndoStack = editor->pUndoStackBottom = NULL;
36   editor->nUndoStackSize = 0;
37   while(p) {
38     pNext = p->next;
39     ME_DestroyDisplayItem(p);    
40     p = pNext;
41   } 
42   p = editor->pRedoStack;
43   editor->pRedoStack = NULL;
44   while(p) {
45     pNext = p->next;
46     ME_DestroyDisplayItem(p);    
47     p = pNext;
48   } 
49 }
50
51 ME_UndoItem *ME_AddUndoItem(ME_TextEditor *editor, ME_DIType type, const ME_DisplayItem *pdi) {
52   if (editor->nUndoMode == umIgnore)
53     return NULL;
54   else if (editor->nUndoLimit == 0)
55     return NULL;
56   else
57   {
58     ME_DisplayItem *pItem = (ME_DisplayItem *)ALLOC_OBJ(ME_UndoItem);
59     ((ME_UndoItem *)pItem)->nCR = ((ME_UndoItem *)pItem)->nLF = -1;
60     switch(type)
61     {
62     case diUndoPotentialEndTransaction:
63         /* only should be added for manually typed chars, not undos or redos */
64         assert(editor->nUndoMode == umAddToUndo);
65         /* intentional fall-through to next case */
66     case diUndoEndTransaction:
67       break;
68     case diUndoSetParagraphFormat:
69       assert(pdi);
70       pItem->member.para = pdi->member.para;
71       pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
72       *pItem->member.para.pFmt = *pdi->member.para.pFmt;
73       break;
74     case diUndoInsertRun:
75       assert(pdi);
76       pItem->member.run = pdi->member.run;
77       pItem->member.run.strText = ME_StrDup(pItem->member.run.strText);
78       ME_AddRefStyle(pItem->member.run.style);
79       if (pdi->member.run.ole_obj)
80       {
81         pItem->member.run.ole_obj = ALLOC_OBJ(*pItem->member.run.ole_obj);
82         ME_CopyReObject(pItem->member.run.ole_obj, pdi->member.run.ole_obj);
83       }
84       else pItem->member.run.ole_obj = NULL;
85       break;
86     case diUndoSetCharFormat:
87       break;
88     case diUndoDeleteRun:
89     case diUndoJoinParagraphs:
90       break;
91     case diUndoSplitParagraph:
92     {
93       ME_DisplayItem *prev_para = pdi->member.para.prev_para;
94       assert(pdi->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
95       pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
96       pItem->member.para.pFmt->cbSize = sizeof(PARAFORMAT2);
97       pItem->member.para.pFmt->dwMask = 0;
98       *pItem->member.para.pFmt = *pdi->member.para.pFmt;
99       pItem->member.para.border = pdi->member.para.border;
100       pItem->member.para.nFlags = prev_para->member.para.nFlags & ~MEPF_CELL;
101       pItem->member.para.pCell = NULL;
102       break;
103     }
104     default:
105       assert(0 == "AddUndoItem, unsupported item type");
106       return NULL;
107     }
108     pItem->type = type;
109     pItem->prev = NULL;
110     if (editor->nUndoMode == umAddToUndo || editor->nUndoMode == umAddBackToUndo)
111     {
112       if (editor->pUndoStack
113           && editor->pUndoStack->type == diUndoPotentialEndTransaction)
114       {
115           editor->pUndoStack->type = diUndoEndTransaction;
116       }
117       if (editor->nUndoMode == umAddToUndo)
118         TRACE("Pushing id=%s to undo stack, deleting redo stack\n", ME_GetDITypeName(type));
119       else
120         TRACE("Pushing id=%s to undo stack\n", ME_GetDITypeName(type));
121
122       pItem->next = editor->pUndoStack;
123       if (type == diUndoEndTransaction || type == diUndoPotentialEndTransaction)
124         editor->nUndoStackSize++;
125       if (editor->pUndoStack)
126         editor->pUndoStack->prev = pItem;
127       else
128         editor->pUndoStackBottom = pItem;
129       editor->pUndoStack = pItem;
130       
131       if (editor->nUndoStackSize > editor->nUndoLimit)
132       { /* remove oldest undo from stack */
133         ME_DisplayItem *p = editor->pUndoStackBottom;
134         while (p->type !=diUndoEndTransaction)
135           p = p->prev; /*find new stack bottom */
136         editor->pUndoStackBottom = p->prev;
137           editor->pUndoStackBottom->next = NULL;
138         do
139         {
140           ME_DisplayItem *pp = p->next;
141           ME_DestroyDisplayItem(p);
142           p = pp;
143         } while (p);
144         editor->nUndoStackSize--;
145       }
146       /* any new operation (not redo) clears the redo stack */
147       if (editor->nUndoMode == umAddToUndo) {
148         ME_DisplayItem *p = editor->pRedoStack;
149         while(p)
150         {
151           ME_DisplayItem *pp = p->next;
152           ME_DestroyDisplayItem(p);
153           p = pp;
154         }
155         editor->pRedoStack = NULL;
156       }
157     }
158     else if (editor->nUndoMode == umAddToRedo)
159     {
160       TRACE("Pushing id=%s to redo stack\n", ME_GetDITypeName(type));
161       pItem->next = editor->pRedoStack;
162       if (editor->pRedoStack)
163         editor->pRedoStack->prev = pItem;
164       editor->pRedoStack = pItem;
165     }
166     else
167       assert(0);
168     return (ME_UndoItem *)pItem;
169   }
170 }
171
172 /**
173  * Commits preceding changes into a transaction that can be undone together.
174  *
175  * This should be called after all the changes occur associated with an event
176  * so that the group of changes can be undone atomically as a transaction.
177  *
178  * This will have no effect the undo mode is set to ignore changes, or if no
179  * changes preceded calling this function before the last time it was called.
180  *
181  * This can also be used to conclude a coalescing transaction (used for grouping
182  * typed characters).
183  */
184 void ME_CommitUndo(ME_TextEditor *editor) {
185   if (editor->nUndoMode == umIgnore)
186     return;
187   
188   assert(editor->nUndoMode == umAddToUndo);
189   
190   /* no transactions, no need to commit */
191   if (!editor->pUndoStack)
192     return;
193
194   /* no need to commit empty transactions */
195   if (editor->pUndoStack->type == diUndoEndTransaction)
196     return;
197     
198   if (editor->pUndoStack->type == diUndoPotentialEndTransaction)
199   {
200       /* Previous transaction was as a result of characters typed,
201        * so the end of this transaction is confirmed. */
202       editor->pUndoStack->type = diUndoEndTransaction;
203       return;
204   }
205
206   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
207   ME_SendSelChange(editor);
208 }
209
210 /**
211  * Groups supsequent changes with previous ones for an undo if coalescing.
212  *
213  * Has no effect if the previous changes were followed by a ME_CommitUndo. This
214  * function will only have an affect if the previous changes were followed by
215  * a call to ME_CommitCoalescingUndo, which allows the transaction to be
216  * continued.
217  *
218  * This allows multiple consecutively typed characters to be grouped together
219  * to be undone by a single undo operation.
220  */
221 void ME_ContinueCoalescingTransaction(ME_TextEditor *editor)
222 {
223   ME_DisplayItem* p;
224
225   if (editor->nUndoMode == umIgnore)
226     return;
227
228   assert(editor->nUndoMode == umAddToUndo);
229
230   p = editor->pUndoStack;
231
232   if (p && p->type == diUndoPotentialEndTransaction) {
233     assert(p->next); /* EndTransactions shouldn't be at bottom of undo stack */
234     editor->pUndoStack = p->next;
235     editor->pUndoStack->prev = NULL;
236     editor->nUndoStackSize--;
237     ME_DestroyDisplayItem(p);
238   }
239 }
240
241 /**
242  * Commits preceding changes into a undo transaction that can be expanded.
243  *
244  * This function allows the transaction to be reopened with
245  * ME_ContinueCoalescingTransaction in order to continue the transaction.  If an
246  * undo item is added to the undo stack as a result of a change without the
247  * transaction being reopened, then the transaction will be ended, and the
248  * changes will become a part of the next transaction.
249  *
250  * This is used to allow typed characters to be grouped together since each
251  * typed character results in a single event, and each event adding undo items
252  * must be committed.  Using this function as opposed to ME_CommitUndo allows
253  * multiple events to be grouped, and undone together.
254  */
255 void ME_CommitCoalescingUndo(ME_TextEditor *editor)
256 {
257   if (editor->nUndoMode == umIgnore)
258     return;
259
260   assert(editor->nUndoMode == umAddToUndo);
261
262   /* no transactions, no need to commit */
263   if (!editor->pUndoStack)
264     return;
265
266   /* no need to commit empty transactions */
267   if (editor->pUndoStack->type == diUndoEndTransaction)
268     return;
269   if (editor->pUndoStack->type == diUndoPotentialEndTransaction)
270     return;
271
272   ME_AddUndoItem(editor, diUndoPotentialEndTransaction, NULL);
273   ME_SendSelChange(editor);
274 }
275
276 static void ME_PlayUndoItem(ME_TextEditor *editor, ME_DisplayItem *pItem)
277 {
278   ME_UndoItem *pUItem = (ME_UndoItem *)pItem;
279
280   if (editor->nUndoMode == umIgnore)
281     return;
282   TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem->type));
283
284   switch(pItem->type)
285   {
286   case diUndoPotentialEndTransaction:
287   case diUndoEndTransaction:
288     assert(0);
289   case diUndoSetParagraphFormat:
290   {
291     ME_Cursor tmp;
292     ME_DisplayItem *para;
293     ME_CursorFromCharOfs(editor, pItem->member.para.nCharOfs, &tmp);
294     para = ME_FindItemBack(tmp.pRun, diParagraph);
295     ME_AddUndoItem(editor, diUndoSetParagraphFormat, para);
296     *para->member.para.pFmt = *pItem->member.para.pFmt;
297     para->member.para.border = pItem->member.para.border;
298     break;
299   }
300   case diUndoSetCharFormat:
301   {
302     ME_SetCharFormat(editor, pUItem->nStart, pUItem->nLen, &pItem->member.ustyle->fmt);
303     break;
304   }
305   case diUndoInsertRun:
306   {
307     ME_InsertRun(editor, pItem->member.run.nCharOfs, pItem);
308     break;
309   }
310   case diUndoDeleteRun:
311   {
312     ME_InternalDeleteText(editor, pUItem->nStart, pUItem->nLen, TRUE);
313     break;
314   }
315   case diUndoJoinParagraphs:
316   {
317     ME_Cursor tmp;
318     ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
319     /* the only thing that's needed is paragraph offset, so no need to split runs */
320     ME_JoinParagraphs(editor, ME_GetParagraph(tmp.pRun), TRUE);
321     break;
322   }
323   case diUndoSplitParagraph:
324   {
325     ME_Cursor tmp;
326     ME_DisplayItem *this_para, *new_para;
327     BOOL bFixRowStart;
328     int paraFlags = pItem->member.para.nFlags & (MEPF_ROWSTART|MEPF_CELL|MEPF_ROWEND);
329     ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
330     if (tmp.nOffset)
331       tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
332     assert(pUItem->nCR >= 0);
333     assert(pUItem->nLF >= 0);
334     this_para = ME_GetParagraph(tmp.pRun);
335     bFixRowStart = this_para->member.para.nFlags & MEPF_ROWSTART;
336     if (bFixRowStart)
337     {
338       /* Re-insert the paragraph before the table, making sure the nFlag value
339        * is correct. */
340       this_para->member.para.nFlags &= ~MEPF_ROWSTART;
341     }
342     new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style,
343                                  pUItem->nCR, pUItem->nLF, paraFlags);
344     if (bFixRowStart)
345       new_para->member.para.nFlags |= MEPF_ROWSTART;
346     assert(pItem->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
347     *new_para->member.para.pFmt = *pItem->member.para.pFmt;
348     new_para->member.para.border = pItem->member.para.border;
349     if (pItem->member.para.pCell)
350     {
351       ME_DisplayItem *pItemCell, *pCell;
352       pItemCell = pItem->member.para.pCell;
353       pCell = new_para->member.para.pCell;
354       pCell->member.cell.nRightBoundary = pItemCell->member.cell.nRightBoundary;
355       pCell->member.cell.border = pItemCell->member.cell.border;
356     }
357     break;
358   }
359   default:
360     assert(0 == "PlayUndoItem, unexpected type");
361   }
362 }
363
364 BOOL ME_Undo(ME_TextEditor *editor) {
365   ME_DisplayItem *p;
366   ME_UndoMode nMode = editor->nUndoMode;
367   
368   if (editor->nUndoMode == umIgnore)
369     return FALSE;
370   assert(nMode == umAddToUndo || nMode == umIgnore);
371   
372   /* no undo items ? */
373   if (!editor->pUndoStack)
374     return FALSE;
375     
376   /* watch out for uncommitted transactions ! */
377   assert(editor->pUndoStack->type == diUndoEndTransaction
378         || editor->pUndoStack->type == diUndoPotentialEndTransaction);
379   
380   editor->nUndoMode = umAddToRedo;
381   p = editor->pUndoStack->next;
382   ME_DestroyDisplayItem(editor->pUndoStack);
383   editor->pUndoStack = p;
384   do {
385     p->prev = NULL;
386     ME_PlayUndoItem(editor, p);
387     editor->pUndoStack = p->next;
388     ME_DestroyDisplayItem(p);
389     p = editor->pUndoStack;
390   } while(p && p->type != diUndoEndTransaction);
391   if (p)
392     p->prev = NULL;
393   ME_MoveCursorFromTableRowStartParagraph(editor);
394   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
395   ME_CheckTablesForCorruption(editor);
396   editor->nUndoStackSize--;
397   editor->nUndoMode = nMode;
398   ME_UpdateRepaint(editor);
399   return TRUE;
400 }
401
402 BOOL ME_Redo(ME_TextEditor *editor) {
403   ME_DisplayItem *p;
404   ME_UndoMode nMode = editor->nUndoMode;
405   
406   assert(nMode == umAddToUndo || nMode == umIgnore);
407   
408   if (editor->nUndoMode == umIgnore)
409     return FALSE;
410   /* no redo items ? */
411   if (!editor->pRedoStack)
412     return FALSE;
413     
414   /* watch out for uncommitted transactions ! */
415   assert(editor->pRedoStack->type == diUndoEndTransaction);
416   
417   editor->nUndoMode = umAddBackToUndo;
418   p = editor->pRedoStack->next;
419   ME_DestroyDisplayItem(editor->pRedoStack);
420   editor->pRedoStack = p;
421   do {
422     p->prev = NULL;
423     ME_PlayUndoItem(editor, p);
424     editor->pRedoStack = p->next;
425     ME_DestroyDisplayItem(p);
426     p = editor->pRedoStack;
427   } while(p && p->type != diUndoEndTransaction);
428   if (p)
429     p->prev = NULL;
430   ME_MoveCursorFromTableRowStartParagraph(editor);
431   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
432   ME_CheckTablesForCorruption(editor);
433   editor->nUndoMode = nMode;
434   ME_UpdateRepaint(editor);
435   return TRUE;
436 }