2 * RichEdit - functions dealing with editor object
4 * Copyright 2004 by Krzysztof Foltman
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.
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.
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
23 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
25 void ME_EmptyUndoStack(ME_TextEditor
*editor
)
27 ME_DisplayItem
*p
, *pNext
;
29 if (editor
->nUndoMode
== umIgnore
)
32 TRACE("Emptying undo stack\n");
34 p
= editor
->pUndoStack
;
35 editor
->pUndoStack
= editor
->pUndoStackBottom
= NULL
;
36 editor
->nUndoStackSize
= 0;
39 ME_DestroyDisplayItem(p
);
42 p
= editor
->pRedoStack
;
43 editor
->pRedoStack
= NULL
;
46 ME_DestroyDisplayItem(p
);
51 ME_UndoItem
*ME_AddUndoItem(ME_TextEditor
*editor
, ME_DIType type
, const ME_DisplayItem
*pdi
) {
52 if (editor
->nUndoMode
== umIgnore
)
54 else if (editor
->nUndoLimit
== 0)
58 ME_DisplayItem
*pItem
= (ME_DisplayItem
*)ALLOC_OBJ(ME_UndoItem
);
59 ((ME_UndoItem
*)pItem
)->nCR
= ((ME_UndoItem
*)pItem
)->nLF
= -1;
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
:
68 case diUndoSetParagraphFormat
:
70 pItem
->member
.para
= pdi
->member
.para
;
71 pItem
->member
.para
.pFmt
= ALLOC_OBJ(PARAFORMAT2
);
72 *pItem
->member
.para
.pFmt
= *pdi
->member
.para
.pFmt
;
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
)
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
);
84 else pItem
->member
.run
.ole_obj
= NULL
;
86 case diUndoSetCharFormat
:
89 case diUndoJoinParagraphs
:
91 case diUndoSplitParagraph
:
93 ME_DisplayItem
*prev_para
= pdi
->member
.para
.prev_para
;
94 assert(pdi
->member
.para
.pFmt
->cbSize
== sizeof(PARAFORMAT2
));
95 pItem
->member
.para
.pFmt
= ALLOC_OBJ(PARAFORMAT2
);
96 pItem
->member
.para
.pFmt
->cbSize
= sizeof(PARAFORMAT2
);
97 pItem
->member
.para
.pFmt
->dwMask
= 0;
98 *pItem
->member
.para
.pFmt
= *pdi
->member
.para
.pFmt
;
99 pItem
->member
.para
.border
= pdi
->member
.para
.border
;
100 pItem
->member
.para
.nFlags
= prev_para
->member
.para
.nFlags
& ~MEPF_CELL
;
101 pItem
->member
.para
.pCell
= NULL
;
105 assert(0 == "AddUndoItem, unsupported item type");
110 if (editor
->nUndoMode
== umAddToUndo
|| editor
->nUndoMode
== umAddBackToUndo
)
112 if (editor
->pUndoStack
113 && editor
->pUndoStack
->type
== diUndoPotentialEndTransaction
)
115 editor
->pUndoStack
->type
= diUndoEndTransaction
;
117 if (editor
->nUndoMode
== umAddToUndo
)
118 TRACE("Pushing id=%s to undo stack, deleting redo stack\n", ME_GetDITypeName(type
));
120 TRACE("Pushing id=%s to undo stack\n", ME_GetDITypeName(type
));
122 pItem
->next
= editor
->pUndoStack
;
123 if (type
== diUndoEndTransaction
|| type
== diUndoPotentialEndTransaction
)
124 editor
->nUndoStackSize
++;
125 if (editor
->pUndoStack
)
126 editor
->pUndoStack
->prev
= pItem
;
128 editor
->pUndoStackBottom
= pItem
;
129 editor
->pUndoStack
= pItem
;
131 if (editor
->nUndoStackSize
> editor
->nUndoLimit
)
132 { /* remove oldest undo from stack */
133 ME_DisplayItem
*p
= editor
->pUndoStackBottom
;
134 while (p
->type
!=diUndoEndTransaction
)
135 p
= p
->prev
; /*find new stack bottom */
136 editor
->pUndoStackBottom
= p
->prev
;
137 editor
->pUndoStackBottom
->next
= NULL
;
140 ME_DisplayItem
*pp
= p
->next
;
141 ME_DestroyDisplayItem(p
);
144 editor
->nUndoStackSize
--;
146 /* any new operation (not redo) clears the redo stack */
147 if (editor
->nUndoMode
== umAddToUndo
) {
148 ME_DisplayItem
*p
= editor
->pRedoStack
;
151 ME_DisplayItem
*pp
= p
->next
;
152 ME_DestroyDisplayItem(p
);
155 editor
->pRedoStack
= NULL
;
158 else if (editor
->nUndoMode
== umAddToRedo
)
160 TRACE("Pushing id=%s to redo stack\n", ME_GetDITypeName(type
));
161 pItem
->next
= editor
->pRedoStack
;
162 if (editor
->pRedoStack
)
163 editor
->pRedoStack
->prev
= pItem
;
164 editor
->pRedoStack
= pItem
;
168 return (ME_UndoItem
*)pItem
;
173 * Commits preceding changes into a transaction that can be undone together.
175 * This should be called after all the changes occur associated with an event
176 * so that the group of changes can be undone atomically as a transaction.
178 * This will have no effect the undo mode is set to ignore changes, or if no
179 * changes preceded calling this function before the last time it was called.
181 * This can also be used to conclude a coalescing transaction (used for grouping
184 void ME_CommitUndo(ME_TextEditor
*editor
) {
185 if (editor
->nUndoMode
== umIgnore
)
188 assert(editor
->nUndoMode
== umAddToUndo
);
190 /* no transactions, no need to commit */
191 if (!editor
->pUndoStack
)
194 /* no need to commit empty transactions */
195 if (editor
->pUndoStack
->type
== diUndoEndTransaction
)
198 if (editor
->pUndoStack
->type
== diUndoPotentialEndTransaction
)
200 /* Previous transaction was as a result of characters typed,
201 * so the end of this transaction is confirmed. */
202 editor
->pUndoStack
->type
= diUndoEndTransaction
;
206 ME_AddUndoItem(editor
, diUndoEndTransaction
, NULL
);
207 ME_SendSelChange(editor
);
211 * Groups supsequent changes with previous ones for an undo if coalescing.
213 * Has no effect if the previous changes were followed by a ME_CommitUndo. This
214 * function will only have an affect if the previous changes were followed by
215 * a call to ME_CommitCoalescingUndo, which allows the transaction to be
218 * This allows multiple consecutively typed characters to be grouped together
219 * to be undone by a single undo operation.
221 void ME_ContinueCoalescingTransaction(ME_TextEditor
*editor
)
225 if (editor
->nUndoMode
== umIgnore
)
228 assert(editor
->nUndoMode
== umAddToUndo
);
230 p
= editor
->pUndoStack
;
232 if (p
&& p
->type
== diUndoPotentialEndTransaction
) {
233 assert(p
->next
); /* EndTransactions shouldn't be at bottom of undo stack */
234 editor
->pUndoStack
= p
->next
;
235 editor
->pUndoStack
->prev
= NULL
;
236 editor
->nUndoStackSize
--;
237 ME_DestroyDisplayItem(p
);
242 * Commits preceding changes into a undo transaction that can be expanded.
244 * This function allows the transaction to be reopened with
245 * ME_ContinueCoalescingTransaction in order to continue the transaction. If an
246 * undo item is added to the undo stack as a result of a change without the
247 * transaction being reopened, then the transaction will be ended, and the
248 * changes will become a part of the next transaction.
250 * This is used to allow typed characters to be grouped together since each
251 * typed character results in a single event, and each event adding undo items
252 * must be committed. Using this function as opposed to ME_CommitUndo allows
253 * multiple events to be grouped, and undone together.
255 void ME_CommitCoalescingUndo(ME_TextEditor
*editor
)
257 if (editor
->nUndoMode
== umIgnore
)
260 assert(editor
->nUndoMode
== umAddToUndo
);
262 /* no transactions, no need to commit */
263 if (!editor
->pUndoStack
)
266 /* no need to commit empty transactions */
267 if (editor
->pUndoStack
->type
== diUndoEndTransaction
)
269 if (editor
->pUndoStack
->type
== diUndoPotentialEndTransaction
)
272 ME_AddUndoItem(editor
, diUndoPotentialEndTransaction
, NULL
);
273 ME_SendSelChange(editor
);
276 static void ME_PlayUndoItem(ME_TextEditor
*editor
, ME_DisplayItem
*pItem
)
278 ME_UndoItem
*pUItem
= (ME_UndoItem
*)pItem
;
280 if (editor
->nUndoMode
== umIgnore
)
282 TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem
->type
));
286 case diUndoPotentialEndTransaction
:
287 case diUndoEndTransaction
:
289 case diUndoSetParagraphFormat
:
292 ME_DisplayItem
*para
;
293 ME_CursorFromCharOfs(editor
, pItem
->member
.para
.nCharOfs
, &tmp
);
294 para
= ME_FindItemBack(tmp
.pRun
, diParagraph
);
295 ME_AddUndoItem(editor
, diUndoSetParagraphFormat
, para
);
296 *para
->member
.para
.pFmt
= *pItem
->member
.para
.pFmt
;
297 para
->member
.para
.border
= pItem
->member
.para
.border
;
300 case diUndoSetCharFormat
:
302 ME_SetCharFormat(editor
, pUItem
->nStart
, pUItem
->nLen
, &pItem
->member
.ustyle
->fmt
);
305 case diUndoInsertRun
:
307 ME_InsertRun(editor
, pItem
->member
.run
.nCharOfs
, pItem
);
310 case diUndoDeleteRun
:
312 ME_InternalDeleteText(editor
, pUItem
->nStart
, pUItem
->nLen
, TRUE
);
315 case diUndoJoinParagraphs
:
318 ME_CursorFromCharOfs(editor
, pUItem
->nStart
, &tmp
);
319 /* the only thing that's needed is paragraph offset, so no need to split runs */
320 ME_JoinParagraphs(editor
, ME_GetParagraph(tmp
.pRun
), TRUE
);
323 case diUndoSplitParagraph
:
326 ME_DisplayItem
*this_para
, *new_para
;
328 int paraFlags
= pItem
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_CELL
|MEPF_ROWEND
);
329 ME_CursorFromCharOfs(editor
, pUItem
->nStart
, &tmp
);
331 tmp
.pRun
= ME_SplitRunSimple(editor
, tmp
.pRun
, tmp
.nOffset
);
332 assert(pUItem
->nCR
>= 0);
333 assert(pUItem
->nLF
>= 0);
334 this_para
= ME_GetParagraph(tmp
.pRun
);
335 bFixRowStart
= this_para
->member
.para
.nFlags
& MEPF_ROWSTART
;
338 /* Re-insert the paragraph before the table, making sure the nFlag value
340 this_para
->member
.para
.nFlags
&= ~MEPF_ROWSTART
;
342 new_para
= ME_SplitParagraph(editor
, tmp
.pRun
, tmp
.pRun
->member
.run
.style
,
343 pUItem
->nCR
, pUItem
->nLF
, paraFlags
);
345 new_para
->member
.para
.nFlags
|= MEPF_ROWSTART
;
346 assert(pItem
->member
.para
.pFmt
->cbSize
== sizeof(PARAFORMAT2
));
347 *new_para
->member
.para
.pFmt
= *pItem
->member
.para
.pFmt
;
348 new_para
->member
.para
.border
= pItem
->member
.para
.border
;
349 if (pItem
->member
.para
.pCell
)
351 ME_DisplayItem
*pItemCell
, *pCell
;
352 pItemCell
= pItem
->member
.para
.pCell
;
353 pCell
= new_para
->member
.para
.pCell
;
354 pCell
->member
.cell
.nRightBoundary
= pItemCell
->member
.cell
.nRightBoundary
;
355 pCell
->member
.cell
.border
= pItemCell
->member
.cell
.border
;
360 assert(0 == "PlayUndoItem, unexpected type");
364 BOOL
ME_Undo(ME_TextEditor
*editor
) {
366 ME_UndoMode nMode
= editor
->nUndoMode
;
368 if (editor
->nUndoMode
== umIgnore
)
370 assert(nMode
== umAddToUndo
|| nMode
== umIgnore
);
372 /* no undo items ? */
373 if (!editor
->pUndoStack
)
376 /* watch out for uncommitted transactions ! */
377 assert(editor
->pUndoStack
->type
== diUndoEndTransaction
378 || editor
->pUndoStack
->type
== diUndoPotentialEndTransaction
);
380 editor
->nUndoMode
= umAddToRedo
;
381 p
= editor
->pUndoStack
->next
;
382 ME_DestroyDisplayItem(editor
->pUndoStack
);
383 editor
->pUndoStack
= p
;
386 ME_PlayUndoItem(editor
, p
);
387 editor
->pUndoStack
= p
->next
;
388 ME_DestroyDisplayItem(p
);
389 p
= editor
->pUndoStack
;
390 } while(p
&& p
->type
!= diUndoEndTransaction
);
393 ME_MoveCursorFromTableRowStartParagraph(editor
);
394 ME_AddUndoItem(editor
, diUndoEndTransaction
, NULL
);
395 ME_CheckTablesForCorruption(editor
);
396 editor
->nUndoStackSize
--;
397 editor
->nUndoMode
= nMode
;
398 ME_UpdateRepaint(editor
);
402 BOOL
ME_Redo(ME_TextEditor
*editor
) {
404 ME_UndoMode nMode
= editor
->nUndoMode
;
406 assert(nMode
== umAddToUndo
|| nMode
== umIgnore
);
408 if (editor
->nUndoMode
== umIgnore
)
410 /* no redo items ? */
411 if (!editor
->pRedoStack
)
414 /* watch out for uncommitted transactions ! */
415 assert(editor
->pRedoStack
->type
== diUndoEndTransaction
);
417 editor
->nUndoMode
= umAddBackToUndo
;
418 p
= editor
->pRedoStack
->next
;
419 ME_DestroyDisplayItem(editor
->pRedoStack
);
420 editor
->pRedoStack
= p
;
423 ME_PlayUndoItem(editor
, p
);
424 editor
->pRedoStack
= p
->next
;
425 ME_DestroyDisplayItem(p
);
426 p
= editor
->pRedoStack
;
427 } while(p
&& p
->type
!= diUndoEndTransaction
);
430 ME_MoveCursorFromTableRowStartParagraph(editor
);
431 ME_AddUndoItem(editor
, diUndoEndTransaction
, NULL
);
432 ME_CheckTablesForCorruption(editor
);
433 editor
->nUndoMode
= nMode
;
434 ME_UpdateRepaint(editor
);