rework histogram_bezier, init wm icon set_icon(gg), update de.po+msg/txt
[goodguy/history.git] / cinelerra-5.1 / cinelerra / undostack.C
index acc6544f010dd0467b2150b206fd1b38d08a3221..23f33bc193148fadc237dc9ed85d8e8133429778 100644 (file)
 #include "bctimer.h"
 #include "clip.h"
 #include "cstrdup.h"
+#include "filexml.h"
 #include "undostack.h"
 #include <string.h>
 
+// undo can be incremental or direct, from key to current
+//#define UNDO_INCREMENTAL
+
 UndoStack::UndoStack() : List<UndoStackItem>()
 {
        current = 0;
@@ -35,60 +39,60 @@ UndoStack::~UndoStack()
 {
 }
 
-UndoStackItem* UndoStack::push()
+UndoStackItem *UndoStack::get_current_undo()
 {
-// current is only 0 if before first undo
-       if(current)
-               current = insert_after(current);
-       else
-               current = insert_before(first);
-
-// delete future undos if necessary
-       if(current && current->next)
-       {
-               while(current->next) remove(last);
-       }
-
-// delete oldest 2 undos if necessary
-       if(total() > UNDOLEVELS)
-       {
-               for(int i = 0; i < 2; i++)
-               {
-                       UndoStackItem *second = first->next;
-                       char *temp_data = 0;
+       UndoStackItem *item = current;
+       if( item &&  (number_of(item) & 1) ) item = item->previous;
+       if( item && !(number_of(item) & 1) ) item = item->previous;
+       return item;
+}
 
+UndoStackItem *UndoStack::get_current_redo()
+{
+       UndoStackItem *item = current ? current : first;
+       if( item &&  (number_of(item) & 1) ) item = item->next;
+       if( item && !(number_of(item) & 1) ) item = item->next;
+       return item;
+}
 
-                       if(!second->is_key())
-                       {
-                               temp_data = second->get_data();
-                       }
-                       remove(first);
 
-// Convert new first to key buffer.
-                       if(!second->is_key())
-                       {
-                               second->set_data(temp_data);
-                       }
-                       delete [] temp_data;
+UndoStackItem* UndoStack::push()
+{
+// delete oldest 2 undos if necessary
+       if( total() > UNDOLEVELS ) {
+               UndoStackItem *item = first, *key = item;
+#ifdef UNDO_INCREMENTAL
+               for( int i=2; --i>=0; item=item->next );
+#else
+               for( int i=2; --i>=0; item=item->next )
+                       if( item->is_key() ) key = item;
+#endif
+               char *data = !item->is_key() ? key->get_data() : 0;
+               while( first != item ) {
+                       if( current == first ) current = first->next;
+                       delete first;
+               }
+               if( data ) {
+                       item->set_data(data);
+                       delete [] data;
                }
        }
 
+// current is only 0 if before first undo
+       current = current ? insert_after(current) : insert_before(first);
+// delete future undos if necessary
+       while( current->next ) remove(last);
        return current;
 }
 
-void UndoStack::pull()
-{
-       if(current) current = PREVIOUS;
-}
-
 UndoStackItem* UndoStack::pull_next()
 {
 // use first entry if none
-       if(!current)
+       if( !current )
                current = first;
        else
 // use next entry if there is a next entry
-       if(current->next)
+       if( current->next )
                current = NEXT;
 // don't change current if there is no next entry
        else
@@ -102,323 +106,57 @@ void UndoStack::dump(FILE *fp)
 {
        fprintf(fp,"UndoStack::dump\n");
        UndoStackItem *current = last;
-       int i = 0;
 // Dump most recent
-       while(current && i < 10)
-       {
-               fprintf(fp,"  %d %p %s %c\n",
-                       i++,
-                       current,
-                       current->get_description(),
-                       current == this->current ? '*' : ' ');
-               current = PREVIOUS;
+       for( int i=0; current && i<32; ++i,current=PREVIOUS ) {
+               fprintf(fp,"%c %2d %14p %-24s %8d %04jx %c\n", current->is_key() ? 'k' : ' ',
+                       i, current, current->get_description(), current->get_size(),
+                       current->get_flags(), current == this->current ? '*' : ' ');
+//char fn[BCSTRLEN]; sprintf(fn,"/tmp/undo%d", i); FILE *fp = fopen(fn,"w");
+//if( fp ) { char *cp=current->get_data(); fwrite(cp,strlen(cp),1,fp); fclose(fp); delete [] cp; }
        }
 }
 
+class undo_record {
+public:
+       int32_t new_ofs;
+       int32_t new_len;
+       int32_t old_len;
+};
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// These difference routines are straight out of the Heroinediff/Heroinepatch
-// utilities.
-
-
-
-
-
-
-// We didn't want to use the diff program because that isn't efficient enough
-// to compress EDL's.  This program also handles binary data.
-
-// Algorithm 1:
-// Search from beginning for first difference.
-// Count everything up to first difference as equal.
-// Determine length of different block by searching for transposition with most
-// similar characters.
-// For every byte in different block, search for occurrence of
-// transposed copy longer than a record header in remaining old buffer.
-// If no match is found, add 1 to the different block length and try the next
-// byte.  Once the most similar match is found, store the changed data leading up to it in the
-// difference record.
-// This can be real slow if there is a large transposition early in the file.
-
-// Algorithm 2:
-// Search backwards from end of both files for last difference.
-// Count all data from last same bytes as a transposition.
-// Search forwards from start of both files until last same bytes
-// for differences.
-
-
-
-
-
-
-
-
-// Minimum size of transposed block.
-// Smaller transpositions should be included in the same difference record.
-#define MIN_TRANS 16
-
-// Format for difference records.
-// The first 4 bytes are the offset of the change.
-// The next 4 bytes are the number of bytes of new data.
-// The next 4 bytes are the number of bytes of old data replaced.
-// The new data follows.
-static void append_record(unsigned char **result,
-       int *result_size,
-       unsigned char *new_data,
-       int new_offset,
-       int new_size,
-       int old_size)
-{
-       if(new_size || old_size)
-       {
-               int record_size = new_size + 12;
-               unsigned char *new_result = new unsigned char[(*result_size) + record_size];
-               memcpy(new_result, (*result), (*result_size));
-               delete [] (*result);
-
-               unsigned char *new_result_ptr = new_result + (*result_size);
-               *(int32_t*)new_result_ptr = new_offset;
-               new_result_ptr += 4;
-               *(int32_t*)new_result_ptr = new_size;
-               new_result_ptr += 4;
-               *(int32_t*)new_result_ptr = old_size;
-               new_result_ptr += 4;
-
-               memcpy(new_result_ptr, new_data, new_size);
-               (*result_size) += record_size;
-               (*result) = new_result;
-       }
-}
-
-
-
-static unsigned char* get_difference_fast(unsigned char *before,
-               int before_len,
-               unsigned char *after,
-               int after_len,
-               int *result_len,
-               int verbose)
+static unsigned char* apply_difference(unsigned char *before, int before_len,
+               unsigned char *patch, int patch_len, int *result_len)
 {
-       unsigned char *result = new unsigned char[4];
-       *result_len = 4;
-
-// Store size of new buffer
-       *(int32_t*)result = after_len;
-
-
-
-// Get last different bytes
-       unsigned char *last_difference_after = after + after_len - 1;
-       unsigned char *last_difference_before = before + before_len - 1;
-       while(1)
-       {
-               if(last_difference_after < after ||
-                       last_difference_before < before) break;
-               if(*last_difference_after != *last_difference_before)
-               {
-                       break;
-               }
-               last_difference_after--;
-               last_difference_before--;
-       }
-       last_difference_after++;
-       last_difference_before++;
-
-
-
-
-
-
-       int done = 0;
-       unsigned char *before_ptr = before;
-       unsigned char *after_ptr = after;
-       unsigned char *before_end = before + before_len;
-       unsigned char *after_end = after + after_len;
-
-// Scan forward for first difference
-       while(!done)
-       {
-               if(before_ptr < before_end &&
-                       after_ptr < after_end)
-               {
-// Both characters equal
-                       if(*before_ptr == *after_ptr &&
-                               before_ptr < last_difference_before &&
-                               after_ptr < last_difference_after)
-                       {
-                               before_ptr++;
-                               after_ptr++;
-                       }
-                       else
-// Characters differ
-                       {
-// Get length of difference.
-                               unsigned char *before_difference_start = before_ptr;
-                               unsigned char *after_difference_start = after_ptr;
-
-
-                               while(*before_ptr != *after_ptr &&
-                                       before_ptr < last_difference_before &&
-                                       after_ptr < last_difference_after)
-                               {
-                                       before_ptr++;
-                                       after_ptr++;
-                               }
-
-// Finished comparing if either pointer hits its last difference
-                               if(before_ptr >= last_difference_before ||
-                                       after_ptr >= last_difference_after)
-                               {
-                                       done = 1;
-                                       before_ptr = last_difference_before;
-                                       after_ptr = last_difference_after;
-                               }
-
-
-
-                               int after_start = after_difference_start - after;
-                               int before_start = before_difference_start - before;
-                               int after_len = after_ptr - after_difference_start;
-                               int before_len = before_ptr - before_difference_start;
-
-                               if(verbose)
-                               {
-                                       char string[1024];
-                                       memcpy(string, after_difference_start, MIN(after_len, 40));
-                                       string[MIN(after_len, 40)] = 0;
-                                       printf("after_offset=0x%x before_offset=0x%x after_size=%d before_size=%d \"%s\"\n",
-                                               after_start,
-                                               before_start,
-                                               after_len,
-                                               before_len,
-                                               string);
-                               }
-
-
-// Create difference record
-                               append_record(&result,
-                                       result_len,
-                                       after_difference_start,
-                                       after_start,
-                                       after_len,
-                                       before_len);
-                       }
+       XMLBuffer xbfr((const char *)patch, patch_len, 0);
+       int32_t len = 0;
+       xbfr.read((char *)&len, sizeof(len));
+       char *result = new char[len];
+       char *rp = result, *bp = (char*)before;
+
+       while( xbfr.itell() < patch_len ) {
+               undo_record urecd;
+               xbfr.read((char*)&urecd, sizeof(urecd));
+               int ofs = rp - result;
+               if( urecd.new_ofs > ofs ) {
+                       int sz = urecd.new_ofs - ofs;
+                       memcpy(rp, bp, sz);
+                       bp += sz;  rp += sz;
                }
-               else
-                       done = 1;
-       }
-       return result;
-}
-
-
-
-
-
-
-
-
-
-static void get_record(unsigned char **patch_ptr,
-       unsigned char *patch_end,
-       unsigned char **after_data,
-       int *after_offset,
-       int *after_size,
-       int *before_size)
-{
-       (*after_data) = 0;
-       if((*patch_ptr) < patch_end)
-       {
-               (*after_offset) = *(int32_t*)(*patch_ptr);
-               (*patch_ptr) += 4;
-               (*after_size) = *(int32_t*)(*patch_ptr);
-               (*patch_ptr) += 4;
-               (*before_size) = *(int32_t*)(*patch_ptr);
-               (*patch_ptr) += 4;
-
-               (*after_data) = (*patch_ptr);
-               (*patch_ptr) += (*after_size);
+               if( urecd.new_len > 0 )
+                       xbfr.read(rp, urecd.new_len);
+               bp += urecd.old_len;  rp += urecd.new_len;
        }
-}
-
 
-
-
-
-static unsigned char* apply_difference(unsigned char *before,
-               int before_len,
-               unsigned char *patch,
-               int patch_len,
-               int *result_len)
-{
-       unsigned char *patch_ptr = patch;
-       *result_len = *(int32_t*)patch_ptr;
-       patch_ptr += 4;
-       unsigned char *patch_end = patch + patch_len;
-       //unsigned char *before_end = before + before_len;
-
-       unsigned char *result = new unsigned char[*result_len];
-       unsigned char *result_ptr = result;
-       unsigned char *result_end = result + *result_len;
-       unsigned char *before_ptr = before;
-
-       int done = 0;
-       while(!done)
-       {
-               unsigned char *after_data;
-               int after_offset;
-               int after_size;
-               int before_size;
-
-               get_record(&patch_ptr,
-                       patch_end,
-                       &after_data,
-                       &after_offset,
-                       &after_size,
-                       &before_size);
-
-               if(after_data)
-               {
-                       int result_offset = result_ptr - result;
-                       if(after_offset > result_offset)
-                       {
-                               int skip_size = after_offset - result_offset;
-                               memcpy(result_ptr, before_ptr, skip_size);
-                               result_ptr += skip_size;
-                               before_ptr += skip_size;
-                       }
-                       memcpy(result_ptr, after_data, after_size);
-                       result_ptr += after_size;
-                       before_ptr += before_size;
-               }
-               else
-               {
 // All data from before_ptr to end of result buffer is identical
-                       if(result_end - result_ptr > 0)
-                               memcpy(result_ptr, before_ptr, result_end - result_ptr);
-                       done = 1;
-               }
+       int sz = bp - (char *)before;
+       if( (before_len-=sz) > 0 ) {
+               memcpy(rp, bp, before_len);
+               rp += before_len;
        }
 
-       return result;
+       *result_len = rp - result;
+       return (unsigned char *)result;
 }
 
-
 UndoStackItem::UndoStackItem()
  : ListItem<UndoStackItem>()
 {
@@ -458,203 +196,271 @@ char* UndoStackItem::get_filename()
 
 const char* UndoStackItem::get_description()
 {
-       if(description)
+       if( description )
                return description;
        else
                return "";
 }
 
-int UndoStackItem::has_data()
+// undo diff
+
+UndoHash::UndoHash(char *txt, int len, UndoHash *nxt)
 {
-       return data_size ? 1 : 0;
+       this->txt = txt;
+       this->len = len;
+       this->nxt = nxt;
+       line[va] = line[vb] = -1;
+       occurs[va] = occurs[vb] = 0;
 }
-
-void UndoStackItem::set_data(char *data)
+UndoHash::~UndoHash()
 {
-       delete [] this->data;
-       this->data = 0;
-       this->data_size = 0;
+}
 
-// Search for key buffer within interval
-       int need_key = 1;
-       UndoStackItem *current = this;
-       this->key = 0;
-       for(int i = 0; i < UNDO_KEY_INTERVAL && current; i++)
-       {
-               if(current->key && current->has_data())
-               {
-                       need_key = 0;
-                       break;
+UndoHashTable::UndoHashTable()
+{
+       for( int i=0; i<hash_sz; ++i ) table[i] = 0;
+       bof = new UndoHash(0, 0, 0);
+       eof = new UndoHash(0, 0, 0);
+}
+UndoHashTable::~UndoHashTable()
+{
+       delete bof;
+       delete eof;
+       for( int i=0; i<hash_sz; ++i ) {
+               for( UndoHash *nxt=0, *hp=table[i]; hp; hp=nxt ) {
+                       nxt = hp->nxt;  delete hp;
                }
-               else
-                       current = PREVIOUS;
        }
+}
 
-       int new_size = strlen(data) + 1;
-
-       if(!need_key)
-       {
-// Reconstruct previous data for difference
-               char *prev_buffer = previous->get_data();
-               int prev_size = prev_buffer ? strlen(prev_buffer) + 1 : 0;
-// Timer timer;
-// timer.update();
-// printf("UndoStackItem::set_data 1\n");
-               this->data = (char*)get_difference_fast((unsigned char*)prev_buffer,
-                       prev_size,
-                       (unsigned char*)data,
-                       new_size,
-                       &this->data_size,
-                       0);
-//printf("UndoStackItem::set_data 2 %jd\n", timer.get_difference());
-
-// Testing
-// FILE *test1 = fopen("/tmp/undo1", "w");
-// fwrite(prev_buffer, prev_size, 1, test1);
-// fclose(test1);
-// FILE *test2 = fopen("/tmp/undo2", "w");
-// fwrite(data, new_size, 1, test2);
-// fclose(test2);
-// FILE *test3 = fopen("/tmp/undo2.diff", "w");
-// fwrite(this->data, this->data_size, 1, test3);
-// fclose(test3);
-//
-//printf("UndoStackItem::set_data 3 %d %d\n", new_size, this->data_size);
-
-// Diff was bigger than original.
-// Happens if a lot of tiny changes happened and the record headers
-// took more space than the changes.
-               if(this->data_size > new_size)
-               {
-                       delete [] this->data;
-                       this->data_size = 0;
-                       need_key = 1;
-               }
-               else
-               {
-// Reconstruct current data from difference
-                       int test_size;
-                       char *test_buffer = (char*)apply_difference((unsigned char*)prev_buffer,
-                               prev_size,
-                               (unsigned char*)this->data,
-                               this->data_size,
-                               &test_size);
-                       if(test_size != new_size ||
-                               memcmp(test_buffer, data, test_size))
-                       {
-// FILE *test1 = fopen("/tmp/undo1", "w");
-// fwrite(prev_buffer, prev_size, 1, test1);
-// fclose(test1);
-// FILE *test2 = fopen("/tmp/undo2", "w");
-// fwrite(data, new_size, 1, test2);
-// fclose(test2);
-// FILE *test3 = fopen("/tmp/undo2.diff", "w");
-// fwrite(this->data, this->data_size, 1, test3);
-// fclose(test3);
-// FILE *test4 = fopen("/tmp/undo3", "w");
-// fwrite(test_buffer, test_size, 1, test4);
-// fclose(test4);
-
-                               printf("UndoStackItem::set_data: incremental undo failed!\n");
-                               need_key = 1;
-                               delete [] this->data;
-                               this->data_size = 0;
-                               this->data = 0;
-                       }
-                       delete [] test_buffer;
-
-               }
-
-
-               delete [] prev_buffer;
+UndoHash *UndoHashTable::add(char *txt, int len)
+{
+       uint8_t *bp = (uint8_t *)txt;
+       int v = 0;
+       for( int i=len; --i>=0; ++bp ) {
+               v = (v<<1) + *bp;
+               v = (v + (v>>hash_sz2)) & hash_sz1;
        }
-
-       if(need_key)
-       {
-               this->key = 1;
-               this->data_size = new_size;
-               this->data = new char[this->data_size];
-               memcpy(this->data, data, this->data_size);
+       UndoHash *hp = table[v];
+       while( hp && strncmp(hp->txt,txt,len) ) hp = hp->nxt;
+       if( !hp ) {
+               hp = new UndoHash(txt, len, table[v]);
+               table[v] = hp;
        }
-       return;
+       return hp;
 }
 
+UndoLine::UndoLine(UndoHash *hash, char *tp)
+{
+       this->txt = tp;  this->len = 0;
+       this->hash = hash;
+       hash->occurs[va] = hash->occurs[vb] = 1;
+}
 
+UndoLine::UndoLine(UndoHashTable *hash, char *txt, int len)
+{
+       this->txt = txt;  this->len = len;
+       this->hash = hash->add(txt, len);
+}
+UndoLine::~UndoLine()
+{
+}
 
-char* UndoStackItem::get_incremental_data()
+int UndoLine::eq(UndoLine *ln)
 {
-       return data;
+       return hash == ln->hash ? 1 : 0;
 }
 
-int UndoStackItem::get_incremental_size()
+void UndoVersion::scan_lines(UndoHashTable *hash, char *sp, char *ep)
 {
-       return data_size;
+       for( int line=1; sp<ep; ++line ) {
+               char *txt = sp;
+               while( sp<ep && *sp++ != '\n' );
+               UndoLine *ln = new UndoLine(hash, txt, sp-txt);
+               ln->hash->line[ver] = line;
+               ++ln->hash->occurs[ver];
+               append(ln);
+       }
 }
 
-int UndoStackItem::get_size()
+
+void UndoStackItem::set_data(char *data)
 {
-       return data_size;
+       delete [] this->data;  this->data = 0;
+       this->data_size = 0;   this->key = 0;
+       int data_size = strlen(data)+1;
+
+       UndoStackItem *current = this;
+// Search for key buffer within interval
+       for( int i=UNDO_KEY_INTERVAL; --i>=0 && current && !current->key; current=PREVIOUS );
+
+       int need_key = current ? 0 : 1;
+// setting need_key = 1 forces all undo data items to key (not diffd)
+//     int need_key = 1;
+       char *prev = 0;  int prev_size = 0;
+       if( !need_key ) {
+#ifdef UNDO_INCREMENTAL
+               prev = previous->get_data();
+               prev_size = prev ? strlen(prev)+1 : 0;
+#else
+               prev = current->data;
+               prev_size = current->data_size;
+#endif
+               int min_size = data_size/16;
+// if the previous is a sizable incremental diff
+               if( previous && !previous->key &&
+                   previous->data_size > min_size ) {
+//  and this minimum diff distance is a lot different, need key
+                       int dist = abs(data_size - prev_size);
+                       if( abs(dist - previous->data_size) > min_size )
+                               need_key = 1;
+               }
+       }
+       if( !need_key ) {
+               int bfr_size = BCTEXTLEN;
+               char *bfr = new char[bfr_size];
+               XMLBuffer xbfr(bfr_size, bfr, 1);
+               xbfr.write((char*)&data_size, sizeof(data_size));
+               UndoHashTable hash;
+               UndoVersion alines(va), blines(vb);
+               char *asp = data, *aep = asp + data_size-1;
+               char *bsp = prev, *bep = bsp + prev_size-1;
+               alines.append(new UndoLine(hash.bof, asp));
+               blines.append(new UndoLine(hash.bof, bsp));
+               alines.scan_lines(&hash, asp, aep);
+               blines.scan_lines(&hash, bsp, bep);
+               int asz = alines.size(), bsz = blines.size();
+// trim matching suffix
+               while( asz > 0 && bsz > 0 && alines[asz-1]->eq(blines[bsz-1]) ) {
+                       aep = alines[--asz]->txt;  alines.remove_object();
+                       bep = blines[--bsz]->txt;  blines.remove_object();
+               }
+// include for matching last item
+               alines.append(new UndoLine(hash.eof, aep));
+               blines.append(new UndoLine(hash.eof, bep));
+               hash.eof->line[va] = asz++;
+               hash.eof->line[vb] = bsz++;
+
+               int ai = 0, bi = 0;
+               while( ai < asz || bi < bsz ) {
+// skip running match
+                       if( alines[ai]->eq(blines[bi]) ) { ++ai;  ++bi;  continue; }
+                       char *adp = alines[ai]->txt, *bdp = blines[bi]->txt;
+// find best unique match
+                       int ma = asz, mb = bsz;
+                       int mx = ma + mb + 1;
+                       for( int ia=ai; ia<asz && ia-ai<mx; ++ia ) {
+                               UndoHash *ah = alines[ia]->hash;
+                               if( ah->occurs[va] != 1 || ah->occurs[vb] != 1 ) continue;
+                               int m = (ah->line[va] - ai) + (ah->line[vb] - bi);
+                               if( m >= mx ) continue;
+                               ma = ah->line[va];  mb = ah->line[vb];  mx = m;
+                       }
+// trim suffix
+                       while( ma > 0 && mb > 0 && alines[ma-1]->eq(blines[mb-1]) ) { --ma;  --mb; }
+                       char *ap = alines[ma]->txt, *bp = blines[mb]->txt;
+                       ai = ma;  bi = mb;
+                       undo_record urecd;
+                       urecd.new_ofs = adp - data;
+                       urecd.new_len = ap - adp;
+                       urecd.old_len = bp - bdp;
+                       xbfr.write((const char*)&urecd, sizeof(urecd));
+                       xbfr.write((const char*)adp, urecd.new_len);
+               }
+
+               int64_t pos = xbfr.otell();
+               if( abs(data_size-prev_size) < pos/2 ) {
+                       this->data = new char[this->data_size = pos];
+                       xbfr.iseek(0);
+                       xbfr.read(this->data, pos);
+#if 1
+                       int test_size = 0;
+                       char *test_data = (char*)apply_difference(
+                               (unsigned char *)prev, prev_size,
+                               (unsigned char *)this->data, this->data_size,
+                               &test_size);
+                       if( test_size != data_size || memcmp(test_data, data, test_size) ) {
+                               printf("UndoStackItem::set_data: *** incremental undo failed!\n");
+                               delete [] this->data;  this->data = 0;  this->data_size = 0;
+                               need_key = 1;
+                       }
+                       delete [] test_data;
+#endif
+               }
+               else
+                       need_key = 1;
+       }
+
+#ifdef UNDO_INCREMENTAL
+       delete [] prev;
+#endif
+       if( need_key ) {
+               this->key = 1;
+               this->data = new char[this->data_size = data_size];
+               memcpy(this->data, data, this->data_size);
+       }
 }
 
 char* UndoStackItem::get_data()
 {
 // Find latest key buffer
        UndoStackItem *current = this;
-       while(current && !current->key)
+       while( current && !current->key )
                current = PREVIOUS;
-       if(!current)
-       {
+       if( !current ) {
                printf("UndoStackItem::get_data: no key buffer found!\n");
                return 0;
        }
 
 // This is the key buffer
-       if(current == this)
-       {
+       if( current == this ) {
                char *result = new char[data_size];
                memcpy(result, data, data_size);
                return result;
        }
-
+#ifdef UNDO_INCREMENTAL
 // Get key buffer
        char *current_data = current->get_data();
-       int current_size = current->get_size();
+       int current_size = current->data_size;
        current = NEXT;
-       while(current)
-       {
+       while( current ) {
 // Do incremental updates
                int new_size;
-               char *new_data = (char*)apply_difference((unsigned char*)current_data,
-                       current_size,
-                       (unsigned char*)current->get_incremental_data(),
-                       current->get_incremental_size(),
+               char *new_data = (char*)apply_difference(
+                       (unsigned char*)current_data, current_size,
+                       (unsigned char*)current->data, current->data_size,
                        &new_size);
                delete [] current_data;
 
-               if(current == this)
+               if( current == this )
                        return new_data;
-               else
-               {
-                       current_data = new_data;
-                       current_size = new_size;
-                       current = NEXT;
-               }
+               current_data = new_data;
+               current_size = new_size;
+               current = NEXT;
        }
-
 // Never hit this object.
        delete [] current_data;
        printf("UndoStackItem::get_data: lost starting object!\n");
        return 0;
+#else
+       int new_size;
+       char *new_data = (char*)apply_difference(
+               (unsigned char*)current->data, current->data_size,
+               (unsigned char*)this->data, this->data_size, &new_size);
+       return new_data;
+#endif
 }
 
-
-
-
 int UndoStackItem::is_key()
 {
        return key;
 }
 
+int UndoStackItem::get_size()
+{
+       return data_size;
+}
+
 void UndoStackItem::set_flags(uint64_t flags)
 {
        this->load_flags = flags;
@@ -675,9 +481,58 @@ void* UndoStackItem::get_creator()
        return creator;
 }
 
+void UndoStackItem::save(FILE *fp)
+{
+       fwrite(&key,1,sizeof(key),fp);
+       fwrite(&load_flags,1,sizeof(load_flags),fp);
+       fwrite(&data_size,1,sizeof(data_size),fp);
+       fwrite(data,1,data_size,fp);
+       for( char *bp=session_filename; *bp; ++bp ) fputc(*bp, fp);
+       fputc(0, fp);
+       for( char *bp=description; *bp; ++bp ) fputc(*bp, fp);
+       fputc(0, fp);
+}
 
+void UndoStackItem::load(FILE *fp)
+{
+       fread(&key,1,sizeof(key),fp);
+       fread(&load_flags,1,sizeof(load_flags),fp);
+       fread(&data_size,1,sizeof(data_size),fp);
+       fread(data=new char[data_size],1,data_size,fp);
+       char filename[BCTEXTLEN], descr[BCTEXTLEN];
+       char *bp = filename, *ep = bp+sizeof(filename)-1;
+       for( int ch; bp<ep && (ch=fgetc(fp))>0; ++bp ) *bp = ch;
+       *bp = 0;
+       session_filename = cstrdup(filename);
+       bp = descr;  ep = bp+sizeof(descr)-1;
+       for( int ch; bp<ep && (ch=fgetc(fp))>0; ++bp ) *bp = ch;
+       *bp = 0;
+       description = cstrdup(descr);
+//printf("read undo key=%d,flags=%jx,data_size=%d,data=%p,file=%s,descr=%s\n",
+// key, load_flags, data_size, data, session_filename, description);
+}
 
+void UndoStack::save(FILE *fp)
+{
+       for( UndoStackItem *item=first; item; item=item->next ) {
+               int is_current = item == current ? 1 : 0;
+               fwrite(&is_current,1,sizeof(is_current),fp);
+               item->save(fp);
+//             if( item == current ) break; // stop at current
+       }
+}
 
-
-
+void UndoStack::load(FILE *fp)
+{
+       while( last ) delete last;
+       current = 0;
+       UndoStackItem *current_item = 0;
+       int is_current = 0;
+       while( fread(&is_current,1,sizeof(is_current),fp) == sizeof(is_current) ) {
+               UndoStackItem *item = push();
+               item->load(fp);
+               if( is_current ) current_item = item;
+       }
+       if( current_item ) current = current_item;
+}