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