Bugfix #25570
[blender.git] / source / blender / editors / util / editmode_undo.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * The Original Code is Copyright (C) 2004 Blender Foundation
21  * All rights reserved.
22  *
23  * The Original Code is: all of this file.
24  *
25  * Contributor(s): none yet.
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30
31 #include <stdlib.h>
32 #include <string.h>
33 #include <math.h>
34
35 #include "MEM_guardedalloc.h"
36
37 #include "DNA_object_types.h"
38 #include "DNA_screen_types.h"
39
40 #include "BLI_blenlib.h"
41 #include "BLI_dynstr.h"
42 #include "BLI_utildefines.h"
43
44
45 #include "BKE_context.h"
46 #include "BKE_depsgraph.h"
47 #include "BKE_global.h"
48
49 #include "ED_mesh.h"
50
51 #include "UI_interface.h"
52 #include "UI_resources.h"
53
54 /* ***************** generic editmode undo system ********************* */
55 /*
56
57 Add this in your local code:
58
59 void undo_editmode_push(bContext *C, const char *name, 
60                 void * (*getdata)(bContext *C),     // use context to retrieve current editdata
61                 void (*freedata)(void *),                       // pointer to function freeing data
62                 void (*to_editmode)(void *, void *),        // data to editmode conversion
63                 void * (*from_editmode)(void *))      // editmode to data conversion
64                 int  (*validate_undo)(void *, void *))      // check if undo data is still valid
65
66
67 Further exported for UI is:
68
69 void undo_editmode_step(bContext *C, int step);  // undo and redo
70 void undo_editmode_clear(void)                          // free & clear all data
71 void undo_editmode_menu(void)                           // history menu
72
73
74 */
75 /* ********************************************************************* */
76
77 /* ****** XXX ***** */
78 void error(const char *UNUSED(arg)) {}
79 /* ****** XXX ***** */
80
81
82 #define MAXUNDONAME     64
83 typedef struct UndoElem {
84         struct UndoElem *next, *prev;
85         ID id;                  // copy of editmode object ID
86         Object *ob;             // pointer to edited object
87         int type;               // type of edited object
88         void *undodata;
89         uintptr_t undosize;
90         char name[MAXUNDONAME];
91         void * (*getdata)(bContext *C);
92         void (*freedata)(void *);
93         void (*to_editmode)(void *, void *);
94         void * (*from_editmode)(void *);
95         int (*validate_undo)(void *, void *);
96 } UndoElem;
97
98 static ListBase undobase={NULL, NULL};
99 static UndoElem *curundo= NULL;
100
101
102 /* ********************* xtern api calls ************* */
103
104 static void undo_restore(UndoElem *undo, void *editdata)
105 {
106         if(undo) {
107                 undo->to_editmode(undo->undodata, editdata);    
108         }
109 }
110
111 /* name can be a dynamic string */
112 void undo_editmode_push(bContext *C, const char *name, 
113                                                 void * (*getdata)(bContext *C),
114                                                 void (*freedata)(void *), 
115                                                 void (*to_editmode)(void *, void *),  
116                                                 void *(*from_editmode)(void *),
117                                                 int (*validate_undo)(void *, void *))
118 {
119         UndoElem *uel;
120         Object *obedit= CTX_data_edit_object(C);
121         void *editdata;
122         int nr;
123         uintptr_t memused, totmem, maxmem;
124
125         /* at first here was code to prevent an "original" key to be insterted twice
126            this was giving conflicts for example when mesh changed due to keys or apply */
127         
128         /* remove all undos after (also when curundo==NULL) */
129         while(undobase.last != curundo) {
130                 uel= undobase.last;
131                 uel->freedata(uel->undodata);
132                 BLI_freelinkN(&undobase, uel);
133         }
134         
135         /* make new */
136         curundo= uel= MEM_callocN(sizeof(UndoElem), "undo editmode");
137         strncpy(uel->name, name, MAXUNDONAME-1);
138         BLI_addtail(&undobase, uel);
139         
140         uel->getdata= getdata;
141         uel->freedata= freedata;
142         uel->to_editmode= to_editmode;
143         uel->from_editmode= from_editmode;
144         uel->validate_undo= validate_undo;
145         
146         /* limit amount to the maximum amount*/
147         nr= 0;
148         uel= undobase.last;
149         while(uel) {
150                 nr++;
151                 if(nr==U.undosteps) break;
152                 uel= uel->prev;
153         }
154         if(uel) {
155                 while(undobase.first!=uel) {
156                         UndoElem *first= undobase.first;
157                         first->freedata(first->undodata);
158                         BLI_freelinkN(&undobase, first);
159                 }
160         }
161
162         /* copy  */
163         memused= MEM_get_memory_in_use();
164         editdata= getdata(C);
165         curundo->undodata= curundo->from_editmode(editdata);
166         curundo->undosize= MEM_get_memory_in_use() - memused;
167         curundo->ob= obedit;
168         curundo->id= obedit->id;
169         curundo->type= obedit->type;
170
171         if(U.undomemory != 0) {
172                 /* limit to maximum memory (afterwards, we can't know in advance) */
173                 totmem= 0;
174                 maxmem= ((uintptr_t)U.undomemory)*1024*1024;
175
176                 uel= undobase.last;
177                 while(uel && uel->prev) {
178                         totmem+= uel->undosize;
179                         if(totmem>maxmem) break;
180                         uel= uel->prev;
181                 }
182
183                 if(uel) {
184                         if(uel->prev && uel->prev->prev)
185                                 uel= uel->prev;
186
187                         while(undobase.first!=uel) {
188                                 UndoElem *first= undobase.first;
189                                 first->freedata(first->undodata);
190                                 BLI_freelinkN(&undobase, first);
191                         }
192                 }
193         }
194 }
195
196 /* helper to remove clean other objects from undo stack */
197 static void undo_clean_stack(bContext *C)
198 {
199         UndoElem *uel, *next;
200         Object *obedit= CTX_data_edit_object(C);
201         
202         /* global undo changes pointers, so we also allow identical names */
203         /* side effect: when deleting/renaming object and start editing new one with same name */
204         
205         uel= undobase.first; 
206         while(uel) {
207                 void *editdata= uel->getdata(C);
208                 int isvalid= 0;
209                 next= uel->next;
210                 
211                 /* for when objects are converted, renamed, or global undo changes pointers... */
212                 if(uel->type==obedit->type) {
213                         if(strcmp(uel->id.name, obedit->id.name)==0) {
214                                 if(uel->validate_undo==NULL)
215                                         isvalid= 1;
216                                 else if(uel->validate_undo(uel->undodata, editdata))
217                                         isvalid= 1;
218                         }
219                 }
220                 if(isvalid) 
221                         uel->ob= obedit;
222                 else {
223                         if(uel == curundo)
224                                 curundo= NULL;
225
226                         uel->freedata(uel->undodata);
227                         BLI_freelinkN(&undobase, uel);
228                 }
229                 
230                 uel= next;
231         }
232         
233         if(curundo == NULL) curundo= undobase.last;
234 }
235
236 /* 1= an undo, -1 is a redo. we have to make sure 'curundo' remains at current situation */
237 void undo_editmode_step(bContext *C, int step)
238 {
239         Object *obedit= CTX_data_edit_object(C);
240         
241         /* prevent undo to happen on wrong object, stack can be a mix */
242         undo_clean_stack(C);
243         
244         if(step==0) {
245                 undo_restore(curundo, curundo->getdata(C));
246         }
247         else if(step==1) {
248                 
249                 if(curundo==NULL || curundo->prev==NULL) error("No more steps to undo");
250                 else {
251                         if(G.f & G_DEBUG) printf("undo %s\n", curundo->name);
252                         curundo= curundo->prev;
253                         undo_restore(curundo, curundo->getdata(C));
254                 }
255         }
256         else {
257                 /* curundo has to remain current situation! */
258                 
259                 if(curundo==NULL || curundo->next==NULL) error("No more steps to redo");
260                 else {
261                         undo_restore(curundo->next, curundo->getdata(C));
262                         curundo= curundo->next;
263                         if(G.f & G_DEBUG) printf("redo %s\n", curundo->name);
264                 }
265         }
266         
267         /* special case for editmesh, mode must be copied back to the scene */
268         if(obedit->type == OB_MESH) {
269                 EM_selectmode_to_scene(CTX_data_scene(C), obedit);
270         }
271
272         DAG_id_tag_update(&obedit->id, OB_RECALC_DATA);
273
274         /* XXX notifiers */
275 }
276
277 void undo_editmode_clear(void)
278 {
279         UndoElem *uel;
280         
281         uel= undobase.first;
282         while(uel) {
283                 uel->freedata(uel->undodata);
284                 uel= uel->next;
285         }
286         BLI_freelistN(&undobase);
287         curundo= NULL;
288 }
289
290 /* based on index nr it does a restore */
291 static void undo_number(bContext *C, int nr)
292 {
293         UndoElem *uel;
294         int a=1;
295         
296         for(uel= undobase.first; uel; uel= uel->next, a++) {
297                 if(a==nr) break;
298         }
299         curundo= uel;
300         undo_editmode_step(C, 0);
301 }
302
303 void undo_editmode_name(bContext *C, const char *undoname)
304 {
305         UndoElem *uel;
306         
307         for(uel= undobase.last; uel; uel= uel->prev) {
308                 if(strcmp(undoname, uel->name)==0)
309                         break;
310         }
311         if(uel && uel->prev) {
312                 curundo= uel->prev;
313                 undo_editmode_step(C, 0);
314         }
315 }
316
317 /* undoname optionally, if NULL it just checks for existing undo steps */
318 int undo_editmode_valid(const char *undoname)
319 {
320         if(undoname) {
321                 UndoElem *uel;
322                 
323                 for(uel= undobase.last; uel; uel= uel->prev) {
324                         if(strcmp(undoname, uel->name)==0)
325                                 break;
326                 }
327                 return uel != NULL;
328         }
329         return undobase.last != undobase.first;
330 }
331
332 /* ************** for interaction with menu/pullown */
333
334 void undo_editmode_menu(bContext *C)
335 {
336         UndoElem *uel;
337         DynStr *ds= BLI_dynstr_new();
338         short event= 0;
339         char *menu;
340
341         undo_clean_stack(C);    // removes other objects from it
342         
343         BLI_dynstr_append(ds, "Editmode Undo History %t");
344         
345         for(uel= undobase.first; uel; uel= uel->next) {
346                 BLI_dynstr_append(ds, "|");
347                 BLI_dynstr_append(ds, uel->name);
348         }
349         
350         menu= BLI_dynstr_get_cstring(ds);
351         BLI_dynstr_free(ds);
352         
353 // XXX  event= pupmenu_col(menu, 20);
354         MEM_freeN(menu);
355         
356         if(event>0) undo_number(C, event);
357 }
358
359 static void do_editmode_undohistorymenu(bContext *C, void *UNUSED(arg), int event)
360 {
361         Object *obedit= CTX_data_edit_object(C);
362         
363         if(obedit==NULL || event<1) return;
364
365         undo_number(C, event-1);
366         
367 }
368
369 uiBlock *editmode_undohistorymenu(bContext *C, ARegion *ar, void *UNUSED(arg))
370 {
371         uiBlock *block;
372         UndoElem *uel;
373         short yco = 20, menuwidth = 120;
374         short item= 1;
375         
376         undo_clean_stack(C);    // removes other objects from it
377
378         block= uiBeginBlock(C, ar, "view3d_edit_mesh_undohistorymenu", UI_EMBOSSP);
379         uiBlockSetButmFunc(block, do_editmode_undohistorymenu, NULL);
380         
381         for(uel= undobase.first; uel; uel= uel->next, item++) {
382                 if (uel==curundo) uiDefBut(block, SEPR, 0, "",          0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, "");
383                 uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, uel->name, 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, (float)item, "");
384                 if (uel==curundo) uiDefBut(block, SEPR, 0, "",          0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, "");
385         }
386         
387         uiBlockSetDirection(block, UI_RIGHT);
388         uiTextBoundsBlock(block, 60);
389         return block;
390 }
391
392 void *undo_editmode_get_prev(Object *ob)
393 {
394         UndoElem *ue= undobase.last;
395         if(ue && ue->prev && ue->prev->ob==ob) return ue->prev->undodata;
396         return NULL;
397 }