home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libmisc / undo.c < prev   
Encoding:
C/C++ Source or Header  |  1998-04-08  |  9.7 KB  |  445 lines

  1. /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18.  
  19. /*
  20.    undo.c --- undo facilities
  21.  */
  22.  
  23. #include "xp.h"            /* For XP_Bool... */
  24. #include "undo.h"
  25.  
  26.  
  27. extern int MK_OUT_OF_MEMORY;
  28.  
  29.  
  30. typedef enum {
  31.   BOUNDARY,
  32.   EVENT
  33. } undo_event_type;
  34.  
  35. typedef struct undo_event 
  36. {
  37.   undo_event_type type;
  38.   struct undo_event* next;
  39.   struct undo_event* prev;
  40.   void* closure;
  41.   int (*undoit)(void*);
  42.   void (*freeit)(void*);
  43.   void* tag;  
  44.   void (*freetag)(void*);  
  45. } undo_event;
  46.  
  47.  
  48. struct UndoState {
  49.   undo_event* events;
  50.   undo_event* redoevents;
  51.   int depth;
  52.   XP_Bool undoing;
  53.   XP_Bool redoing;
  54.   XP_Bool loggedsomething;
  55.   int count;
  56.   XP_AllocStructInfo allocinfo;
  57.   int maxdepth;
  58.   XP_Bool discardall;
  59. };
  60.  
  61.  
  62.  
  63.  
  64. #ifdef DEBUG
  65. static void
  66. undo_check_integrity(UndoState* state)
  67. {
  68.   int i;
  69.   for (i=0 ; i<2 ; i++) {
  70.     undo_event** start;
  71.     undo_event* tmp;
  72.     int count1, count2;
  73.     if (i == 0) {
  74.       start = &(state->redoevents);
  75.     } else {
  76.       start = &(state->events);
  77.     }
  78.     if (*start) {
  79.       count1 = 0;
  80.       for (tmp = (*start)->next ; tmp != *start ; tmp = tmp->next) {
  81.         count1++;
  82.       }
  83.       count2 = 0;
  84.       for (tmp = (*start)->prev ; tmp != *start ; tmp = tmp->prev) {
  85.         count2++;
  86.       }
  87.       XP_ASSERT(count1 == count2);
  88.     }
  89.   }
  90. }
  91. #else
  92. #define undo_check_integrity(state)    /* no-op */
  93. #endif
  94.  
  95.  
  96.  
  97. UndoState*
  98. UNDO_Create(int maxdepth)
  99. {
  100.   UndoState* state = XP_NEW_ZAP(UndoState);
  101.   if (state) {
  102.     state->maxdepth = maxdepth;
  103.     XP_InitAllocStructInfo(&(state->allocinfo), sizeof(undo_event));
  104.   }
  105.   return state;
  106. }
  107.  
  108.  
  109. static void
  110. undo_free_list(UndoState* state, undo_event** list)
  111. {
  112.   undo_event* tmp;
  113.   undo_event* next;
  114.   undo_check_integrity(state);
  115.   if (*list) {
  116.     (*list)->prev->next = NULL;
  117.     for (tmp = *list ; tmp ; tmp = next) {
  118.       next = tmp->next;
  119.       if (tmp->freeit) (*tmp->freeit)(tmp->closure);
  120.       if (tmp->freetag) (*tmp->freetag)(tmp->tag);      
  121.       XP_FreeStruct(&state->allocinfo, tmp);
  122.     }
  123.     *list = NULL;
  124.   }
  125.   undo_check_integrity(state);
  126. }
  127.  
  128. void
  129. UNDO_Destroy(UndoState* state)
  130. {
  131.   undo_free_list(state, &state->events);
  132.   undo_free_list(state, &state->redoevents);
  133.   XP_FreeAllStructs(&state->allocinfo);
  134.   XP_FREE(state);
  135. }
  136.  
  137.  
  138. void
  139. UNDO_DiscardAll(UndoState* state)
  140. {
  141.   undo_free_list(state, &state->events);
  142.   undo_free_list(state, &state->redoevents);
  143.   if (state->depth > 0) state->discardall = TRUE;
  144.   state->count = 0;
  145. }
  146.  
  147.  
  148. static int
  149. undo_log(UndoState* state, undo_event* event)
  150. {
  151.   undo_event** start;
  152.   int status;
  153.   undo_check_integrity(state);
  154.   if (event->type != BOUNDARY && state->depth == 0) {
  155.     UNDO_StartBatch(state);
  156.     status = undo_log(state, event);
  157.     if (status < 0) {
  158.       state->depth = 0;
  159.       return status;
  160.     }
  161.     return UNDO_EndBatch(state, event->freetag, event->tag);
  162.   }
  163.   if (state->undoing) {
  164.     start = &(state->redoevents);
  165.   } else {
  166.     start = &(state->events);
  167.   }
  168.   if (!*start) {
  169.     event->next = event;
  170.     event->prev = event;
  171.   } else {
  172.     event->next = *start;
  173.     event->prev = event->next->prev;
  174.     event->next->prev = event;
  175.     event->prev->next = event;
  176.   }
  177.   *start = event;
  178.   undo_check_integrity(state);
  179.   return 0;
  180. }
  181.  
  182.  
  183. int
  184. UNDO_LogEvent(UndoState* state, int (*undoit)(void*),
  185.               void (*freeit)(void*), void* closure,
  186.               void (*freetag)(void*), void* tag)
  187. {
  188.   undo_event* tmp;
  189.   if (state->discardall) {
  190.     (*freeit)(closure);
  191.     if (freetag) (*freetag)(tag);          
  192.     return 0;
  193.   }
  194.   tmp = (undo_event*) XP_AllocStructZero(&state->allocinfo);
  195.   if (!tmp) {
  196.     UNDO_DiscardAll(state);
  197.     (*freeit)(closure);
  198.     if (freetag) (*freetag)(tag);
  199.     return MK_OUT_OF_MEMORY;
  200.   }
  201.   
  202.   state->loggedsomething = TRUE;
  203.   
  204.   tmp->type       = EVENT;
  205.   tmp->undoit     = undoit;
  206.   tmp->freeit     = freeit;
  207.   tmp->closure    = closure;
  208.   tmp->tag        = tag;
  209.   tmp->freetag    = freetag;
  210.   
  211.   return undo_log(state, tmp);
  212. }
  213.  
  214.  
  215. int
  216. UNDO_StartBatch(UndoState* state)
  217. {
  218.  #if 0 /* This is bad.  The redo stack is not purged when a new event is logged. */
  219.   if (state->depth == 0) {
  220.  #else /* A better way: Only reset loggedsomething if we're in the midst of an undo or redo. */
  221.   if( state->undoing || state->redoing ) {
  222.  #endif
  223.     state->loggedsomething = FALSE;
  224.   }
  225.   state->depth++;
  226.   undo_check_integrity(state);
  227.   return 0;
  228. }
  229.  
  230.  
  231. int
  232. UNDO_EndBatch(UndoState* state, void (*freetag)(void*), void* tag)
  233. {
  234.   int status;
  235.   undo_check_integrity(state);
  236.   XP_ASSERT(state->depth > 0);
  237.   state->depth--;
  238.   if (state->depth == 0) {
  239.     undo_event** start;
  240.     if (state->discardall) {
  241.       UNDO_DiscardAll(state);
  242.       state->discardall = FALSE;
  243.       return 0;
  244.     }
  245.     if (state->undoing) {
  246.       start = &(state->redoevents);
  247.     } else {
  248.       start = &(state->events);
  249.       if (state->loggedsomething) {
  250.         undo_free_list(state, &state->redoevents);
  251.       }
  252.     }
  253.     if (*start && (*start)->type != BOUNDARY) {
  254.       undo_event* tmp = (undo_event*)XP_AllocStructZero(&state->allocinfo);
  255.       if (!tmp) {
  256.         UNDO_DiscardAll(state);
  257.         return MK_OUT_OF_MEMORY;
  258.       }
  259.       tmp->type     = BOUNDARY;
  260.       tmp->tag      = tag;
  261.       tmp->freetag  = freetag;
  262.       status = undo_log(state, tmp);
  263.       if (status < 0) {
  264.         UNDO_DiscardAll(state);
  265.         return status;
  266.       }
  267.       if (!state->undoing) {
  268.         if (state->count >= state->maxdepth) {
  269.           /* exceeded undo count - pull one off the bottom of the stack. */
  270.           undo_event* prev;
  271.           for (tmp = state->events->prev ; ; tmp = prev) {
  272.             undo_event_type type = tmp->type;
  273.             /* better not be at the top of the stack, */
  274.             XP_ASSERT(tmp != state->events);
  275.             prev = tmp->prev;
  276.             tmp->prev->next = tmp->next;
  277.             tmp->next->prev = tmp->prev;
  278.             if (type == EVENT) {
  279.               if (tmp->freeit) (*tmp->freeit)(tmp->closure);
  280.               if (tmp->freetag) (*tmp->freetag)(tmp->tag);                    
  281.             }
  282.             XP_FreeStruct(&state->allocinfo, tmp);
  283.             /* stop at the next boundary, which makes a whole Batch */
  284.             if (type == BOUNDARY) break;
  285.           }
  286.         }
  287.         else
  288.         {
  289.           state->count++;
  290.         }
  291.       }
  292.     }
  293.     undo_check_integrity(state);
  294.   }
  295.   return 0;
  296. }
  297.  
  298.  
  299.  
  300. static int
  301. undo_doone(UndoState* state, undo_event** from)
  302. {
  303.   int status = 0;
  304.   undo_event* tmp = *from;
  305.   undo_check_integrity(state);
  306.   XP_ASSERT(tmp != NULL);
  307.   switch (tmp->type) {
  308.   case BOUNDARY:
  309.     break;
  310.   case EVENT:
  311.     if (tmp->undoit) {
  312.       status = (*tmp->undoit)(tmp->closure);
  313.     }
  314.     break;
  315.   default:
  316.     XP_ASSERT(0);
  317.   }
  318.   *from = tmp->next;
  319.   if (*from == tmp) {
  320.     *from = NULL;
  321.   } else {
  322.     (*from)->prev = tmp->prev;
  323.     (*from)->prev->next = (*from);
  324.   }
  325.   undo_check_integrity(state);
  326.   XP_FreeStruct(&state->allocinfo, tmp);
  327.   return status;
  328. }
  329.   
  330.  
  331.  
  332.  
  333. static int
  334. undo_doit(UndoState* state, undo_event** from)
  335. {
  336.   int status;
  337.   void (*tmp_freetag)(void*);
  338.   void *tmp_tag;
  339.   
  340.   XP_ASSERT(state->depth == 0);
  341.   if (!*from) return 0;
  342.   status = UNDO_StartBatch(state);
  343.   if (status < 0) return status;
  344.   
  345.   XP_ASSERT((*from)->type == BOUNDARY);
  346.   
  347.   /* Save the tag information so it persists in the opposing undo/redo stack */
  348.   tmp_freetag = (*from)->freetag;  
  349.   tmp_tag     = (*from)->tag;
  350.   
  351.   do {
  352.     status = undo_doone(state, from);
  353.     if (status < 0) break;
  354.   } while (*from && (*from)->type != BOUNDARY);
  355.   if (status >= 0) status = UNDO_EndBatch(state, tmp_freetag, tmp_tag);
  356.   if (status < 0) {
  357.     UNDO_DiscardAll(state);
  358.   }
  359.   return status;
  360. }
  361.   
  362.  
  363.  
  364.  
  365. int
  366. UNDO_DoUndo(UndoState* state)
  367. {
  368.   int status;
  369.   XP_ASSERT(state->depth == 0 && !state->undoing);
  370.   state->undoing = TRUE;
  371.   status = undo_doit(state, &(state->events));
  372.   state->undoing = FALSE;
  373.   state->count--;
  374.   return status;
  375. }
  376.  
  377.  
  378. int
  379. UNDO_DoRedo(UndoState* state)
  380. {
  381.   int status;
  382.   undo_event* tmp = state->redoevents;
  383.   state->redoevents = NULL;    /* Prevent any code from throwing away the
  384.                                remaining redo events. */
  385.   XP_ASSERT(state->depth == 0 && !state->undoing);
  386.   state->redoing = TRUE;  
  387.   status = undo_doit(state, &tmp);
  388.   state->redoing = FALSE;    
  389.   XP_ASSERT(state->redoevents == NULL);
  390.   state->redoevents = tmp;
  391.   return status;
  392. }
  393.  
  394.  
  395. static XP_Bool
  396. undo_has_event(undo_event* event)
  397. {
  398.   undo_event* tmp = event;
  399.   if (tmp) {
  400.     do {
  401.       if (tmp->type == EVENT) return TRUE;
  402.       tmp = tmp->next;
  403.     } while (tmp != event);
  404.   }
  405.   return FALSE;
  406. }    
  407.  
  408. XP_Bool
  409. UNDO_CanUndo(UndoState* state)
  410. {
  411.   return undo_has_event(state->events);
  412. }
  413.  
  414. XP_Bool
  415. UNDO_CanRedo(UndoState* state)
  416. {
  417.   return undo_has_event(state->redoevents);
  418. }
  419.  
  420. static void *
  421. undo_get_tag(undo_event* event)
  422. {
  423.   undo_event* tmp = event;
  424.   if (tmp) {
  425.     do {
  426.       if (tmp->type == BOUNDARY)
  427.          return tmp->tag;
  428.       tmp = tmp->next;
  429.     } while (tmp != event);
  430.   }
  431.   return NULL;
  432. }    
  433.  
  434. void *
  435. UNDO_PeekUndoTag(UndoState* state)
  436. {
  437.   return state ? undo_get_tag(state->events) : NULL;
  438. }
  439.  
  440. void *
  441. UNDO_PeekRedoTag(UndoState* state)
  442. {
  443.   return state ? undo_get_tag(state->redoevents) : NULL;
  444. }
  445.