Revert "winex11.drv: Optimise getting the bits of a DIB after calling SetDIBits."
[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       pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
93       pItem->member.para.pFmt->cbSize = sizeof(PARAFORMAT2);
94       pItem->member.para.pFmt->dwMask = 0;
95  
96       break;
97     default:
98       assert(0 == "AddUndoItem, unsupported item type");
99       return NULL;
100     }
101     pItem->type = type;
102     pItem->prev = NULL;
103     if (editor->nUndoMode == umAddToUndo || editor->nUndoMode == umAddBackToUndo)
104     {
105       if (editor->pUndoStack
106           && editor->pUndoStack->type == diUndoPotentialEndTransaction)
107       {
108           editor->pUndoStack->type = diUndoEndTransaction;
109       }
110       if (editor->nUndoMode == umAddToUndo)
111         TRACE("Pushing id=%s to undo stack, deleting redo stack\n", ME_GetDITypeName(type));
112       else
113         TRACE("Pushing id=%s to undo stack\n", ME_GetDITypeName(type));
114
115       pItem->next = editor->pUndoStack;
116       if (type == diUndoEndTransaction || type == diUndoPotentialEndTransaction)
117         editor->nUndoStackSize++;
118       if (editor->pUndoStack)
119         editor->pUndoStack->prev = pItem;
120       else
121         editor->pUndoStackBottom = pItem;
122       editor->pUndoStack = pItem;
123       
124       if (editor->nUndoStackSize > editor->nUndoLimit)
125       { /* remove oldest undo from stack */
126         ME_DisplayItem *p = editor->pUndoStackBottom;
127         while (p->type !=diUndoEndTransaction)
128           p = p->prev; /*find new stack bottom */
129         editor->pUndoStackBottom = p->prev;
130           editor->pUndoStackBottom->next = NULL;
131         do
132         {
133           ME_DisplayItem *pp = p->next;
134           ME_DestroyDisplayItem(p);
135           p = pp;
136         } while (p);
137         editor->nUndoStackSize--;
138       }
139       /* any new operation (not redo) clears the redo stack */
140       if (editor->nUndoMode == umAddToUndo) {
141         ME_DisplayItem *p = editor->pRedoStack;
142         while(p)
143         {
144           ME_DisplayItem *pp = p->next;
145           ME_DestroyDisplayItem(p);
146           p = pp;
147         }
148         editor->pRedoStack = NULL;
149       }
150     }
151     else if (editor->nUndoMode == umAddToRedo)
152     {
153       TRACE("Pushing id=%s to redo stack\n", ME_GetDITypeName(type));
154       pItem->next = editor->pRedoStack;
155       if (editor->pRedoStack)
156         editor->pRedoStack->prev = pItem;
157       editor->pRedoStack = pItem;
158     }
159     else
160       assert(0);
161     return (ME_UndoItem *)pItem;
162   }
163 }
164
165 /**
166  * Commits preceding changes into a transaction that can be undone together.
167  *
168  * This should be called after all the changes occur associated with an event
169  * so that the group of changes can be undone atomically as a transaction.
170  *
171  * This will have no effect the undo mode is set to ignore changes, or if no
172  * changes preceded calling this function before the last time it was called.
173  *
174  * This can also be used to conclude a coalescing transaction (used for grouping
175  * typed characters).
176  */
177 void ME_CommitUndo(ME_TextEditor *editor) {
178   if (editor->nUndoMode == umIgnore)
179     return;
180   
181   assert(editor->nUndoMode == umAddToUndo);
182   
183   /* no transactions, no need to commit */
184   if (!editor->pUndoStack)
185     return;
186
187   /* no need to commit empty transactions */
188   if (editor->pUndoStack->type == diUndoEndTransaction)
189     return;
190     
191   if (editor->pUndoStack->type == diUndoPotentialEndTransaction)
192   {
193       /* Previous transaction was as a result of characters typed,
194        * so the end of this transaction is confirmed. */
195       editor->pUndoStack->type = diUndoEndTransaction;
196       return;
197   }
198
199   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
200   ME_SendSelChange(editor);
201 }
202
203 /**
204  * Groups supsequent changes with previous ones for an undo if coalescing.
205  *
206  * Has no effect if the previous changes were followed by a ME_CommitUndo. This
207  * function will only have an affect if the previous changes were followed by
208  * a call to ME_CommitCoalescingUndo, which allows the transaction to be
209  * continued.
210  *
211  * This allows multiple consecutively typed characters to be grouped together
212  * to be undone by a single undo operation.
213  */
214 void ME_ContinueCoalescingTransaction(ME_TextEditor *editor)
215 {
216   ME_DisplayItem* p;
217
218   if (editor->nUndoMode == umIgnore)
219     return;
220
221   assert(editor->nUndoMode == umAddToUndo);
222
223   p = editor->pUndoStack;
224
225   if (p && p->type == diUndoPotentialEndTransaction) {
226     assert(p->next); /* EndTransactions shouldn't be at bottom of undo stack */
227     editor->pUndoStack = p->next;
228     editor->pUndoStack->prev = NULL;
229     editor->nUndoStackSize--;
230     ME_DestroyDisplayItem(p);
231   }
232 }
233
234 /**
235  * Commits preceding changes into a undo transaction that can be expanded.
236  *
237  * This function allows the transaction to be reopened with
238  * ME_ContinueCoalescingTransaction in order to continue the transaction.  If an
239  * undo item is added to the undo stack as a result of a change without the
240  * transaction being reopened, then the transaction will be ended, and the
241  * changes will become a part of the next transaction.
242  *
243  * This is used to allow typed characters to be grouped together since each
244  * typed character results in a single event, and each event adding undo items
245  * must be committed.  Using this function as opposed to ME_CommitUndo allows
246  * multiple events to be grouped, and undone together.
247  */
248 void ME_CommitCoalescingUndo(ME_TextEditor *editor)
249 {
250   if (editor->nUndoMode == umIgnore)
251     return;
252
253   assert(editor->nUndoMode == umAddToUndo);
254
255   /* no transactions, no need to commit */
256   if (!editor->pUndoStack)
257     return;
258
259   /* no need to commit empty transactions */
260   if (editor->pUndoStack->type == diUndoEndTransaction)
261     return;
262   if (editor->pUndoStack->type == diUndoPotentialEndTransaction)
263     return;
264
265   ME_AddUndoItem(editor, diUndoPotentialEndTransaction, NULL);
266   ME_SendSelChange(editor);
267 }
268
269 static void ME_PlayUndoItem(ME_TextEditor *editor, ME_DisplayItem *pItem)
270 {
271   ME_UndoItem *pUItem = (ME_UndoItem *)pItem;
272
273   if (editor->nUndoMode == umIgnore)
274     return;
275   TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem->type));
276
277   switch(pItem->type)
278   {
279   case diUndoPotentialEndTransaction:
280   case diUndoEndTransaction:
281     assert(0);
282   case diUndoSetParagraphFormat:
283   {
284     ME_Cursor tmp;
285     ME_CursorFromCharOfs(editor, pItem->member.para.nCharOfs, &tmp);
286     ME_SetParaFormat(editor, ME_FindItemBack(tmp.pRun, diParagraph), pItem->member.para.pFmt);
287     break;
288   }
289   case diUndoSetCharFormat:
290   {
291     ME_SetCharFormat(editor, pUItem->nStart, pUItem->nLen, &pItem->member.ustyle->fmt);
292     break;
293   }
294   case diUndoInsertRun:
295   {
296     ME_InsertRun(editor, pItem->member.run.nCharOfs, pItem);
297     break;
298   }
299   case diUndoDeleteRun:
300   {
301     ME_InternalDeleteText(editor, pUItem->nStart, pUItem->nLen);
302     break;
303   }
304   case diUndoJoinParagraphs:
305   {
306     ME_Cursor tmp;
307     ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
308     /* the only thing that's needed is paragraph offset, so no need to split runs */
309     ME_JoinParagraphs(editor, ME_GetParagraph(tmp.pRun));
310     break;
311   }
312   case diUndoSplitParagraph:
313   {
314     ME_Cursor tmp;
315     ME_DisplayItem *new_para;
316     ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
317     if (tmp.nOffset)
318       tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
319     assert(pUItem->nCR >= 0);
320     assert(pUItem->nLF >= 0);
321     new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style,
322       pUItem->nCR, pUItem->nLF);
323     assert(pItem->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
324     *new_para->member.para.pFmt = *pItem->member.para.pFmt;
325     break;
326   }
327   default:
328     assert(0 == "PlayUndoItem, unexpected type");
329   }
330 }
331
332 BOOL ME_Undo(ME_TextEditor *editor) {
333   ME_DisplayItem *p;
334   ME_UndoMode nMode = editor->nUndoMode;
335   
336   if (editor->nUndoMode == umIgnore)
337     return FALSE;
338   assert(nMode == umAddToUndo || nMode == umIgnore);
339   
340   /* no undo items ? */
341   if (!editor->pUndoStack)
342     return FALSE;
343     
344   /* watch out for uncommitted transactions ! */
345   assert(editor->pUndoStack->type == diUndoEndTransaction
346         || editor->pUndoStack->type == diUndoPotentialEndTransaction);
347   
348   editor->nUndoMode = umAddToRedo;
349   p = editor->pUndoStack->next;
350   ME_DestroyDisplayItem(editor->pUndoStack);
351   editor->pUndoStack = p;
352   do {
353     p->prev = NULL;
354     ME_PlayUndoItem(editor, p);
355     editor->pUndoStack = p->next;
356     ME_DestroyDisplayItem(p);
357     p = editor->pUndoStack;
358   } while(p && p->type != diUndoEndTransaction);
359   if (p)
360     p->prev = NULL;
361   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
362   editor->nUndoStackSize--;
363   editor->nUndoMode = nMode;
364   ME_UpdateRepaint(editor);
365   return TRUE;
366 }
367
368 BOOL ME_Redo(ME_TextEditor *editor) {
369   ME_DisplayItem *p;
370   ME_UndoMode nMode = editor->nUndoMode;
371   
372   assert(nMode == umAddToUndo || nMode == umIgnore);
373   
374   if (editor->nUndoMode == umIgnore)
375     return FALSE;
376   /* no redo items ? */
377   if (!editor->pRedoStack)
378     return FALSE;
379     
380   /* watch out for uncommitted transactions ! */
381   assert(editor->pRedoStack->type == diUndoEndTransaction);
382   
383   editor->nUndoMode = umAddBackToUndo;
384   p = editor->pRedoStack->next;
385   ME_DestroyDisplayItem(editor->pRedoStack);
386   editor->pRedoStack = p;
387   do {
388     p->prev = NULL;
389     ME_PlayUndoItem(editor, p);
390     editor->pRedoStack = p->next;
391     ME_DestroyDisplayItem(p);
392     p = editor->pRedoStack;
393   } while(p && p->type != diUndoEndTransaction);
394   if (p)
395     p->prev = NULL;
396   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
397   editor->nUndoMode = nMode;
398   ME_UpdateRepaint(editor);
399   return TRUE;
400 }