home *** CD-ROM | disk | FTP | other *** search
- /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * The contents of this file are subject to the Netscape Public License
- * Version 1.0 (the "NPL"); you may not use this file except in
- * compliance with the NPL. You may obtain a copy of the NPL at
- * http://www.mozilla.org/NPL/
- *
- * Software distributed under the NPL is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
- * for the specific language governing rights and limitations under the
- * NPL.
- *
- * The Initial Developer of this code under the NPL is Netscape
- * Communications Corporation. Portions created by Netscape are
- * Copyright (C) 1998 Netscape Communications Corporation. All Rights
- * Reserved.
- */
-
- /*
- undo.c --- undo facilities
- */
-
- #include "xp.h" /* For XP_Bool... */
- #include "undo.h"
-
-
- extern int MK_OUT_OF_MEMORY;
-
-
- typedef enum {
- BOUNDARY,
- EVENT
- } undo_event_type;
-
- typedef struct undo_event
- {
- undo_event_type type;
- struct undo_event* next;
- struct undo_event* prev;
- void* closure;
- int (*undoit)(void*);
- void (*freeit)(void*);
- void* tag;
- void (*freetag)(void*);
- } undo_event;
-
-
- struct UndoState {
- undo_event* events;
- undo_event* redoevents;
- int depth;
- XP_Bool undoing;
- XP_Bool redoing;
- XP_Bool loggedsomething;
- int count;
- XP_AllocStructInfo allocinfo;
- int maxdepth;
- XP_Bool discardall;
- };
-
-
-
-
- #ifdef DEBUG
- static void
- undo_check_integrity(UndoState* state)
- {
- int i;
- for (i=0 ; i<2 ; i++) {
- undo_event** start;
- undo_event* tmp;
- int count1, count2;
- if (i == 0) {
- start = &(state->redoevents);
- } else {
- start = &(state->events);
- }
- if (*start) {
- count1 = 0;
- for (tmp = (*start)->next ; tmp != *start ; tmp = tmp->next) {
- count1++;
- }
- count2 = 0;
- for (tmp = (*start)->prev ; tmp != *start ; tmp = tmp->prev) {
- count2++;
- }
- XP_ASSERT(count1 == count2);
- }
- }
- }
- #else
- #define undo_check_integrity(state) /* no-op */
- #endif
-
-
-
- UndoState*
- UNDO_Create(int maxdepth)
- {
- UndoState* state = XP_NEW_ZAP(UndoState);
- if (state) {
- state->maxdepth = maxdepth;
- XP_InitAllocStructInfo(&(state->allocinfo), sizeof(undo_event));
- }
- return state;
- }
-
-
- static void
- undo_free_list(UndoState* state, undo_event** list)
- {
- undo_event* tmp;
- undo_event* next;
- undo_check_integrity(state);
- if (*list) {
- (*list)->prev->next = NULL;
- for (tmp = *list ; tmp ; tmp = next) {
- next = tmp->next;
- if (tmp->freeit) (*tmp->freeit)(tmp->closure);
- if (tmp->freetag) (*tmp->freetag)(tmp->tag);
- XP_FreeStruct(&state->allocinfo, tmp);
- }
- *list = NULL;
- }
- undo_check_integrity(state);
- }
-
- void
- UNDO_Destroy(UndoState* state)
- {
- undo_free_list(state, &state->events);
- undo_free_list(state, &state->redoevents);
- XP_FreeAllStructs(&state->allocinfo);
- XP_FREE(state);
- }
-
-
- void
- UNDO_DiscardAll(UndoState* state)
- {
- undo_free_list(state, &state->events);
- undo_free_list(state, &state->redoevents);
- if (state->depth > 0) state->discardall = TRUE;
- state->count = 0;
- }
-
-
- static int
- undo_log(UndoState* state, undo_event* event)
- {
- undo_event** start;
- int status;
- undo_check_integrity(state);
- if (event->type != BOUNDARY && state->depth == 0) {
- UNDO_StartBatch(state);
- status = undo_log(state, event);
- if (status < 0) {
- state->depth = 0;
- return status;
- }
- return UNDO_EndBatch(state, event->freetag, event->tag);
- }
- if (state->undoing) {
- start = &(state->redoevents);
- } else {
- start = &(state->events);
- }
- if (!*start) {
- event->next = event;
- event->prev = event;
- } else {
- event->next = *start;
- event->prev = event->next->prev;
- event->next->prev = event;
- event->prev->next = event;
- }
- *start = event;
- undo_check_integrity(state);
- return 0;
- }
-
-
- int
- UNDO_LogEvent(UndoState* state, int (*undoit)(void*),
- void (*freeit)(void*), void* closure,
- void (*freetag)(void*), void* tag)
- {
- undo_event* tmp;
- if (state->discardall) {
- (*freeit)(closure);
- if (freetag) (*freetag)(tag);
- return 0;
- }
- tmp = (undo_event*) XP_AllocStructZero(&state->allocinfo);
- if (!tmp) {
- UNDO_DiscardAll(state);
- (*freeit)(closure);
- if (freetag) (*freetag)(tag);
- return MK_OUT_OF_MEMORY;
- }
-
- state->loggedsomething = TRUE;
-
- tmp->type = EVENT;
- tmp->undoit = undoit;
- tmp->freeit = freeit;
- tmp->closure = closure;
- tmp->tag = tag;
- tmp->freetag = freetag;
-
- return undo_log(state, tmp);
- }
-
-
- int
- UNDO_StartBatch(UndoState* state)
- {
- #if 0 /* This is bad. The redo stack is not purged when a new event is logged. */
- if (state->depth == 0) {
- #else /* A better way: Only reset loggedsomething if we're in the midst of an undo or redo. */
- if( state->undoing || state->redoing ) {
- #endif
- state->loggedsomething = FALSE;
- }
- state->depth++;
- undo_check_integrity(state);
- return 0;
- }
-
-
- int
- UNDO_EndBatch(UndoState* state, void (*freetag)(void*), void* tag)
- {
- int status;
- undo_check_integrity(state);
- XP_ASSERT(state->depth > 0);
- state->depth--;
- if (state->depth == 0) {
- undo_event** start;
- if (state->discardall) {
- UNDO_DiscardAll(state);
- state->discardall = FALSE;
- return 0;
- }
- if (state->undoing) {
- start = &(state->redoevents);
- } else {
- start = &(state->events);
- if (state->loggedsomething) {
- undo_free_list(state, &state->redoevents);
- }
- }
- if (*start && (*start)->type != BOUNDARY) {
- undo_event* tmp = (undo_event*)XP_AllocStructZero(&state->allocinfo);
- if (!tmp) {
- UNDO_DiscardAll(state);
- return MK_OUT_OF_MEMORY;
- }
- tmp->type = BOUNDARY;
- tmp->tag = tag;
- tmp->freetag = freetag;
- status = undo_log(state, tmp);
- if (status < 0) {
- UNDO_DiscardAll(state);
- return status;
- }
- if (!state->undoing) {
- if (state->count >= state->maxdepth) {
- /* exceeded undo count - pull one off the bottom of the stack. */
- undo_event* prev;
- for (tmp = state->events->prev ; ; tmp = prev) {
- undo_event_type type = tmp->type;
- /* better not be at the top of the stack, */
- XP_ASSERT(tmp != state->events);
- prev = tmp->prev;
- tmp->prev->next = tmp->next;
- tmp->next->prev = tmp->prev;
- if (type == EVENT) {
- if (tmp->freeit) (*tmp->freeit)(tmp->closure);
- if (tmp->freetag) (*tmp->freetag)(tmp->tag);
- }
- XP_FreeStruct(&state->allocinfo, tmp);
- /* stop at the next boundary, which makes a whole Batch */
- if (type == BOUNDARY) break;
- }
- }
- else
- {
- state->count++;
- }
- }
- }
- undo_check_integrity(state);
- }
- return 0;
- }
-
-
-
- static int
- undo_doone(UndoState* state, undo_event** from)
- {
- int status = 0;
- undo_event* tmp = *from;
- undo_check_integrity(state);
- XP_ASSERT(tmp != NULL);
- switch (tmp->type) {
- case BOUNDARY:
- break;
- case EVENT:
- if (tmp->undoit) {
- status = (*tmp->undoit)(tmp->closure);
- }
- break;
- default:
- XP_ASSERT(0);
- }
- *from = tmp->next;
- if (*from == tmp) {
- *from = NULL;
- } else {
- (*from)->prev = tmp->prev;
- (*from)->prev->next = (*from);
- }
- undo_check_integrity(state);
- XP_FreeStruct(&state->allocinfo, tmp);
- return status;
- }
-
-
-
-
- static int
- undo_doit(UndoState* state, undo_event** from)
- {
- int status;
- void (*tmp_freetag)(void*);
- void *tmp_tag;
-
- XP_ASSERT(state->depth == 0);
- if (!*from) return 0;
- status = UNDO_StartBatch(state);
- if (status < 0) return status;
-
- XP_ASSERT((*from)->type == BOUNDARY);
-
- /* Save the tag information so it persists in the opposing undo/redo stack */
- tmp_freetag = (*from)->freetag;
- tmp_tag = (*from)->tag;
-
- do {
- status = undo_doone(state, from);
- if (status < 0) break;
- } while (*from && (*from)->type != BOUNDARY);
- if (status >= 0) status = UNDO_EndBatch(state, tmp_freetag, tmp_tag);
- if (status < 0) {
- UNDO_DiscardAll(state);
- }
- return status;
- }
-
-
-
-
- int
- UNDO_DoUndo(UndoState* state)
- {
- int status;
- XP_ASSERT(state->depth == 0 && !state->undoing);
- state->undoing = TRUE;
- status = undo_doit(state, &(state->events));
- state->undoing = FALSE;
- state->count--;
- return status;
- }
-
-
- int
- UNDO_DoRedo(UndoState* state)
- {
- int status;
- undo_event* tmp = state->redoevents;
- state->redoevents = NULL; /* Prevent any code from throwing away the
- remaining redo events. */
- XP_ASSERT(state->depth == 0 && !state->undoing);
- state->redoing = TRUE;
- status = undo_doit(state, &tmp);
- state->redoing = FALSE;
- XP_ASSERT(state->redoevents == NULL);
- state->redoevents = tmp;
- return status;
- }
-
-
- static XP_Bool
- undo_has_event(undo_event* event)
- {
- undo_event* tmp = event;
- if (tmp) {
- do {
- if (tmp->type == EVENT) return TRUE;
- tmp = tmp->next;
- } while (tmp != event);
- }
- return FALSE;
- }
-
- XP_Bool
- UNDO_CanUndo(UndoState* state)
- {
- return undo_has_event(state->events);
- }
-
- XP_Bool
- UNDO_CanRedo(UndoState* state)
- {
- return undo_has_event(state->redoevents);
- }
-
- static void *
- undo_get_tag(undo_event* event)
- {
- undo_event* tmp = event;
- if (tmp) {
- do {
- if (tmp->type == BOUNDARY)
- return tmp->tag;
- tmp = tmp->next;
- } while (tmp != event);
- }
- return NULL;
- }
-
- void *
- UNDO_PeekUndoTag(UndoState* state)
- {
- return state ? undo_get_tag(state->events) : NULL;
- }
-
- void *
- UNDO_PeekRedoTag(UndoState* state)
- {
- return state ? undo_get_tag(state->redoevents) : NULL;
- }
-