undo.c 13.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * RichEdit - functions dealing with editor object
 *
 * Copyright 2004 by Krzysztof Foltman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 22 23 24 25 26 27 28
 */

#include "editor.h"

WINE_DEFAULT_DEBUG_CHANNEL(richedit);

void ME_EmptyUndoStack(ME_TextEditor *editor)
{
  ME_DisplayItem *p, *pNext;
  
29 30 31
  if (editor->nUndoMode == umIgnore)
    return;
  
32 33 34
  TRACE("Emptying undo stack\n");

  p = editor->pUndoStack;
35 36
  editor->pUndoStack = editor->pUndoStackBottom = NULL;
  editor->nUndoStackSize = 0;
37 38 39 40 41 42 43 44 45 46 47 48 49 50
  while(p) {
    pNext = p->next;
    ME_DestroyDisplayItem(p);    
    p = pNext;
  } 
  p = editor->pRedoStack;
  editor->pRedoStack = NULL;
  while(p) {
    pNext = p->next;
    ME_DestroyDisplayItem(p);    
    p = pNext;
  } 
}

51
ME_UndoItem *ME_AddUndoItem(ME_TextEditor *editor, ME_DIType type, const ME_DisplayItem *pdi) {
52 53
  if (editor->nUndoMode == umIgnore)
    return NULL;
54 55
  else if (editor->nUndoLimit == 0)
    return NULL;
56 57
  else
  {
58
    ME_DisplayItem *pItem = ALLOC_OBJ(ME_UndoItem);
59 60
    switch(type)
    {
61 62 63 64
    case diUndoPotentialEndTransaction:
        /* only should be added for manually typed chars, not undos or redos */
        assert(editor->nUndoMode == umAddToUndo);
        /* intentional fall-through to next case */
65 66 67 68
    case diUndoEndTransaction:
      break;
    case diUndoSetParagraphFormat:
      assert(pdi);
69
      pItem->member.para = pdi->member.para;
70
      pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
71
      *pItem->member.para.pFmt = *pdi->member.para.pFmt;
72 73 74
      break;
    case diUndoInsertRun:
      assert(pdi);
75
      pItem->member.run = pdi->member.run;
76 77
      pItem->member.run.strText = ME_StrDup(pItem->member.run.strText);
      ME_AddRefStyle(pItem->member.run.style);
78 79 80 81 82 83
      if (pdi->member.run.ole_obj)
      {
        pItem->member.run.ole_obj = ALLOC_OBJ(*pItem->member.run.ole_obj);
        ME_CopyReObject(pItem->member.run.ole_obj, pdi->member.run.ole_obj);
      }
      else pItem->member.run.ole_obj = NULL;
84 85 86 87 88 89 90
      break;
    case diUndoSetCharFormat:
      break;
    case diUndoDeleteRun:
    case diUndoJoinParagraphs:
      break;
    case diUndoSplitParagraph:
91 92
    {
      ME_DisplayItem *prev_para = pdi->member.para.prev_para;
93
      assert(pdi->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
94
      pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
95 96
      pItem->member.para.pFmt->cbSize = sizeof(PARAFORMAT2);
      pItem->member.para.pFmt->dwMask = 0;
97
      *pItem->member.para.pFmt = *pdi->member.para.pFmt;
98
      pItem->member.para.border = pdi->member.para.border;
99 100
      pItem->member.para.nFlags = prev_para->member.para.nFlags & ~MEPF_CELL;
      pItem->member.para.pCell = NULL;
101
      break;
102
    }
103 104 105 106 107 108 109 110
    default:
      assert(0 == "AddUndoItem, unsupported item type");
      return NULL;
    }
    pItem->type = type;
    pItem->prev = NULL;
    if (editor->nUndoMode == umAddToUndo || editor->nUndoMode == umAddBackToUndo)
    {
111 112 113 114 115
      if (editor->pUndoStack
          && editor->pUndoStack->type == diUndoPotentialEndTransaction)
      {
          editor->pUndoStack->type = diUndoEndTransaction;
      }
116 117 118 119
      if (editor->nUndoMode == umAddToUndo)
        TRACE("Pushing id=%s to undo stack, deleting redo stack\n", ME_GetDITypeName(type));
      else
        TRACE("Pushing id=%s to undo stack\n", ME_GetDITypeName(type));
120

121
      pItem->next = editor->pUndoStack;
122
      if (type == diUndoEndTransaction || type == diUndoPotentialEndTransaction)
123
        editor->nUndoStackSize++;
124 125
      if (editor->pUndoStack)
        editor->pUndoStack->prev = pItem;
126 127
      else
        editor->pUndoStackBottom = pItem;
128
      editor->pUndoStack = pItem;
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
      
      if (editor->nUndoStackSize > editor->nUndoLimit)
      { /* remove oldest undo from stack */
        ME_DisplayItem *p = editor->pUndoStackBottom;
        while (p->type !=diUndoEndTransaction)
          p = p->prev; /*find new stack bottom */
        editor->pUndoStackBottom = p->prev;
          editor->pUndoStackBottom->next = NULL;
        do
        {
          ME_DisplayItem *pp = p->next;
          ME_DestroyDisplayItem(p);
          p = pp;
        } while (p);
        editor->nUndoStackSize--;
      }
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
      /* any new operation (not redo) clears the redo stack */
      if (editor->nUndoMode == umAddToUndo) {
        ME_DisplayItem *p = editor->pRedoStack;
        while(p)
        {
          ME_DisplayItem *pp = p->next;
          ME_DestroyDisplayItem(p);
          p = pp;
        }
        editor->pRedoStack = NULL;
      }
    }
    else if (editor->nUndoMode == umAddToRedo)
    {
      TRACE("Pushing id=%s to redo stack\n", ME_GetDITypeName(type));
      pItem->next = editor->pRedoStack;
      if (editor->pRedoStack)
        editor->pRedoStack->prev = pItem;
      editor->pRedoStack = pItem;
    }
    else
      assert(0);
    return (ME_UndoItem *)pItem;
  }
}

171 172 173 174 175 176 177 178 179 180 181 182
/**
 * Commits preceding changes into a transaction that can be undone together.
 *
 * This should be called after all the changes occur associated with an event
 * so that the group of changes can be undone atomically as a transaction.
 *
 * This will have no effect the undo mode is set to ignore changes, or if no
 * changes preceded calling this function before the last time it was called.
 *
 * This can also be used to conclude a coalescing transaction (used for grouping
 * typed characters).
 */
183
void ME_CommitUndo(ME_TextEditor *editor) {
184 185 186
  if (editor->nUndoMode == umIgnore)
    return;
  
187 188 189 190 191 192 193 194 195 196
  assert(editor->nUndoMode == umAddToUndo);
  
  /* no transactions, no need to commit */
  if (!editor->pUndoStack)
    return;

  /* no need to commit empty transactions */
  if (editor->pUndoStack->type == diUndoEndTransaction)
    return;
    
197 198 199 200 201 202 203 204
  if (editor->pUndoStack->type == diUndoPotentialEndTransaction)
  {
      /* Previous transaction was as a result of characters typed,
       * so the end of this transaction is confirmed. */
      editor->pUndoStack->type = diUndoEndTransaction;
      return;
  }

205 206 207
  ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
}

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
/**
 * Groups supsequent changes with previous ones for an undo if coalescing.
 *
 * Has no effect if the previous changes were followed by a ME_CommitUndo. This
 * function will only have an affect if the previous changes were followed by
 * a call to ME_CommitCoalescingUndo, which allows the transaction to be
 * continued.
 *
 * This allows multiple consecutively typed characters to be grouped together
 * to be undone by a single undo operation.
 */
void ME_ContinueCoalescingTransaction(ME_TextEditor *editor)
{
  ME_DisplayItem* p;

  if (editor->nUndoMode == umIgnore)
    return;

  assert(editor->nUndoMode == umAddToUndo);

  p = editor->pUndoStack;

  if (p && p->type == diUndoPotentialEndTransaction) {
    assert(p->next); /* EndTransactions shouldn't be at bottom of undo stack */
    editor->pUndoStack = p->next;
    editor->pUndoStack->prev = NULL;
    editor->nUndoStackSize--;
    ME_DestroyDisplayItem(p);
  }
}

/**
 * Commits preceding changes into a undo transaction that can be expanded.
 *
 * This function allows the transaction to be reopened with
 * ME_ContinueCoalescingTransaction in order to continue the transaction.  If an
 * undo item is added to the undo stack as a result of a change without the
 * transaction being reopened, then the transaction will be ended, and the
 * changes will become a part of the next transaction.
 *
 * This is used to allow typed characters to be grouped together since each
 * typed character results in a single event, and each event adding undo items
 * must be committed.  Using this function as opposed to ME_CommitUndo allows
 * multiple events to be grouped, and undone together.
 */
void ME_CommitCoalescingUndo(ME_TextEditor *editor)
{
  if (editor->nUndoMode == umIgnore)
    return;

  assert(editor->nUndoMode == umAddToUndo);

  /* no transactions, no need to commit */
  if (!editor->pUndoStack)
    return;

  /* no need to commit empty transactions */
  if (editor->pUndoStack->type == diUndoEndTransaction)
    return;
  if (editor->pUndoStack->type == diUndoPotentialEndTransaction)
    return;

  ME_AddUndoItem(editor, diUndoPotentialEndTransaction, NULL);
}

273
static void ME_PlayUndoItem(ME_TextEditor *editor, ME_DisplayItem *pItem)
274 275 276
{
  ME_UndoItem *pUItem = (ME_UndoItem *)pItem;

277 278
  if (editor->nUndoMode == umIgnore)
    return;
279 280 281 282
  TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem->type));

  switch(pItem->type)
  {
283
  case diUndoPotentialEndTransaction:
284 285 286 287 288
  case diUndoEndTransaction:
    assert(0);
  case diUndoSetParagraphFormat:
  {
    ME_Cursor tmp;
289
    ME_DisplayItem *para;
290
    ME_CursorFromCharOfs(editor, pItem->member.para.nCharOfs, &tmp);
291 292 293
    para = ME_FindItemBack(tmp.pRun, diParagraph);
    ME_AddUndoItem(editor, diUndoSetParagraphFormat, para);
    *para->member.para.pFmt = *pItem->member.para.pFmt;
294
    para->member.para.border = pItem->member.para.border;
295 296 297 298
    break;
  }
  case diUndoSetCharFormat:
  {
299 300
    ME_Cursor start, end;
    ME_CursorFromCharOfs(editor, pUItem->nStart, &start);
301 302
    end = start;
    ME_MoveCursorChars(editor, &end, pUItem->nLen);
303
    ME_SetCharFormat(editor, &start, &end, &pItem->member.ustyle->fmt);
304 305 306 307
    break;
  }
  case diUndoInsertRun:
  {
308 309 310 311 312 313
    ME_Cursor tmp;
    ME_CursorFromCharOfs(editor, pItem->member.run.nCharOfs, &tmp);
    ME_InsertRunAtCursor(editor, &tmp, pItem->member.run.style,
                         pItem->member.run.strText->szData,
                         pItem->member.run.strText->nLen,
                         pItem->member.run.nFlags);
314 315 316 317
    break;
  }
  case diUndoDeleteRun:
  {
318 319 320
    ME_Cursor tmp;
    ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
    ME_InternalDeleteText(editor, &tmp, pUItem->nLen, TRUE);
321 322 323 324 325 326 327
    break;
  }
  case diUndoJoinParagraphs:
  {
    ME_Cursor tmp;
    ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
    /* the only thing that's needed is paragraph offset, so no need to split runs */
328
    ME_JoinParagraphs(editor, tmp.pPara, TRUE);
329 330 331 332 333
    break;
  }
  case diUndoSplitParagraph:
  {
    ME_Cursor tmp;
334 335 336
    ME_DisplayItem *this_para, *new_para;
    BOOL bFixRowStart;
    int paraFlags = pItem->member.para.nFlags & (MEPF_ROWSTART|MEPF_CELL|MEPF_ROWEND);
337 338
    ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
    if (tmp.nOffset)
339
      ME_SplitRunSimple(editor, &tmp);
340
    assert(pUItem->eol_str);
341
    this_para = tmp.pPara;
342 343 344 345 346 347 348
    bFixRowStart = this_para->member.para.nFlags & MEPF_ROWSTART;
    if (bFixRowStart)
    {
      /* Re-insert the paragraph before the table, making sure the nFlag value
       * is correct. */
      this_para->member.para.nFlags &= ~MEPF_ROWSTART;
    }
349
    new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style,
350
                                 pUItem->eol_str, paraFlags);
351 352
    if (bFixRowStart)
      new_para->member.para.nFlags |= MEPF_ROWSTART;
353
    assert(pItem->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
354
    *new_para->member.para.pFmt = *pItem->member.para.pFmt;
355
    new_para->member.para.border = pItem->member.para.border;
356 357 358 359 360 361
    if (pItem->member.para.pCell)
    {
      ME_DisplayItem *pItemCell, *pCell;
      pItemCell = pItem->member.para.pCell;
      pCell = new_para->member.para.pCell;
      pCell->member.cell.nRightBoundary = pItemCell->member.cell.nRightBoundary;
362
      pCell->member.cell.border = pItemCell->member.cell.border;
363
    }
364 365 366 367 368 369 370
    break;
  }
  default:
    assert(0 == "PlayUndoItem, unexpected type");
  }
}

