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