riched20: Move undo handling to a different set of structs as the display item struct...
[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 static void destroy_undo_item( struct undo_item *undo )
26 {
27     switch( undo->type )
28     {
29     case undo_insert_run:
30         heap_free( undo->u.insert_run.str );
31         ME_ReleaseStyle( undo->u.insert_run.style );
32         break;
33     case undo_split_para:
34         ME_DestroyString( undo->u.split_para.eol_str );
35         break;
36     default:
37         break;
38     }
39
40     heap_free( undo );
41 }
42
43 static void empty_redo_stack(ME_TextEditor *editor)
44 {
45     struct undo_item *cursor, *cursor2;
46     LIST_FOR_EACH_ENTRY_SAFE( cursor, cursor2, &editor->redo_stack, struct undo_item, entry )
47     {
48         list_remove( &cursor->entry );
49         destroy_undo_item( cursor );
50     }
51 }
52
53 void ME_EmptyUndoStack(ME_TextEditor *editor)
54 {
55   struct undo_item *cursor, *cursor2;
56   if (editor->nUndoMode == umIgnore)
57     return;
58   
59   TRACE("Emptying undo stack\n");
60
61   editor->nUndoStackSize = 0;
62
63   LIST_FOR_EACH_ENTRY_SAFE( cursor, cursor2, &editor->undo_stack, struct undo_item, entry )
64   {
65       list_remove( &cursor->entry );
66       destroy_undo_item( cursor );
67   }
68
69   empty_redo_stack( editor );
70 }
71
72 static struct undo_item *add_undo( ME_TextEditor *editor, enum undo_type type )
73 {
74     struct undo_item *undo, *item;
75     struct list *head;
76
77     if (editor->nUndoMode == umIgnore) return NULL;
78     if (editor->nUndoLimit == 0) return NULL;
79
80     undo = heap_alloc( sizeof(*undo) );
81     if (!undo) return NULL;
82     undo->type = type;
83
84     if (editor->nUndoMode == umAddToUndo || editor->nUndoMode == umAddBackToUndo)
85     {
86
87         head = list_head( &editor->undo_stack );
88         if (head)
89         {
90             item = LIST_ENTRY( head, struct undo_item, entry );
91             if (item->type == undo_potential_end_transaction)
92                 item->type = undo_end_transaction;
93         }
94
95         if (editor->nUndoMode == umAddToUndo)
96             TRACE("Pushing id=%d to undo stack, deleting redo stack\n", type);
97         else
98             TRACE("Pushing id=%d to undo stack\n", type);
99
100         list_add_head( &editor->undo_stack, &undo->entry );
101
102         if (type == undo_end_transaction || type == undo_potential_end_transaction)
103             editor->nUndoStackSize++;
104
105         if (editor->nUndoStackSize > editor->nUndoLimit)
106         {
107             struct undo_item *cursor2;
108             /* remove oldest undo from stack */
109             LIST_FOR_EACH_ENTRY_SAFE_REV( item, cursor2, &editor->undo_stack, struct undo_item, entry )
110             {
111                 BOOL done = (item->type == undo_end_transaction);
112                 list_remove( &item->entry );
113                 destroy_undo_item( item );
114                 if (done) break;
115             }
116             editor->nUndoStackSize--;
117         }
118
119         /* any new operation (not redo) clears the redo stack */
120         if (editor->nUndoMode == umAddToUndo) empty_redo_stack( editor );
121     }
122     else if (editor->nUndoMode == umAddToRedo)
123     {
124         TRACE("Pushing id=%d to redo stack\n", type);
125         list_add_head( &editor->redo_stack, &undo->entry );
126     }
127
128     return undo;
129 }
130
131 BOOL add_undo_insert_run( ME_TextEditor *editor, int pos, const WCHAR *str, int len, int flags, ME_Style *style )
132 {
133     struct undo_item *undo = add_undo( editor, undo_insert_run );
134     if (!undo) return FALSE;
135
136     undo->u.insert_run.str = heap_alloc( (len + 1) * sizeof(WCHAR) );
137     if (!undo->u.insert_run.str)
138     {
139         ME_EmptyUndoStack( editor );
140         return FALSE;
141     }
142     memcpy( undo->u.insert_run.str, str, len * sizeof(WCHAR) );
143     undo->u.insert_run.str[len] = 0;
144     undo->u.insert_run.pos = pos;
145     undo->u.insert_run.len = len;
146     undo->u.insert_run.flags = flags;
147     undo->u.insert_run.style = style;
148     ME_AddRefStyle( style );
149     return TRUE;
150 }
151
152 BOOL add_undo_set_para_fmt( ME_TextEditor *editor, const ME_Paragraph *para )
153 {
154     struct undo_item *undo = add_undo( editor, undo_set_para_fmt );
155     if (!undo) return FALSE;
156
157     undo->u.set_para_fmt.pos = para->nCharOfs;
158     undo->u.set_para_fmt.fmt = *para->pFmt;
159     undo->u.set_para_fmt.border = para->border;
160
161     return TRUE;
162 }
163
164 BOOL add_undo_set_char_fmt( ME_TextEditor *editor, int pos, int len, const CHARFORMAT2W *fmt )
165 {
166     struct undo_item *undo = add_undo( editor, undo_set_char_fmt );
167     if (!undo) return FALSE;
168
169     undo->u.set_char_fmt.pos = pos;
170     undo->u.set_char_fmt.len = len;
171     undo->u.set_char_fmt.fmt = *fmt;
172
173     return TRUE;
174 }
175
176 BOOL add_undo_join_paras( ME_TextEditor *editor, int pos )
177 {
178     struct undo_item *undo = add_undo( editor, undo_join_paras );
179     if (!undo) return FALSE;
180
181     undo->u.join_paras.pos = pos;
182     return TRUE;
183 }
184
185 BOOL add_undo_split_para( ME_TextEditor *editor, const ME_Paragraph *para, const ME_Run *run, const ME_Cell *cell )
186 {
187     struct undo_item *undo = add_undo( editor, undo_split_para );
188     if (!undo) return FALSE;
189
190     undo->u.split_para.pos = para->nCharOfs - run->strText->nLen;
191     undo->u.split_para.eol_str = ME_StrDup( run->strText );
192     undo->u.split_para.fmt = *para->pFmt;
193     undo->u.split_para.border = para->border;
194     undo->u.split_para.flags = para->prev_para->member.para.nFlags & ~MEPF_CELL;
195
196     if (cell)
197     {
198         undo->u.split_para.cell_border = cell->border;
199         undo->u.split_para.cell_right_boundary = cell->nRightBoundary;
200     }
201     return TRUE;
202 }
203
204 BOOL add_undo_delete_run( ME_TextEditor *editor, int pos, int len )
205 {
206     struct undo_item *undo = add_undo( editor, undo_delete_run );
207     if (!undo) return FALSE;
208
209     undo->u.delete_run.pos = pos;
210     undo->u.delete_run.len = len;
211
212     return TRUE;
213 }
214
215 /**
216  * Commits preceding changes into a transaction that can be undone together.
217  *
218  * This should be called after all the changes occur associated with an event
219  * so that the group of changes can be undone atomically as a transaction.
220  *
221  * This will have no effect the undo mode is set to ignore changes, or if no
222  * changes preceded calling this function before the last time it was called.
223  *
224  * This can also be used to conclude a coalescing transaction (used for grouping
225  * typed characters).
226  */
227 void ME_CommitUndo(ME_TextEditor *editor)
228 {
229   struct undo_item *item;
230   struct list *head;
231
232   if (editor->nUndoMode == umIgnore)
233     return;
234   
235   assert(editor->nUndoMode == umAddToUndo);
236   
237   /* no transactions, no need to commit */
238   head = list_head( &editor->undo_stack );
239   if (!head) return;
240
241   /* no need to commit empty transactions */
242   item = LIST_ENTRY( head, struct undo_item, entry );
243   if (item->type == undo_end_transaction) return;
244
245   if (item->type == undo_potential_end_transaction)
246   {
247       item->type = undo_end_transaction;
248       return;
249   }
250
251   add_undo( editor, undo_end_transaction );
252 }
253
254 /**
255  * Groups subsequent changes with previous ones for an undo if coalescing.
256  *
257  * Has no effect if the previous changes were followed by a ME_CommitUndo. This
258  * function will only have an affect if the previous changes were followed by
259  * a call to ME_CommitCoalescingUndo, which allows the transaction to be
260  * continued.
261  *
262  * This allows multiple consecutively typed characters to be grouped together
263  * to be undone by a single undo operation.
264  */
265 void ME_ContinueCoalescingTransaction(ME_TextEditor *editor)
266 {
267   struct undo_item *item;
268   struct list *head;
269
270   if (editor->nUndoMode == umIgnore)
271     return;
272
273   assert(editor->nUndoMode == umAddToUndo);
274
275   head = list_head( &editor->undo_stack );
276   if (!head) return;
277
278   item = LIST_ENTRY( head, struct undo_item, entry );
279   if (item->type == undo_potential_end_transaction)
280   {
281     list_remove( &item->entry );
282     editor->nUndoStackSize--;
283     destroy_undo_item( item );
284   }
285 }
286
287 /**
288  * Commits preceding changes into a undo transaction that can be expanded.
289  *
290  * This function allows the transaction to be reopened with
291  * ME_ContinueCoalescingTransaction in order to continue the transaction.  If an
292  * undo item is added to the undo stack as a result of a change without the
293  * transaction being reopened, then the transaction will be ended, and the
294  * changes will become a part of the next transaction.
295  *
296  * This is used to allow typed characters to be grouped together since each
297  * typed character results in a single event, and each event adding undo items
298  * must be committed.  Using this function as opposed to ME_CommitUndo allows
299  * multiple events to be grouped, and undone together.
300  */
301 void ME_CommitCoalescingUndo(ME_TextEditor *editor)
302 {
303   struct undo_item *item;
304   struct list *head;
305
306   if (editor->nUndoMode == umIgnore)
307     return;
308
309   assert(editor->nUndoMode == umAddToUndo);
310
311   head = list_head( &editor->undo_stack );
312   if (!head) return;
313
314   /* no need to commit empty transactions */
315   item = LIST_ENTRY( head, struct undo_item, entry );
316   if (item->type == undo_end_transaction ||
317       item->type == undo_potential_end_transaction)
318       return;
319
320   add_undo( editor, undo_potential_end_transaction );
321 }
322
323 static void ME_PlayUndoItem(ME_TextEditor *editor, struct undo_item *undo)
324 {
325
326   if (editor->nUndoMode == umIgnore)
327     return;
328   TRACE("Playing undo/redo item, id=%d\n", undo->type);
329
330   switch(undo->type)
331   {
332   case undo_potential_end_transaction:
333   case undo_end_transaction:
334     assert(0);
335   case undo_set_para_fmt:
336   {
337     ME_Cursor tmp;
338     ME_DisplayItem *para;
339     ME_CursorFromCharOfs(editor, undo->u.set_para_fmt.pos, &tmp);
340     para = ME_FindItemBack(tmp.pRun, diParagraph);
341     add_undo_set_para_fmt( editor, &para->member.para );
342     *para->member.para.pFmt = undo->u.set_para_fmt.fmt;
343     para->member.para.border = undo->u.set_para_fmt.border;
344     break;
345   }
346   case undo_set_char_fmt:
347   {
348     ME_Cursor start, end;
349     ME_CursorFromCharOfs(editor, undo->u.set_char_fmt.pos, &start);
350     end = start;
351     ME_MoveCursorChars(editor, &end, undo->u.set_char_fmt.len);
352     ME_SetCharFormat(editor, &start, &end, &undo->u.set_char_fmt.fmt);
353     break;
354   }
355   case undo_insert_run:
356   {
357     ME_Cursor tmp;
358     ME_CursorFromCharOfs(editor, undo->u.insert_run.pos, &tmp);
359     ME_InsertRunAtCursor(editor, &tmp, undo->u.insert_run.style,
360                          undo->u.insert_run.str,
361                          undo->u.insert_run.len,
362                          undo->u.insert_run.flags);
363     break;
364   }
365   case undo_delete_run:
366   {
367     ME_Cursor tmp;
368     ME_CursorFromCharOfs(editor, undo->u.delete_run.pos, &tmp);
369     ME_InternalDeleteText(editor, &tmp, undo->u.delete_run.len, TRUE);
370     break;
371   }
372   case undo_join_paras:
373   {
374     ME_Cursor tmp;
375     ME_CursorFromCharOfs(editor, undo->u.join_paras.pos, &tmp);
376     ME_JoinParagraphs(editor, tmp.pPara, TRUE);
377     break;
378   }
379   case undo_split_para:
380   {
381     ME_Cursor tmp;
382     ME_DisplayItem *this_para, *new_para;
383     BOOL bFixRowStart;
384     int paraFlags = undo->u.split_para.flags & (MEPF_ROWSTART|MEPF_CELL|MEPF_ROWEND);
385     ME_CursorFromCharOfs(editor, undo->u.split_para.pos, &tmp);
386     if (tmp.nOffset)
387       ME_SplitRunSimple(editor, &tmp);
388     this_para = tmp.pPara;
389     bFixRowStart = this_para->member.para.nFlags & MEPF_ROWSTART;
390     if (bFixRowStart)
391     {
392       /* Re-insert the paragraph before the table, making sure the nFlag value
393        * is correct. */
394       this_para->member.para.nFlags &= ~MEPF_ROWSTART;
395     }
396     new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style,
397                                  undo->u.split_para.eol_str, paraFlags);
398     /* ME_SplitParagraph owns eol_str */
399     undo->u.split_para.eol_str = NULL;
400     if (bFixRowStart)
401       new_para->member.para.nFlags |= MEPF_ROWSTART;
402     *new_para->member.para.pFmt = undo->u.split_para.fmt;
403     new_para->member.para.border = undo->u.split_para.border;
404     if (paraFlags)
405     {
406       ME_DisplayItem *pCell = new_para->member.para.pCell;
407       pCell->member.cell.nRightBoundary = undo->u.split_para.cell_right_boundary;
408       pCell->member.cell.border = undo->u.split_para.cell_border;
409     }
410     break;
411   }
412   }
413 }
414
415 BOOL ME_Undo(ME_TextEditor *editor)
416 {
417   ME_UndoMode nMode = editor->nUndoMode;
418   struct list *head;
419   struct undo_item *undo, *cursor2;
420
421   if (editor->nUndoMode == umIgnore) return FALSE;
422   assert(nMode == umAddToUndo || nMode == umIgnore);
423
424   head = list_head( &editor->undo_stack );
425   if (!head) return FALSE;
426
427   /* watch out for uncommitted transactions ! */
428   undo = LIST_ENTRY( head, struct undo_item, entry );
429   assert(undo->type == undo_end_transaction
430         || undo->type == undo_potential_end_transaction);
431
432   editor->nUndoMode = umAddToRedo;
433
434   list_remove( &undo->entry );
435   destroy_undo_item( undo );
436
437   LIST_FOR_EACH_ENTRY_SAFE( undo, cursor2, &editor->undo_stack, struct undo_item, entry )
438   {
439       if (undo->type == undo_end_transaction) break;
440       ME_PlayUndoItem( editor, undo );
441       list_remove( &undo->entry );
442       destroy_undo_item( undo );
443   }
444
445   ME_MoveCursorFromTableRowStartParagraph(editor);
446   add_undo( editor, undo_end_transaction );
447   ME_CheckTablesForCorruption(editor);
448   editor->nUndoStackSize--;
449   editor->nUndoMode = nMode;
450   ME_UpdateRepaint(editor, FALSE);
451   return TRUE;
452 }
453
454 BOOL ME_Redo(ME_TextEditor *editor)
455 {
456   ME_UndoMode nMode = editor->nUndoMode;
457   struct list *head;
458   struct undo_item *undo, *cursor2;
459
460   assert(nMode == umAddToUndo || nMode == umIgnore);
461   
462   if (editor->nUndoMode == umIgnore) return FALSE;
463
464   head = list_head( &editor->redo_stack );
465   if (!head) return FALSE;
466
467   /* watch out for uncommitted transactions ! */
468   undo = LIST_ENTRY( head, struct undo_item, entry );
469   assert( undo->type == undo_end_transaction );
470
471   editor->nUndoMode = umAddBackToUndo;
472   list_remove( &undo->entry );
473   destroy_undo_item( undo );
474
475   LIST_FOR_EACH_ENTRY_SAFE( undo, cursor2, &editor->redo_stack, struct undo_item, entry )
476   {
477       if (undo->type == undo_end_transaction) break;
478       ME_PlayUndoItem( editor, undo );
479       list_remove( &undo->entry );
480       destroy_undo_item( undo );
481   }
482   ME_MoveCursorFromTableRowStartParagraph(editor);
483   add_undo( editor, undo_end_transaction );
484   ME_CheckTablesForCorruption(editor);
485   editor->nUndoMode = nMode;
486   ME_UpdateRepaint(editor, FALSE);
487   return TRUE;
488 }