#include "tkTextUndo.h"
#include "tkInt.h"
#include "tkAlloc.h"
#include <assert.h>
#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
# define _TK_NEED_IMPLEMENTATION
# include "tkTextUndoPriv.h"
#endif
#ifndef MAX
# define MAX(a,b) ((a) < (b) ? b : a)
#endif
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? a : b)
#endif
typedef TkTextUndoMyAtom MyUndoAtom;
#define ATOM_SIZE(n) (Tk_Offset(TkTextUndoMyAtom, data) \
+ Tk_Offset(TkTextUndoAtom, array) + (n)*sizeof(TkTextUndoSubAtom))
enum { InitialCapacity = 20 };
static void
FreeItems(
const TkTextUndoStack stack,
const TkTextUndoAtom *atom)
{
TkTextUndoFreeProc *freeProc = stack->freeProc;
const TkTextUndoSubAtom *arr;
unsigned i, n;
assert(atom);
if (!freeProc) {
return;
}
arr = atom->array;
n = atom->arraySize;
for (i = 0; i < n; ++i) {
freeProc(stack, &arr[i]);
}
}
static void
Release(
TkTextUndoStack stack,
MyUndoAtom *atom)
{
MyUndoAtom *first, *root, *prev;
if (!atom) {
return;
}
assert(stack->root);
first = atom;
root = stack->root;
prev = atom->prev;
do {
MyUndoAtom *next = atom->next;
FreeItems(stack, &atom->data);
free(atom);
atom = next;
} while (atom != root);
if (first == root) {
stack->root = stack->last = NULL;
} else {
root->prev = prev;
prev->next = root;
}
}
static void
ResetCurrent(
TkTextUndoStack stack,
bool force)
{
TkTextUndoMyAtom *current = stack->current;
if (current) {
FreeItems(stack, ¤t->data);
}
if (force || !current || current->capacity > InitialCapacity) {
static unsigned Size = ATOM_SIZE(InitialCapacity);
current = stack->current = memset(realloc(current, Size), 0, Size);
current->capacity = InitialCapacity;
}
current->data.arraySize = 0;
current->data.size = 0;
current->undoSize = 0;
}
static MyUndoAtom *
SwapCurrent(
TkTextUndoStack stack,
MyUndoAtom *atom)
{
MyUndoAtom *current = stack->current;
assert(atom != current);
if (current->capacity != current->data.size) {
current = stack->current = realloc(current, ATOM_SIZE(current->data.arraySize));
current->capacity = current->data.arraySize;
}
if (!atom) {
stack->current = NULL;
return current;
}
if (atom->next == atom) {
current->next = current;
current->prev = current;
} else {
current->next = atom->next;
current->prev = atom->prev;
atom->next->prev = current;
atom->prev->next = current;
}
stack->current = atom;
atom->data.arraySize = 0;
atom->data.size = 0;
atom->undoSize = 0;
atom->next = atom->prev = NULL;
if (stack->root == atom) {
stack->root = current;
}
if (stack->last == atom) {
stack->last = current;
}
return current;
}
static bool
ClearRedoStack(
TkTextUndoStack stack)
{
MyUndoAtom *atom;
if (stack->redoDepth == 0) {
return false;
}
atom = stack->last ? stack->last->next : stack->root;
assert(atom);
stack->redoDepth = 0;
stack->redoSize = 0;
stack->redoItems = 0;
Release(stack, atom);
return true;
}
static void
InsertCurrentAtom(
TkTextUndoStack stack)
{
MyUndoAtom *atom;
MyUndoAtom *current = stack->current;
if (!current || current->data.arraySize == 0) {
assert(!stack->doingUndo && !stack->doingRedo);
return;
}
if (stack->maxSize > 0 && !stack->doingRedo) {
unsigned newStackSize = current->data.size;
if (stack->doingUndo) {
newStackSize = MAX(current->undoSize, newStackSize);
}
newStackSize += stack->undoSize + stack->redoSize;
if (newStackSize > stack->maxSize) {
if (stack->doingUndo) {
ClearRedoStack(stack);
} else {
stack->irreversible = true;
}
FreeItems(stack, &stack->current->data);
ResetCurrent(stack, false);
return;
}
}
if (stack->doingRedo) {
if (!stack->last) {
stack->last = stack->root;
}
atom = stack->last;
SwapCurrent(stack, atom);
stack->undoDepth += 1;
stack->undoSize += atom->data.size;
stack->undoItems += atom->data.arraySize;
} else if (stack->doingUndo) {
assert(stack->maxRedoDepth <= 0 || stack->redoDepth < stack->maxRedoDepth);
atom = stack->last ? stack->last->next : stack->root;
SwapCurrent(stack, atom);
stack->redoDepth += 1;
stack->redoSize += atom->data.size;
stack->redoItems += atom->data.arraySize;
} else if (stack->last && stack->undoDepth == stack->maxUndoDepth) {
ClearRedoStack(stack);
assert(stack->last);
atom = stack->last->next;
stack->root = atom->next;
stack->last = atom;
stack->undoSize -= atom->data.size;
stack->undoItems -= atom->data.arraySize;
stack->irreversible = true;
FreeItems(stack, &atom->data);
SwapCurrent(stack, atom);
stack->undoSize += atom->data.size;
stack->undoItems += atom->data.arraySize;
} else {
ClearRedoStack(stack);
if (stack->last == NULL) {
stack->last = stack->root;
}
atom = SwapCurrent(stack, NULL);
if ((atom->prev = stack->last)) {
atom->next = stack->last->next;
stack->last->next->prev = atom;
stack->last->next = atom;
} else {
atom->next = atom->prev = stack->root = atom;
}
stack->last = atom;
stack->undoDepth += 1;
stack->undoSize += atom->data.size;
stack->undoItems += atom->data.arraySize;
}
if (!stack->doingUndo) {
atom->undoSize = atom->data.size;
}
ResetCurrent(stack, false);
}
static int
ResetStack(
TkTextUndoStack stack,
bool irreversible)
{
bool contentChanged;
assert(stack);
if (stack->doingUndo || stack->doingRedo) {
return TCL_ERROR;
}
contentChanged = stack->undoDepth > 0 || stack->redoDepth > 0 || stack->current;
if (contentChanged) {
Release(stack, stack->root);
ResetCurrent(stack, true);
stack->root = NULL;
stack->last = NULL;
stack->undoDepth = 0;
stack->redoDepth = 0;
stack->undoItems = 0;
stack->redoItems = 0;
stack->undoSize = 0;
stack->redoSize = 0;
stack->irreversible = irreversible;
stack->pushSeparator = false;
if (stack->contentChangedProc) {
stack->contentChangedProc(stack);
}
}
return TCL_OK;
}
TkTextUndoStack
TkTextUndoCreateStack(
unsigned maxUndoDepth,
int maxRedoDepth,
unsigned maxSize,
TkTextUndoPerformProc undoProc,
TkTextUndoFreeProc freeProc,
TkTextUndoStackContentChangedProc contentChangedProc)
{
TkTextUndoStack stack;
assert(undoProc);
stack = memset(malloc(sizeof(*stack)), 0, sizeof(*stack));
stack->undoProc = undoProc;
stack->freeProc = freeProc;
stack->contentChangedProc = contentChangedProc;
stack->maxUndoDepth = maxUndoDepth;
stack->maxRedoDepth = MAX(maxRedoDepth, -1);
stack->maxSize = maxSize;
return stack;
}
void
TkTextUndoDestroyStack(
TkTextUndoStack *stackPtr)
{
if (stackPtr) {
TkTextUndoStack stack = *stackPtr;
if (stack) {
assert(stack);
TkTextUndoClearStack(stack);
if (stack->current) {
FreeItems(stack, &stack->current->data);
}
free(stack);
*stackPtr = NULL;
}
}
}
int
TkTextUndoResetStack(
TkTextUndoStack stack)
{
return stack ? ResetStack(stack, false) : TCL_ERROR;
}
int
TkTextUndoClearStack(
TkTextUndoStack stack)
{
return stack ? ResetStack(stack, stack->undoDepth > 0) : TCL_ERROR;
}
int
TkTextUndoClearUndoStack(
TkTextUndoStack stack)
{
if (!stack) {
return TCL_OK;
}
if (stack->doingUndo || stack->doingRedo) {
return TCL_ERROR;
}
if (stack->undoDepth > 0) {
TkTextUndoMyAtom *atom;
assert(stack->last);
stack->undoDepth = 0;
stack->undoSize = 0;
stack->undoItems = 0;
atom = stack->root;
stack->root = stack->last->next;
stack->last = NULL;
Release(stack, atom);
ResetCurrent(stack, true);
stack->irreversible = true;
if (stack->contentChangedProc) {
stack->contentChangedProc(stack);
}
}
return TCL_OK;
}
int
TkTextUndoClearRedoStack(
TkTextUndoStack stack)
{
if (!stack) {
return TCL_OK;
}
if (stack->doingUndo || stack->doingRedo) {
return TCL_ERROR;
}
if (ClearRedoStack(stack) && stack->contentChangedProc) {
stack->contentChangedProc(stack);
}
return TCL_OK;
}
int
TkTextUndoSetMaxStackDepth(
TkTextUndoStack stack,
unsigned maxUndoDepth,
int maxRedoDepth)
{
assert(stack);
if (stack->doingUndo || stack->doingRedo) {
return TCL_ERROR;
}
if (maxUndoDepth > 0 || maxRedoDepth >= 0) {
unsigned depth = stack->maxUndoDepth;
if (depth == 0) {
depth = stack->undoDepth + stack->redoDepth;
}
if ((0 < maxUndoDepth && maxUndoDepth < depth)
|| (0 <= maxRedoDepth && (unsigned) maxRedoDepth < (unsigned) stack->maxRedoDepth)) {
unsigned deleteRedos = MIN(stack->redoDepth, depth - maxUndoDepth);
if (0 <= maxRedoDepth && maxRedoDepth < stack->maxRedoDepth) {
deleteRedos = MIN(stack->redoDepth,
MAX(deleteRedos, stack->maxRedoDepth - maxRedoDepth));
}
stack->redoDepth -= deleteRedos;
depth = maxUndoDepth - deleteRedos;
if (deleteRedos > 0) {
MyUndoAtom *atom = stack->root;
for ( ; deleteRedos > 0; --deleteRedos) {
atom = atom->prev;
stack->redoSize -= atom->data.size;
stack->redoItems -= atom->data.arraySize;
}
Release(stack, atom);
}
if (maxUndoDepth > 0 && stack->undoDepth > depth) {
MyUndoAtom *root = stack->root;
MyUndoAtom *atom = stack->root;
unsigned deleteUndos = stack->undoDepth - depth;
stack->undoDepth -= deleteUndos;
for ( ; deleteUndos > 0; --deleteUndos) {
stack->undoSize -= root->data.size;
stack->undoItems -= root->data.arraySize;
root = root->next;
}
stack->root = root;
stack->irreversible = true;
Release(stack, atom);
}
if (stack->contentChangedProc) {
stack->contentChangedProc(stack);
}
}
}
stack->maxUndoDepth = maxUndoDepth;
stack->maxRedoDepth = MAX(maxRedoDepth, -1);
return TCL_OK;
}
int
TkTextUndoSetMaxStackSize(
TkTextUndoStack stack,
unsigned maxSize,
bool applyImmediately)
{
assert(stack);
if (stack->doingUndo || stack->doingRedo) {
return TCL_ERROR;
}
if (applyImmediately
&& 0 < maxSize
&& maxSize < stack->undoSize + stack->redoSize) {
unsigned size = stack->undoSize + stack->redoSize;
MyUndoAtom *atom = stack->root;
unsigned depth = stack->redoDepth;
while (depth > 0 && maxSize < size) {
atom = atom->prev;
size -= atom->data.size;
stack->redoSize -= atom->data.size;
stack->redoItems -= atom->data.arraySize;
depth -= 1;
}
while (atom->data.size == 0 && atom != stack->root) {
stack->redoItems += atom->data.arraySize;
atom = atom->next;
depth += 1;
}
if (depth < stack->redoDepth) {
stack->redoDepth = depth;
Release(stack, atom);
}
if (maxSize < size && stack->last) {
MyUndoAtom *root = stack->root;
depth = stack->undoDepth;
while (depth > 0 && maxSize < size) {
size -= root->data.size;
stack->undoSize -= root->data.size;
stack->undoItems -= root->data.arraySize;
depth -= 1;
root = root->next;
}
while (root->data.size == 0 && depth < stack->undoDepth) {
stack->undoItems += root->data.arraySize;
depth += 1;
root = root->prev;
}
if (depth < stack->undoDepth) {
stack->undoDepth = depth;
atom = stack->root;
stack->root = root;
stack->irreversible = true;
Release(stack, atom);
}
}
if (stack->contentChangedProc) {
stack->contentChangedProc(stack);
}
}
stack->maxSize = maxSize;
return TCL_OK;
}
static void
PushSeparator(
TkTextUndoStack stack,
bool force)
{
assert(stack);
if (force || stack->pushSeparator) {
if (!stack->doingUndo && !stack->doingRedo) {
InsertCurrentAtom(stack);
}
}
stack->pushSeparator = false;
}
void
TkTextUndoPushSeparator(
TkTextUndoStack stack,
bool immediately)
{
assert(stack);
if (immediately) {
PushSeparator(stack, true);
} else {
stack->pushSeparator = true;
}
}
int
TkTextUndoPushItem(
TkTextUndoStack stack,
TkTextUndoItem item,
unsigned size)
{
MyUndoAtom *atom;
TkTextUndoSubAtom *subAtom;
assert(stack);
assert(item);
PushSeparator(stack, false);
if (stack->doingUndo && TkTextUndoRedoStackIsFull(stack)) {
if (stack->freeProc) {
stack->freeProc(stack, item);
}
return TCL_ERROR;
}
atom = stack->current;
if (!atom) {
ResetCurrent(stack, true);
atom = stack->current;
} else if (atom->data.arraySize == atom->capacity) {
atom->capacity *= 2;
atom = stack->current = realloc(atom, ATOM_SIZE(atom->capacity));
}
subAtom = ((TkTextUndoSubAtom *) atom->data.array) + atom->data.arraySize++;
subAtom->item = item;
subAtom->size = size;
subAtom->redo = stack->doingUndo;
atom->data.size += size;
atom->data.redo = stack->doingUndo;
if (stack->contentChangedProc && !stack->doingUndo && !stack->doingRedo) {
stack->contentChangedProc(stack);
}
return TCL_OK;
}
int
TkTextUndoPushRedoItem(
TkTextUndoStack stack,
TkTextUndoItem item,
unsigned size)
{
int rc;
assert(stack);
assert(!TkTextUndoIsPerformingUndoRedo(stack));
PushSeparator(stack, true);
stack->doingUndo = true;
rc = TkTextUndoPushItem(stack, item, size);
stack->doingUndo = false;
return rc;
}
TkTextUndoItem
TkTextUndoSwapLastItem(
TkTextUndoStack stack,
TkTextUndoItem item,
unsigned *size)
{
TkTextUndoAtom *last;
TkTextUndoSubAtom *subAtom;
TkTextUndoItem oldItem;
unsigned oldSize;
assert(stack);
assert(TkTextUndoGetLastUndoSubAtom(stack));
last = stack->current ? &stack->current->data : &stack->last->data;
subAtom = (TkTextUndoSubAtom *) last->array + (last->arraySize - 1);
oldSize = subAtom->size;
last->size -= oldSize;
last->size += *size;
stack->undoSize -= oldSize;
stack->undoSize += *size;
oldItem = subAtom->item;
subAtom->item = item;
subAtom->size = *size;
*size = oldSize;
return oldItem;
}
int
TkTextUndoDoUndo(
TkTextUndoStack stack)
{
int rc;
assert(stack);
if (stack->doingUndo || stack->doingRedo) {
return TCL_ERROR;
}
InsertCurrentAtom(stack);
if (stack->undoDepth == 0) {
rc = TCL_ERROR;
} else {
MyUndoAtom *atom;
assert(stack->last);
stack->actual = atom = stack->last;
stack->doingUndo = true;
stack->undoDepth -= 1;
stack->undoSize -= stack->actual->data.size;
stack->undoItems -= stack->actual->data.arraySize;
stack->undoProc(stack, &stack->actual->data);
stack->last = stack->undoDepth ? stack->last->prev : NULL;
stack->actual = NULL;
if (!stack->current || stack->current->data.arraySize == 0) {
stack->redoDepth = 0;
stack->redoSize = 0;
stack->redoItems = 0;
Release(stack, stack->last ? stack->last->next : stack->root);
} else {
FreeItems(stack, &atom->data);
InsertCurrentAtom(stack);
}
stack->doingUndo = false;
rc = TCL_OK;
if (stack->contentChangedProc) {
stack->contentChangedProc(stack);
}
}
return rc;
}
int
TkTextUndoDoRedo(
TkTextUndoStack stack)
{
int rc;
assert(stack);
if (stack->doingUndo || stack->doingRedo) {
return TCL_ERROR;
}
InsertCurrentAtom(stack);
if (stack->redoDepth == 0) {
rc = TCL_ERROR;
} else {
MyUndoAtom *atom;
stack->actual = atom = stack->last ? stack->last->next : stack->root;
stack->doingRedo = true;
stack->redoDepth -= 1;
stack->redoSize -= atom->data.size;
stack->redoItems -= atom->data.arraySize;
stack->undoProc(stack, &atom->data);
stack->last = atom;
stack->actual = NULL;
if (!stack->current || stack->current->data.arraySize == 0) {
if (stack->undoDepth > 0) {
stack->undoDepth = 0;
stack->undoItems = 0;
stack->undoSize = 0;
atom = stack->root;
stack->root = stack->last->next;
stack->last = NULL;
} else {
stack->root = atom->next;
}
Release(stack, atom);
stack->irreversible = true;
} else {
FreeItems(stack, &atom->data);
InsertCurrentAtom(stack);
}
stack->doingRedo = false;
rc = TCL_OK;
if (stack->contentChangedProc) {
stack->contentChangedProc(stack);
}
}
return rc;
}
bool
TkTextUndoStackIsFull(
const TkTextUndoStack stack)
{
if (!stack) {
return true;
}
if (stack->doingUndo) {
return stack->maxRedoDepth >= 0 && stack->redoDepth >= stack->maxRedoDepth;
}
return stack->maxUndoDepth > 0 && stack->undoDepth >= stack->maxUndoDepth;
}
const TkTextUndoAtom *
TkTextUndoFirstUndoAtom(
TkTextUndoStack stack)
{
assert(stack);
if (stack->current && stack->current->data.arraySize && !stack->doingUndo) {
return &(stack->iter = stack->current)->data;
}
if (stack->undoDepth > 0 && stack->last != stack->actual) {
return &(stack->iter = stack->last)->data;
}
stack->iter = NULL;
return NULL;
}
const TkTextUndoAtom *
TkTextUndoNextUndoAtom(
TkTextUndoStack stack)
{
assert(stack);
if (stack->iter) {
if (stack->iter == stack->current) {
if (stack->undoDepth > 0 && stack->last != stack->actual) {
return &(stack->iter = stack->last)->data;
}
stack->iter = NULL;
} else if (stack->iter != stack->root && (stack->iter = stack->iter->prev) != stack->actual) {
return &stack->iter->data;
} else {
stack->iter = NULL;
}
}
return NULL;
}
const TkTextUndoAtom *
TkTextUndoFirstRedoAtom(
TkTextUndoStack stack)
{
assert(stack);
if (stack->redoDepth > 0 && stack->root->prev != stack->actual) {
return &(stack->iter = stack->root->prev)->data;
}
stack->iter = NULL;
if (stack->current && stack->current->data.arraySize && stack->doingUndo) {
return &stack->current->data;
}
return NULL;
}
const TkTextUndoAtom *
TkTextUndoNextRedoAtom(
TkTextUndoStack stack)
{
assert(stack);
if (stack->iter) {
if (stack->iter != stack->root
&& (stack->iter = stack->iter->prev) != stack->last
&& stack->iter != stack->actual) {
return &stack->iter->data;
}
stack->iter = NULL;
if (stack->current && stack->current->data.arraySize && stack->doingUndo) {
return &stack->current->data;
}
}
return NULL;
}
#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
#define inline extern
inline void TkTextUndoSetContext(TkTextUndoStack stack, TkTextUndoContext context);
inline TkTextUndoContext TkTextUndoGetContext(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetMaxUndoDepth(const TkTextUndoStack stack);
inline int TkTextUndoGetMaxRedoDepth(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetMaxSize(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetCurrentDepth(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetCurrentSize(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetCurrentUndoStackDepth(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetCurrentRedoStackDepth(const TkTextUndoStack stack);
inline unsigned TkTextUndoCountUndoItems(const TkTextUndoStack stack);
inline unsigned TkTextUndoCountRedoItems(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetCurrentUndoSize(const TkTextUndoStack stack);
inline unsigned TkTextUndoGetCurrentRedoSize(const TkTextUndoStack stack);
inline unsigned TkTextUndoCountCurrentUndoItems(const TkTextUndoStack stack);
inline unsigned TkTextUndoCountCurrentRedoItems(const TkTextUndoStack stack);
inline bool TkTextUndoContentIsIrreversible(const TkTextUndoStack stack);
inline bool TkTextUndoContentIsModified(const TkTextUndoStack stack);
inline bool TkTextUndoIsPerformingUndo(const TkTextUndoStack stack);
inline bool TkTextUndoIsPerformingRedo(const TkTextUndoStack stack);
inline bool TkTextUndoIsPerformingUndoRedo(const TkTextUndoStack stack);
inline const TkTextUndoAtom *TkTextUndoCurrentUndoAtom(const TkTextUndoStack stack);
inline const TkTextUndoAtom *TkTextUndoCurrentRedoAtom(const TkTextUndoStack stack);
inline const TkTextUndoSubAtom *TkTextUndoGetLastUndoSubAtom(const TkTextUndoStack stack);
inline bool TkTextUndoUndoStackIsFull(const TkTextUndoStack stack);
inline bool TkTextUndoRedoStackIsFull(const TkTextUndoStack stack);
#endif