1 /***************************************************************************
3 TextEditor.mcc - Textediting MUI Custom Class
4 Copyright (C) 1997-2000 Allan Odgaard
5 Copyright (C) 2005-2014 TextEditor.mcc Open Source Team
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 TextEditor class Support Site: http://www.sf.net/projects/texteditor-mcc
21 ***************************************************************************/
25 #include <clib/alib_protos.h>
27 #include <proto/exec.h>
28 #include <proto/intuition.h>
34 // free the memory occupated by an undo step
35 static void FreeUndoStep(struct InstData
*data
, ULONG index
)
37 struct UserAction
*step
= &data
->undoSteps
[index
];
41 D(DBF_UNDO
, "free undo step %ld", index
);
43 if(step
->type
== ET_DELETEBLOCK
|| step
->type
== ET_DELETEBLOCK_NOMOVE
|| step
->type
== ET_PASTEBLOCK
)
45 if(step
->clip
!= NULL
)
47 FreeVecPooled(data
->mypool
, step
->clip
);
51 // forget about the type of undo
60 BOOL
Undo(struct InstData
*data
)
66 D(DBF_UNDO
, "before maxUndoSteps=%ld nextUndoStep=%ld usedUndoSteps=%ld", data
->maxUndoSteps
, data
->nextUndoStep
, data
->usedUndoSteps
);
68 // check if there is something in the undo buffer available
69 if(isFlagClear(data
->flags
, FLG_ReadOnly
) && data
->nextUndoStep
> 0)
71 struct UserAction
*action
;
72 BOOL moveCursor
= TRUE
;
76 data
->blockinfo
.enabled
= FALSE
;
77 MarkText(data
, data
->blockinfo
.startx
, data
->blockinfo
.startline
, data
->blockinfo
.stopx
, data
->blockinfo
.stopline
);
80 // as the redo operation automatically
81 // becomes available when undo is used we just
82 // check here if we didn't yet set RedoAvailable
83 // as we only want to set it once
84 if(data
->nextUndoStep
== data
->usedUndoSteps
)
85 set(data
->object
, MUIA_TextEditor_RedoAvailable
, TRUE
);
89 action
= &data
->undoSteps
[data
->nextUndoStep
];
91 // if(data->actualline != LineNode(data, buffer->y) || data->CPos_X != buffer->x)
92 SetCursor(data
, data
->CPos_X
, data
->actualline
, FALSE
);
94 data
->CPos_X
= action
->x
;
95 data
->actualline
= LineNode(data
, action
->y
);
96 ScrollIntoDisplay(data
);
98 // perform the saved undo action
103 D(DBF_UNDO
, "undo PASTECHAR");
104 action
->del
.character
= data
->actualline
->line
.Contents
[data
->CPos_X
];
105 action
->del
.style
= GetStyle(data
->CPos_X
, data
->actualline
);
106 action
->del
.flow
= data
->actualline
->line
.Flow
;
107 action
->del
.separator
= data
->actualline
->line
.Separator
;
108 action
->del
.highlight
= data
->actualline
->line
.Highlight
;
109 RemoveChars(data
, data
->CPos_X
, data
->actualline
, 1);
115 D(DBF_UNDO
, "undo DELETECHAR");
116 PasteChars(data
, data
->CPos_X
, data
->actualline
, 1, (char *)&action
->del
.character
, action
);
122 D(DBF_UNDO
, "undo SPLITLINE");
123 MergeLines(data
, data
->actualline
);
129 D(DBF_UNDO
, "undo MERGELINES");
130 SplitLine(data
, data
->CPos_X
, data
->actualline
, FALSE
, action
);
134 case ET_BACKSPACEMERGE
:
136 D(DBF_UNDO
, "undo BACKSPACEMARGE");
137 SplitLine(data
, data
->CPos_X
, data
->actualline
, TRUE
, action
);
143 struct marking block
=
146 LineNode(data
, action
->y
),
148 LineNode(data
, action
->blk
.y
),
151 STRPTR clip
= GetBlock(data
, &block
);
153 D(DBF_UNDO
, "undo PASTEBLOCK");
154 CutBlock2(data
, CUTF_CUT
|CUTF_UPDATE
, &block
);
159 case ET_DELETEBLOCK_NOMOVE
:
161 D(DBF_UNDO
, "undo DELETEBLOCK_NOMOVE");
168 struct Hook
*oldhook
;
170 D(DBF_UNDO
, "undo DELETEBLOCK");
171 oldhook
= data
->ImportHook
;
172 data
->ImportHook
= &ImPlainHook
;
173 InsertText(data
, action
->clip
, moveCursor
);
174 data
->ImportHook
= oldhook
;
175 FreeVecPooled(data
->mypool
, action
->clip
);
179 action
->blk
.x
= data
->CPos_X
;
180 action
->blk
.y
= LineNr(data
, data
->actualline
);
182 if(moveCursor
== FALSE
)
184 data
->CPos_X
= action
->x
;
185 data
->actualline
= LineNode(data
, action
->y
);
195 ScrollIntoDisplay(data
);
197 if(isFlagSet(data
->flags
, FLG_Active
))
198 SetCursor(data
, data
->CPos_X
, data
->actualline
, TRUE
);
200 // if there are no further undo levels we
201 // have to set UndoAvailable to FALSE
202 if(data
->nextUndoStep
== 0)
204 set(data
->object
, MUIA_TextEditor_UndoAvailable
, FALSE
);
206 if(isFlagClear(data
->flags
, FLG_UndoLost
))
207 data
->HasChanged
= FALSE
;
214 DoMethod(data
->object
, MUIM_TextEditor_HandleError
, Error_NothingToUndo
);
217 D(DBF_UNDO
, "after maxUndoSteps=%ld nextUndoStep=%ld usedUndoSteps=%ld", data
->maxUndoSteps
, data
->nextUndoStep
, data
->usedUndoSteps
);
226 BOOL
Redo(struct InstData
*data
)
228 BOOL success
= FALSE
;
232 D(DBF_UNDO
, "before maxUndoSteps=%ld nextUndoStep=%ld usedUndoSteps=%ld", data
->maxUndoSteps
, data
->nextUndoStep
, data
->usedUndoSteps
);
234 // check if there something to redo at all
235 if(isFlagClear(data
->flags
, FLG_ReadOnly
) && data
->nextUndoStep
< data
->usedUndoSteps
)
237 struct UserAction
*action
;
241 data
->blockinfo
.enabled
= FALSE
;
242 MarkText(data
, data
->blockinfo
.startx
, data
->blockinfo
.startline
, data
->blockinfo
.stopx
, data
->blockinfo
.stopline
);
245 // in case nextUndoStep is equal zero then we have to
246 // set the undoavailable attribute to true to signal
247 // others that undo is available
248 if(data
->nextUndoStep
== 0)
249 set(data
->object
, MUIA_TextEditor_UndoAvailable
, TRUE
);
251 action
= &data
->undoSteps
[data
->nextUndoStep
];
252 data
->nextUndoStep
++;
254 // if(data->actualline != LineNode(data, buffer->y) || data->CPos_X != buffer->x)
255 SetCursor(data
, data
->CPos_X
, data
->actualline
, FALSE
);
256 data
->CPos_X
= action
->x
;
257 data
->actualline
= LineNode(data
, action
->y
);
258 ScrollIntoDisplay(data
);
264 D(DBF_UNDO
, "redo PASTECHAR");
265 PasteChars(data
, data
->CPos_X
, data
->actualline
, 1, (char *)&action
->del
.character
, action
);
272 D(DBF_UNDO
, "redo DELETECHAR");
273 RemoveChars(data
, data
->CPos_X
, data
->actualline
, 1);
279 D(DBF_UNDO
, "redo SPLITLINE");
280 SplitLine(data
, data
->CPos_X
, data
->actualline
, TRUE
, NULL
);
285 case ET_BACKSPACEMERGE
:
287 D(DBF_UNDO
, "redo MERGELINES/BACKSPACEMERGE");
288 MergeLines(data
, data
->actualline
);
294 struct Hook
*oldhook
= data
->ImportHook
;
296 D(DBF_UNDO
, "redo PASTEBLOCK");
297 data
->ImportHook
= &ImPlainHook
;
298 InsertText(data
, action
->clip
, TRUE
);
299 data
->ImportHook
= oldhook
;
300 FreeVecPooled(data
->mypool
, action
->clip
);
304 action
->blk
.x
= data
->CPos_X
;
305 action
->blk
.y
= LineNr(data
, data
->actualline
);
309 case ET_DELETEBLOCK_NOMOVE
:
312 struct marking block
=
315 LineNode(data
, action
->y
),
317 LineNode(data
, action
->blk
.y
),
320 STRPTR clip
= GetBlock(data
, &block
);
322 D(DBF_UNDO
, "redo DELETEBLOCK/DELETEBLOCK_NOMOVE");
323 CutBlock2(data
, CUTF_CUT
|CUTF_UPDATE
, &block
);
333 ScrollIntoDisplay(data
);
335 if(isFlagSet(data
->flags
, FLG_Active
))
336 SetCursor(data
, data
->CPos_X
, data
->actualline
, TRUE
);
338 // if nextUndoStep == usedUndoSteps this signals that we
339 // don't have any things to redo anymore.
340 if(data
->nextUndoStep
== data
->usedUndoSteps
)
341 set(data
->object
, MUIA_TextEditor_RedoAvailable
, FALSE
);
347 DoMethod(data
->object
, MUIM_TextEditor_HandleError
, Error_NothingToRedo
);
350 D(DBF_UNDO
, "after maxUndoSteps=%ld nextUndoStep=%ld usedUndoSteps=%ld", data
->maxUndoSteps
, data
->nextUndoStep
, data
->usedUndoSteps
);
357 /// AddToUndoBuffer()
358 BOOL
AddToUndoBuffer(struct InstData
*data
, enum EventType eventtype
, void *eventData
)
362 D(DBF_UNDO
, "before maxUndoSteps=%ld nextUndoStep=%ld usedUndoSteps=%ld", data
->maxUndoSteps
, data
->nextUndoStep
, data
->usedUndoSteps
);
364 if(isFlagClear(data
->flags
, FLG_ReadOnly
) && data
->maxUndoSteps
> 0)
366 struct UserAction
*action
;
369 // check if there is still enough space in our undo buffer
370 // and if not we clean it up one entry
371 if(data
->nextUndoStep
== data
->maxUndoSteps
)
373 // free the oldest stored action and forget about it
374 D(DBF_UNDO
, "undo buffer is full, loose the oldest step");
375 FreeUndoStep(data
, 0);
376 data
->nextUndoStep
--;
377 data
->usedUndoSteps
--;
379 // shift all remaining actions one step to the front
380 memmove(&data
->undoSteps
[0], &data
->undoSteps
[1], sizeof(data
->undoSteps
[0]) * data
->maxUndoSteps
);
382 // signal the user that something in the undo buffer was lost
383 setFlag(data
->flags
, FLG_UndoLost
);
389 // adding something new to the undo buffer will erase all previously
391 for(i
= data
->nextUndoStep
; i
< data
->usedUndoSteps
; i
++)
393 D(DBF_UNDO
, "free not yet redone step %ld", i
);
394 FreeUndoStep(data
, i
);
396 data
->usedUndoSteps
= data
->nextUndoStep
;
399 action
= &data
->undoSteps
[data
->nextUndoStep
];
401 // clear any previous data
402 memset(action
, 0, sizeof(*action
));
404 action
->x
= data
->CPos_X
;
405 action
->y
= LineNr(data
, data
->actualline
);
406 action
->type
= eventtype
;
410 case ET_BACKSPACEMERGE
:
413 struct line_node
*next
= GetNextLine(data
->actualline
);
415 D(DBF_UNDO
, "add undo MERGELINES/BACKSPACEMERGE");
416 action
->del
.highlight
= next
->line
.Highlight
;
417 action
->del
.flow
= next
->line
.Flow
;
418 action
->del
.separator
= next
->line
.Separator
;
424 STRPTR str
= (STRPTR
)eventData
;
426 D(DBF_UNDO
, "add undo DELETECHAR");
427 action
->del
.character
= str
[0];
428 action
->del
.style
= GetStyle(data
->CPos_X
, data
->actualline
);
429 action
->del
.flow
= data
->actualline
->line
.Flow
;
430 action
->del
.separator
= data
->actualline
->line
.Separator
;
431 action
->del
.highlight
= data
->actualline
->line
.Highlight
;
437 struct marking
*block
= (struct marking
*)eventData
;
439 D(DBF_UNDO
, "add undo PASTEBLOCK");
440 action
->x
= block
->startx
;
441 action
->y
= LineNr(data
, block
->startline
);
442 action
->blk
.x
= block
->stopx
;
443 action
->blk
.y
= LineNr(data
, block
->stopline
);
448 case ET_DELETEBLOCK_NOMOVE
:
451 struct marking
*block
= (struct marking
*)eventData
;
453 D(DBF_UNDO
, "add undo DELETEBLOCK");
454 if((text
= GetBlock(data
, block
)) != NULL
)
456 action
->x
= block
->startx
;
457 action
->y
= LineNr(data
, block
->startline
);
460 if(eventtype
== ET_DELETEBLOCK
&& isFlagSet(data
->flags
, FLG_FreezeCrsr
))
461 action
->type
= ET_DELETEBLOCK_NOMOVE
;
479 // adding the undo step was successful, update the variables
480 // advance the index for the next undo step
481 data
->nextUndoStep
++;
482 // and count this new step
483 data
->usedUndoSteps
++;
487 // something went wrong, invoke the error method
488 DoMethod(data
->object
, MUIM_TextEditor_HandleError
, Error_NotEnoughUndoMem
);
491 // trigger possible notifications, no matter if the action succeeded or not,
492 // because the undo/redo situation might have changed
493 SetAttrs(data
->object
, MUIA_TextEditor_RedoAvailable
, data
->nextUndoStep
< data
->usedUndoSteps
,
494 MUIA_TextEditor_UndoAvailable
, data
->usedUndoSteps
!= 0,
498 D(DBF_UNDO
, "after maxUndoSteps=%ld nextUndoStep=%ld usedUndoSteps=%ld", data
->maxUndoSteps
, data
->nextUndoStep
, data
->usedUndoSteps
);
505 /// ResetUndoBuffer()
506 void ResetUndoBuffer(struct InstData
*data
)
510 if(data
->maxUndoSteps
!= 0 && data
->undoSteps
!= NULL
)
514 for(i
= 0; i
< data
->usedUndoSteps
; i
++)
515 FreeUndoStep(data
, i
);
517 data
->usedUndoSteps
= 0;
518 data
->nextUndoStep
= 0;
520 // trigger possible notifications
521 SetAttrs(data
->object
, MUIA_TextEditor_RedoAvailable
, FALSE
,
522 MUIA_TextEditor_UndoAvailable
, FALSE
,
530 /// ResizeUndoBuffer()
531 void ResizeUndoBuffer(struct InstData
*data
, ULONG newMaxUndoSteps
)
535 if(data
->maxUndoSteps
!= newMaxUndoSteps
)
537 struct UserAction
*newUndoSteps
= NULL
;
539 D(DBF_UNDO
, "resizing undo buffer from %ld to %ld steps", data
->maxUndoSteps
, newMaxUndoSteps
);
541 if(newMaxUndoSteps
!= 0)
546 // calculate number of bytes from number of undo levels
547 oldSize
= (data
->maxUndoSteps
* sizeof(struct UserAction
));
548 newSize
= (newMaxUndoSteps
* sizeof(struct UserAction
));
550 // allocate a new undo buffer
551 if((newUndoSteps
= AllocVecPooled(data
->mypool
, newSize
)) != NULL
)
553 if(data
->undoSteps
!= NULL
)
555 // copy over the old undo steps
556 memcpy(newUndoSteps
, data
->undoSteps
, MIN(oldSize
, newSize
));
561 if(data
->undoSteps
!= NULL
)
565 // free the steps which don't fit into the new buffer anymore
566 for(i
= newMaxUndoSteps
; i
< data
->maxUndoSteps
; i
++)
567 FreeUndoStep(data
, i
);
569 // free the old buffer
570 FreeVecPooled(data
->mypool
, data
->undoSteps
);
573 // reset everything to the new values
574 data
->undoSteps
= newUndoSteps
;
575 data
->maxUndoSteps
= newMaxUndoSteps
;
576 data
->usedUndoSteps
= MIN(data
->usedUndoSteps
, newMaxUndoSteps
);
577 data
->nextUndoStep
= MIN(data
->nextUndoStep
, newMaxUndoSteps
);
579 // trigger possible notifications
580 SetAttrs(data
->object
, MUIA_TextEditor_RedoAvailable
, data
->nextUndoStep
< data
->usedUndoSteps
,
581 MUIA_TextEditor_UndoAvailable
, data
->usedUndoSteps
!= 0,
590 void FreeUndoBuffer(struct InstData
*data
)
594 if(data
->undoSteps
!= NULL
)
598 for(i
= 0; i
< data
->usedUndoSteps
; i
++)
599 FreeUndoStep(data
, i
);
601 FreeVecPooled(data
->mypool
, data
->undoSteps
);
602 data
->undoSteps
= NULL
;