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