1 /*
2  * Copyright (c) 2004 by Alexander V. Lukyanov (lav@yars.free.net)
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #include <config.h>
20 #include "undo.h"
21 
22 Undo undo;
23 
Undo()24 Undo::Undo()
25 {
26    chain_head=0;
27    chain_ptr=0;
28    chain_tail=0;
29    group_open=0;
30    current_group=0;
31    group_head=0;
32    enabled=true;
33    locked=false;
34    last_change_time=0;
35 
36    glue_changes=true;
37    glue_max_time=5;
38    min_groups=4;
39    max_size=0x10000000;
40 }
Clear()41 void Undo::Clear()
42 {
43    while(chain_head)
44    {
45       Change *to_delete=chain_head;
46       chain_head=chain_head->next;
47       delete to_delete;
48    }
49    chain_tail=chain_ptr=0;
50    current_group=0;
51    group_open=0;
52    delete group_head;
53    group_head=0;
54 }
~Undo()55 Undo::~Undo()
56 {
57    Clear();
58 }
59 
BeginUndoGroup()60 void Undo::BeginUndoGroup()
61 {
62    if(!group_open)
63    {
64       current_group++;
65       group_head=new GroupHead;
66    }
67    group_open++;
68 }
AddChange(Change * c)69 void Undo::AddChange(Change *c)
70 {
71    if(!Enabled())
72    {
73       delete c;
74       return;
75    }
76    // cut undo list at current position, so redo is not possible.
77    while(chain_ptr)
78    {
79       if(chain_ptr==chain_tail)
80 	 chain_tail=chain_ptr->prev;
81       if(chain_ptr==chain_head)
82 	 chain_head=chain_ptr->next;
83       if(chain_ptr->next)
84 	 chain_ptr->next->prev=chain_ptr->prev;
85       if(chain_ptr->prev)
86 	 chain_ptr->prev->next=chain_ptr->next;
87       Change *to_delete=chain_ptr;
88       chain_ptr=chain_ptr->next;
89       delete to_delete;
90    }
91    if(!group_open)
92    {
93       current_group++;
94       group_head=new GroupHead;
95    }
96    c->group=current_group;
97    c->group_head=group_head;
98    group_head=0;
99 
100    bool joined=false;
101 
102    time_t now=time(0);
103    if(glue_changes && chain_tail
104    && now>=last_change_time && now-last_change_time<=glue_max_time)
105       joined=chain_tail->Join(c);
106    last_change_time=now;
107 
108    if(joined)
109    {
110       delete c;
111       return;
112    }
113 
114    c->prev=chain_tail;
115    c->next=0;
116    if(chain_tail)
117       chain_tail->next=c;
118    chain_tail=c;
119    if(!chain_head)
120       chain_head=c;
121 }
EndUndoGroup()122 void Undo::EndUndoGroup()
123 {
124    delete group_head;
125    group_head=0;
126    if(group_open<=0)
127       return;
128    group_open--;
129    CheckSize();
130 }
131 
Change(type_t t,const char * l,num ls,const char * r,num rs)132 Undo::Change::Change(type_t t,const char *l,num ls,const char *r,num rs)
133 {
134    left=0;
135    left_size=0;
136    right=0;
137    right_size=0;
138 
139    type=t;
140    pos=CurrentPos;
141    old_modified=modified;
142 
143    next=prev=0;
144 
145    if(ls>0)
146    {
147       left=(char*)malloc(ls);
148       if(!left)
149 	 return;
150    }
151    if(rs>0)
152    {
153       right=(char*)malloc(rs);
154       if(!right)
155       {
156 	 free(left);
157 	 left=0;
158 	 return;
159       }
160    }
161    left_size=ls;
162    right_size=rs;
163    if(ls>0)
164       memcpy(left,l,ls);
165    if(rs>0)
166       memcpy(right,r,rs);
167 }
168 
UndoGroup()169 void Undo::UndoGroup()
170 {
171    if(!chain_tail)
172       return;
173    if(!chain_ptr)
174       chain_ptr=chain_tail;
175    else
176    {
177       if(!chain_ptr->prev)
178 	 return;
179       chain_ptr=chain_ptr->prev;
180    }
181    unsigned g=chain_ptr->group;
182    locked=true;
183    for(;;)
184    {
185       chain_ptr->Undo();
186       if(chain_ptr->prev==0 || chain_ptr->prev->group!=g)
187 	 break;
188       chain_ptr=chain_ptr->prev;
189    }
190    locked=false;
191 }
RedoGroup()192 void Undo::RedoGroup()
193 {
194    if(!chain_ptr)
195       return;
196    unsigned g=chain_ptr->group;
197    locked=true;
198    for(;;)
199    {
200       chain_ptr->Redo();
201       chain_ptr=chain_ptr->next;
202       if(!chain_ptr || chain_ptr->group!=g)
203 	 break;
204    }
205    locked=false;
206 }
UndoOne()207 void Undo::UndoOne()
208 {
209    if(!chain_tail)
210       return;
211    if(!chain_ptr)
212       chain_ptr=chain_tail;
213    else
214    {
215       if(!chain_ptr->prev)
216 	 return;
217       chain_ptr=chain_ptr->prev;
218    }
219    locked=true;
220    chain_ptr->Undo();
221    if(!chain_ptr->group_head)
222       SetStdCol();
223    locked=false;
224 }
RedoOne()225 void Undo::RedoOne()
226 {
227    if(!chain_ptr)
228       return;
229    locked=true;
230    chain_ptr->Redo();
231    chain_ptr=chain_ptr->next;
232    locked=false;
233 }
234 
Undo()235 void Undo::Change::Undo()
236 {
237    switch(type)
238    {
239    case DELETE:
240       CurrentPos=pos-left_size;
241       InsertBlock(left,left_size,right,right_size);
242       break;
243    case INSERT:
244       CurrentPos=pos+left_size;
245       DeleteBlock(left_size,right_size);
246       break;
247    case REPLACE:
248       CurrentPos=pos;
249       ReplaceBlock(left,left_size);
250       break;
251    }
252    modified=old_modified;
253    if(group_head)
254       group_head->Undo();
255 }
Redo()256 void Undo::Change::Redo()
257 {
258    CurrentPos=pos;
259    switch(type)
260    {
261    case DELETE:
262       DeleteBlock(left_size,right_size);
263       break;
264    case INSERT:
265       InsertBlock(left,left_size,right,right_size);
266       break;
267    case REPLACE:
268       ReplaceBlock(right,right_size);
269       CurrentPos+=right_size;
270       break;
271    }
272 }
mappend(char ** buf,num * size,const char * add,num add_size)273 static bool mappend(char **buf,num *size,const char *add,num add_size)
274 {
275    if(add_size<=0)
276       return true;
277    char *newbuf=(char*)realloc(*buf,*size+add_size);
278    if(!newbuf)
279       return false;
280    *buf=newbuf;
281    memmove(*buf+*size,add,add_size);
282    *size+=add_size;
283    return true;
284 }
mprepend(char ** buf,num * size,const char * add,num add_size)285 static bool mprepend(char **buf,num *size,const char *add,num add_size)
286 {
287    if(add_size<=0)
288       return true;
289    char *newbuf=(char*)realloc(*buf,*size+add_size);
290    if(!newbuf)
291       return false;
292    *buf=newbuf;
293    memmove(*buf+add_size,*buf,*size);
294    memmove(*buf,add,add_size);
295    *size+=add_size;
296    return true;
297 }
Join(const Change * c)298 bool Undo::Change::Join(const Change *c)
299 {
300    if(c->type!=type)
301       return false;
302    if(c->group_head && c->group_head->pos!=c->pos)
303       return false;
304    switch(type)
305    {
306    case DELETE:
307       if(pos-left_size!=c->pos)
308 	 return false;
309       if(!mappend(&right,&right_size,c->right,c->right_size))
310 	 return false;
311       if(!mprepend(&left,&left_size,c->left,c->left_size))
312       {
313 	 right_size-=c->right_size;
314 	 return false;
315       }
316       break;
317    case INSERT:
318       if(pos+left_size!=c->pos)
319 	 return false;
320       if(!mappend(&left,&left_size,c->left,c->left_size))
321 	 return false;
322       if(!mprepend(&right,&right_size,c->right,c->right_size))
323       {
324 	 left_size-=c->left_size;
325 	 return false;
326       }
327       break;
328    case REPLACE:
329       if(pos+right_size!=c->pos)
330 	 return false;
331       if(!mappend(&right,&right_size,c->right,c->right_size))
332 	 return false;
333       if(!mappend(&left,&left_size,c->left,c->left_size))
334       {
335 	 right_size-=c->right_size;
336 	 return false;
337       }
338       break;
339    }
340    return true;
341 }
342 
CheckSize()343 void Undo::CheckSize()
344 {
345    Change *scan=0;
346    if(chain_ptr)
347       scan=chain_ptr->prev;
348    else
349       scan=chain_tail;
350    if(!scan)
351       return;
352    unsigned g=scan->group;
353    int count=1;
354    num size=0;
355    while(scan)
356    {
357       size+=scan->GetSize();
358       scan=scan->prev;
359       if(!scan)
360 	 break;
361       if(scan->group!=g)
362       {
363 	 g=scan->group;
364 	 count++;
365 	 if(size>=max_size && count>min_groups)
366 	 {
367 	    // cut the undo list.
368 	    scan->next->prev=0;
369 	    chain_head=scan->next;
370 	    while(scan)
371 	    {
372 	       Change *to_delete=scan;
373 	       scan=scan->prev;
374 	       delete to_delete;
375 	    }
376 	    break;
377 	 }
378       }
379    }
380 }
381 
GroupHead()382 Undo::GroupHead::GroupHead()
383 {
384    pos=CurrentPos;
385    stdcol=::stdcol;
386    block_begin=BlockBegin;
387    block_end=BlockEnd;
388    block_hide=hide;
389 }
Undo()390 void Undo::GroupHead::Undo()
391 {
392    CurrentPos=pos;
393    ::stdcol=stdcol;
394    BlockBegin=block_begin;
395    BlockEnd=block_end;
396    hide=block_hide;
397 }
398 
FileSaved()399 void Undo::FileSaved()
400 {
401    // set all undo/redo versions as modified.
402    for(Change *scan=chain_head; scan; scan=scan->next)
403 	scan->old_modified=true;
404 }
405