371
BOOL ME_Undo(ME_TextEditor *editor) {
372 373 374
  ME_DisplayItem *p;
  ME_UndoMode nMode = editor->nUndoMode;
  
375
  if (editor->nUndoMode == umIgnore)
376
    return FALSE;
377 378 379 380
  assert(nMode == umAddToUndo || nMode == umIgnore);
  
  /* no undo items ? */
  if (!editor->pUndoStack)
381
    return FALSE;
382
    
Austin English's avatar
Austin English committed
383
  /* watch out for uncommitted transactions ! */
384 385
  assert(editor->pUndoStack->type == diUndoEndTransaction
        || editor->pUndoStack->type == diUndoPotentialEndTransaction);
386 387 388 389
  
  editor->nUndoMode = umAddToRedo;
  p = editor->pUndoStack->next;
  ME_DestroyDisplayItem(editor->pUndoStack);
390
  editor->pUndoStack = p;
391
  do {
392
    p->prev = NULL;
393
    ME_PlayUndoItem(editor, p);
394 395 396
    editor->pUndoStack = p->next;
    ME_DestroyDisplayItem(p);
    p = editor->pUndoStack;
397 398 399
  } while(p && p->type != diUndoEndTransaction);
  if (p)
    p->prev = NULL;
400
  ME_MoveCursorFromTableRowStartParagraph(editor);
401
  ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
402
  ME_CheckTablesForCorruption(editor);
403
  editor->nUndoStackSize--;
404
  editor->nUndoMode = nMode;
405
  ME_UpdateRepaint(editor, FALSE);
406
  return TRUE;
407 408
}

