[RICHED20]
[reactos.git] / reactos / dll / win32 / 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, ME_String *eol_str, 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 - eol_str->nLen;
191 undo->u.split_para.eol_str = eol_str;
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->szData, undo->u.split_para.eol_str->nLen, paraFlags);
398 if (bFixRowStart)
399 new_para->member.para.nFlags |= MEPF_ROWSTART;
400 *new_para->member.para.pFmt = undo->u.split_para.fmt;
401 new_para->member.para.border = undo->u.split_para.border;
402 if (paraFlags)
403 {
404 ME_DisplayItem *pCell = new_para->member.para.pCell;
405 pCell->member.cell.nRightBoundary = undo->u.split_para.cell_right_boundary;
406 pCell->member.cell.border = undo->u.split_para.cell_border;
407 }
408 break;
409 }
410 }
411 }
412
413 BOOL ME_Undo(ME_TextEditor *editor)
414 {
415 ME_UndoMode nMode = editor->nUndoMode;
416 struct list *head;
417 struct undo_item *undo, *cursor2;
418
419 if (editor->nUndoMode == umIgnore) return FALSE;
420 assert(nMode == umAddToUndo || nMode == umIgnore);
421
422 head = list_head( &editor->undo_stack );
423 if (!head) return FALSE;
424
425 /* watch out for uncommitted transactions ! */
426 undo = LIST_ENTRY( head, struct undo_item, entry );
427 assert(undo->type == undo_end_transaction
428 || undo->type == undo_potential_end_transaction);
429
430 editor->nUndoMode = umAddToRedo;
431
432 list_remove( &undo->entry );
433 destroy_undo_item( undo );
434
435 LIST_FOR_EACH_ENTRY_SAFE( undo, cursor2, &editor->undo_stack, struct undo_item, entry )
436 {
437 if (undo->type == undo_end_transaction) break;
438 ME_PlayUndoItem( editor, undo );
439 list_remove( &undo->entry );
440 destroy_undo_item( undo );
441 }
442
443 ME_MoveCursorFromTableRowStartParagraph(editor);
444 add_undo( editor, undo_end_transaction );
445 ME_CheckTablesForCorruption(editor);
446 editor->nUndoStackSize--;
447 editor->nUndoMode = nMode;
448 ME_UpdateRepaint(editor, FALSE);
449 return TRUE;
450 }
451
452 BOOL ME_Redo(ME_TextEditor *editor)
453 {
454 ME_UndoMode nMode = editor->nUndoMode;
455 struct list *head;
456 struct undo_item *undo, *cursor2;
457
458 assert(nMode == umAddToUndo || nMode == umIgnore);
459
460 if (editor->nUndoMode == umIgnore) return FALSE;
461
462 head = list_head( &editor->redo_stack );
463 if (!head) return FALSE;
464
465 /* watch out for uncommitted transactions ! */
466 undo = LIST_ENTRY( head, struct undo_item, entry );
467 assert( undo->type == undo_end_transaction );
468
469 editor->nUndoMode = umAddBackToUndo;
470 list_remove( &undo->entry );
471 destroy_undo_item( undo );
472
473 LIST_FOR_EACH_ENTRY_SAFE( undo, cursor2, &editor->redo_stack, struct undo_item, entry )
474 {
475 if (undo->type == undo_end_transaction) break;
476 ME_PlayUndoItem( editor, undo );
477 list_remove( &undo->entry );
478 destroy_undo_item( undo );
479 }
480 ME_MoveCursorFromTableRowStartParagraph(editor);
481 add_undo( editor, undo_end_transaction );
482 ME_CheckTablesForCorruption(editor);
483 editor->nUndoMode = nMode;
484 ME_UpdateRepaint(editor, FALSE);
485 return TRUE;
486 }