oleaut32: Uncomment a line to implement conversion from VT_DISPATCH to VT_BSTR.
[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, 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     switch(type)
60     {
61     case diUndoEndTransaction:
62       break;
63     case diUndoSetParagraphFormat:
64       assert(pdi);
65       CopyMemory(&pItem->member.para, &pdi->member.para, sizeof(ME_Paragraph));
66       pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
67       CopyMemory(pItem->member.para.pFmt, pdi->member.para.pFmt, sizeof(PARAFORMAT2));
68       break;
69     case diUndoInsertRun:
70       assert(pdi);
71       CopyMemory(&pItem->member.run, &pdi->member.run, sizeof(ME_Run));
72       pItem->member.run.strText = ME_StrDup(pItem->member.run.strText);
73       ME_AddRefStyle(pItem->member.run.style);
74       break;
75     case diUndoSetCharFormat:
76     case diUndoSetDefaultCharFormat:
77       break;
78     case diUndoDeleteRun:
79     case diUndoJoinParagraphs:
80       break;
81     case diUndoSplitParagraph:
82       pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
83       pItem->member.para.pFmt->cbSize = sizeof(PARAFORMAT2);
84       pItem->member.para.pFmt->dwMask = 0;
85  
86       break;
87     default:
88       assert(0 == "AddUndoItem, unsupported item type");
89       return NULL;
90     }
91     pItem->type = type;
92     pItem->prev = NULL;
93     if (editor->nUndoMode == umAddToUndo || editor->nUndoMode == umAddBackToUndo)
94     {
95       if (editor->nUndoMode == umAddToUndo)
96         TRACE("Pushing id=%s to undo stack, deleting redo stack\n", ME_GetDITypeName(type));
97       else
98         TRACE("Pushing id=%s to undo stack\n", ME_GetDITypeName(type));
99
100       pItem->next = editor->pUndoStack;
101       if (type == diUndoEndTransaction)
102         editor->nUndoStackSize++;
103       if (editor->pUndoStack)
104         editor->pUndoStack->prev = pItem;
105       else
106         editor->pUndoStackBottom = pItem;
107       editor->pUndoStack = pItem;
108       
109       if (editor->nUndoStackSize > editor->nUndoLimit)
110       { /* remove oldest undo from stack */
111         ME_DisplayItem *p = editor->pUndoStackBottom;
112         while (p->type !=diUndoEndTransaction)
113           p = p->prev; /*find new stack bottom */
114         editor->pUndoStackBottom = p->prev;
115           editor->pUndoStackBottom->next = NULL;
116         do
117         {
118           ME_DisplayItem *pp = p->next;
119           ME_DestroyDisplayItem(p);
120           p = pp;
121         } while (p);
122         editor->nUndoStackSize--;
123       }
124       /* any new operation (not redo) clears the redo stack */
125       if (editor->nUndoMode == umAddToUndo) {
126         ME_DisplayItem *p = editor->pRedoStack;
127         while(p)
128         {
129           ME_DisplayItem *pp = p->next;
130           ME_DestroyDisplayItem(p);
131           p = pp;
132         }
133         editor->pRedoStack = NULL;
134       }
135     }
136     else if (editor->nUndoMode == umAddToRedo)
137     {
138       TRACE("Pushing id=%s to redo stack\n", ME_GetDITypeName(type));
139       pItem->next = editor->pRedoStack;
140       if (editor->pRedoStack)
141         editor->pRedoStack->prev = pItem;
142       editor->pRedoStack = pItem;
143     }
144     else
145       assert(0);
146     return (ME_UndoItem *)pItem;
147   }
148 }
149
150 void ME_CommitUndo(ME_TextEditor *editor) {
151   
152   if (editor->nUndoMode == umIgnore)
153     return;
154   
155   assert(editor->nUndoMode == umAddToUndo);
156   
157   /* no transactions, no need to commit */
158   if (!editor->pUndoStack)
159     return;
160
161   /* no need to commit empty transactions */
162   if (editor->pUndoStack->type == diUndoEndTransaction)
163     return;
164     
165   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
166   ME_SendSelChange(editor);
167   editor->nModifyStep++;
168 }
169
170 void ME_PlayUndoItem(ME_TextEditor *editor, ME_DisplayItem *pItem)
171 {
172   ME_UndoItem *pUItem = (ME_UndoItem *)pItem;
173
174   if (editor->nUndoMode == umIgnore)
175     return;
176   TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem->type));
177
178   switch(pItem->type)
179   {
180   case diUndoEndTransaction:
181     assert(0);
182   case diUndoSetParagraphFormat:
183   {
184     ME_Cursor tmp;
185     ME_CursorFromCharOfs(editor, pItem->member.para.nCharOfs, &tmp);
186     ME_SetParaFormat(editor, ME_FindItemBack(tmp.pRun, diParagraph), pItem->member.para.pFmt);
187     break;
188   }
189   case diUndoSetCharFormat:
190   {
191     ME_SetCharFormat(editor, pUItem->nStart, pUItem->nLen, &pItem->member.ustyle->fmt);
192     break;
193   }
194   case diUndoSetDefaultCharFormat:
195   {
196     ME_SetDefaultCharFormat(editor, &pItem->member.ustyle->fmt);
197     break;
198   }
199   case diUndoInsertRun:
200   {
201     ME_InsertRun(editor, pItem->member.run.nCharOfs, pItem);
202     break;
203   }
204   case diUndoDeleteRun:
205   {
206     ME_InternalDeleteText(editor, pUItem->nStart, pUItem->nLen);
207     break;
208   }
209   case diUndoJoinParagraphs:
210   {
211     ME_Cursor tmp;
212     ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
213     /* the only thing that's needed is paragraph offset, so no need to split runs */
214     ME_JoinParagraphs(editor, ME_GetParagraph(tmp.pRun));
215     break;
216   }
217   case diUndoSplitParagraph:
218   {
219     ME_Cursor tmp;
220     ME_DisplayItem *new_para;
221     ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
222     if (tmp.nOffset)
223       tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
224     new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style);
225     assert(pItem->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
226     CopyMemory(new_para->member.para.pFmt, pItem->member.para.pFmt, sizeof(PARAFORMAT2));
227     break;
228   }
229   default:
230     assert(0 == "PlayUndoItem, unexpected type");
231   }
232 }
233
234 void ME_Undo(ME_TextEditor *editor) {
235   ME_DisplayItem *p;
236   ME_UndoMode nMode = editor->nUndoMode;
237   
238   if (editor->nUndoMode == umIgnore)
239     return;
240   assert(nMode == umAddToUndo || nMode == umIgnore);
241   
242   /* no undo items ? */
243   if (!editor->pUndoStack)
244     return;
245     
246   /* watch out for uncommited transactions ! */
247   assert(editor->pUndoStack->type == diUndoEndTransaction);
248   
249   editor->nUndoMode = umAddToRedo;
250   p = editor->pUndoStack->next;
251   ME_DestroyDisplayItem(editor->pUndoStack);
252   do {
253     ME_DisplayItem *pp = p;
254     ME_PlayUndoItem(editor, p);
255     p = p->next;
256     ME_DestroyDisplayItem(pp);
257   } while(p && p->type != diUndoEndTransaction);
258   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
259   editor->pUndoStack = p;
260   editor->nUndoStackSize--;
261   if (p)
262     p->prev = NULL;
263   editor->nUndoMode = nMode;
264   editor->nModifyStep--;
265   ME_UpdateRepaint(editor);
266 }
267
268 void ME_Redo(ME_TextEditor *editor) {
269   ME_DisplayItem *p;
270   ME_UndoMode nMode = editor->nUndoMode;
271   
272   assert(nMode == umAddToUndo || nMode == umIgnore);
273   
274   if (editor->nUndoMode == umIgnore)
275     return;
276   /* no redo items ? */
277   if (!editor->pRedoStack)
278     return;
279     
280   /* watch out for uncommited transactions ! */
281   assert(editor->pRedoStack->type == diUndoEndTransaction);
282   
283   editor->nUndoMode = umAddBackToUndo;
284   p = editor->pRedoStack->next;
285   ME_DestroyDisplayItem(editor->pRedoStack);
286   do {
287     ME_DisplayItem *pp = p;
288     ME_PlayUndoItem(editor, p);
289     p = p->next;
290     ME_DestroyDisplayItem(pp);
291   } while(p && p->type != diUndoEndTransaction);
292   ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
293   editor->pRedoStack = p;
294   if (p)
295     p->prev = NULL;
296   editor->nUndoMode = nMode;
297   editor->nModifyStep++;
298   ME_UpdateRepaint(editor);
299 }