409
BOOL ME_Redo(ME_TextEditor *editor) {
410 411 412 413 414
  ME_DisplayItem *p;
  ME_UndoMode nMode = editor->nUndoMode;
  
  assert(nMode == umAddToUndo || nMode == umIgnore);
  
415
  if (editor->nUndoMode == umIgnore)
416
    return FALSE;
417 418
  /* no redo items ? */
  if (!editor->pRedoStack)
419
    return FALSE;
420
    
Austin English's avatar
Austin English committed
421
  /* watch out for uncommitted transactions ! */
422 423 424 425 426
  assert(editor->pRedoStack->type == diUndoEndTransaction);
  
  editor->nUndoMode = umAddBackToUndo;
  p = editor->pRedoStack->next;
  ME_DestroyDisplayItem(editor->pRedoStack);
427
  editor->pRedoStack = p;
428
  do {
429
    p->prev = NULL;
430
    ME_PlayUndoItem(editor, p);
431 432 433
    editor->pRedoStack = p->next;
    ME_DestroyDisplayItem(p);
    p = editor->pRedoStack;
434 435 436
  } while(p && p->type != diUndoEndTransaction);
  if (p)
    p->prev = NULL;
437
  ME_MoveCursorFromTableRowStartParagraph(editor);
438
  ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
439
  ME_CheckTablesForCorruption(editor);
440
  editor->nUndoMode = nMode;
441
  ME_UpdateRepaint(editor, FALSE);
442
  return TRUE;
443
}