Cleanup: doxygen comments
[blender-staging.git] / source / blender / editors / util / editmode_undo.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (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 Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2004 Blender Foundation
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/util/editmode_undo.c
29  *  \ingroup edutil
30  */
31
32 #include <stdlib.h>
33 #include <string.h>
34 #include <math.h>
35
36 #include "MEM_guardedalloc.h"
37
38 #include "DNA_object_types.h"
39
40 #include "BLI_blenlib.h"
41 #include "BLI_utildefines.h"
42
43 #include "BKE_blender_undo.h"
44 #include "BKE_context.h"
45 #include "BKE_depsgraph.h"
46 #include "BKE_global.h"
47
48 #include "ED_util.h"
49 #include "ED_mesh.h"
50
51 #include "util_intern.h"
52
53 /* ****** XXX ***** */
54 static void error(const char *UNUSED(arg)) {}
55 /* ****** XXX ***** */
56
57 typedef struct UndoElem {
58         struct UndoElem *next, *prev;
59         /** copy of edit-mode object ID */
60         ID id;
61         /** pointer to edited object */
62         Object *ob;
63         /** type of edited object */
64         int type;
65         void *undodata;
66         uintptr_t undosize;
67         char name[BKE_UNDO_STR_MAX];
68
69         /** Use context to retrieve current edit-data. */
70         void * (*getdata)(bContext * C);
71         /** Pointer to function freeing data. */
72         void (*freedata)(void *);
73          /** Data to edit-mode conversion. */
74         void (*to_editmode)(void *, void *, void *);
75         /** Edit-mode to data conversion. */
76         void * (*from_editmode)(void *, void *);
77         /** Check if undo data is still valid. */
78         int (*validate_undo)(void *, void *);
79 } UndoElem;
80
81 static ListBase g_undobase = {NULL, NULL};
82 static UndoElem *g_curundo = NULL;
83
84 static void undo_restore(UndoElem *undo, void *editdata, void *obdata)
85 {
86         if (undo) {
87                 undo->to_editmode(undo->undodata, editdata, obdata);
88         }
89 }
90
91 /**
92  * name can be a dynamic string
93  * See #UndoElem for callbacks docs.
94  * */
95 void undo_editmode_push(
96         bContext *C, const char *name,
97         void * (*getdata)(bContext * C),
98         void (*freedata)(void *),
99         void (*to_editmode)(void *, void *, void *),
100         void *(*from_editmode)(void *, void *),
101         int (*validate_undo)(void *, void *))
102 {
103         UndoElem *uel;
104         Object *obedit = CTX_data_edit_object(C);
105         void *editdata;
106         int nr;
107         uintptr_t mem_used, mem_total, mem_max;
108
109         /* at first here was code to prevent an "original" key to be inserted twice
110          * this was giving conflicts for example when mesh changed due to keys or apply */
111         
112         /* remove all undos after (also when g_curundo == NULL) */
113         while (g_undobase.last != g_curundo) {
114                 uel = g_undobase.last;
115                 uel->freedata(uel->undodata);
116                 BLI_freelinkN(&g_undobase, uel);
117         }
118         
119         /* make new */
120         g_curundo = uel = MEM_callocN(sizeof(UndoElem), "undo editmode");
121         BLI_strncpy(uel->name, name, sizeof(uel->name));
122         BLI_addtail(&g_undobase, uel);
123         
124         uel->getdata = getdata;
125         uel->freedata = freedata;
126         uel->to_editmode = to_editmode;
127         uel->from_editmode = from_editmode;
128         uel->validate_undo = validate_undo;
129         
130         /* limit amount to the maximum amount*/
131         nr = 0;
132         uel = g_undobase.last;
133         while (uel) {
134                 nr++;
135                 if (nr == U.undosteps) {
136                         break;
137                 }
138                 uel = uel->prev;
139         }
140         if (uel) {
141                 while (g_undobase.first != uel) {
142                         UndoElem *first = g_undobase.first;
143                         first->freedata(first->undodata);
144                         BLI_freelinkN(&g_undobase, first);
145                 }
146         }
147
148         /* copy  */
149         mem_used = MEM_get_memory_in_use();
150         editdata = getdata(C);
151         g_curundo->undodata = g_curundo->from_editmode(editdata, obedit->data);
152         g_curundo->undosize = MEM_get_memory_in_use() - mem_used;
153         g_curundo->ob = obedit;
154         g_curundo->id = obedit->id;
155         g_curundo->type = obedit->type;
156
157         if (U.undomemory != 0) {
158                 /* limit to maximum memory (afterwards, we can't know in advance) */
159                 mem_total = 0;
160                 mem_max = ((uintptr_t)U.undomemory) * 1024 * 1024;
161
162                 uel = g_undobase.last;
163                 while (uel && uel->prev) {
164                         mem_total += uel->undosize;
165                         if (mem_total > mem_max) {
166                                 break;
167                         }
168                         uel = uel->prev;
169                 }
170
171                 if (uel) {
172                         if (uel->prev && uel->prev->prev) {
173                                 uel = uel->prev;
174                         }
175                         while (g_undobase.first != uel) {
176                                 UndoElem *first = g_undobase.first;
177                                 first->freedata(first->undodata);
178                                 BLI_freelinkN(&g_undobase, first);
179                         }
180                 }
181         }
182 }
183
184 /* helper to remove clean other objects from undo stack */
185 static void undo_clean_stack(bContext *C)
186 {
187         UndoElem *uel;
188         Object *obedit = CTX_data_edit_object(C);
189
190         /* global undo changes pointers, so we also allow identical names */
191         /* side effect: when deleting/renaming object and start editing new one with same name */
192
193         uel = g_undobase.first;
194         while (uel) {
195                 void *editdata = uel->getdata(C);
196                 bool is_valid = false;
197                 UndoElem *uel_next = uel->next;
198
199                 /* for when objects are converted, renamed, or global undo changes pointers... */
200                 if (uel->type == obedit->type) {
201                         if (STREQ(uel->id.name, obedit->id.name)) {
202                                 if (uel->validate_undo == NULL) {
203                                         is_valid = true;
204                                 }
205                                 else if (uel->validate_undo(uel->undodata, editdata)) {
206                                         is_valid = true;
207                                 }
208                         }
209                 }
210                 if (is_valid) {
211                         uel->ob = obedit;
212                 }
213                 else {
214                         if (uel == g_curundo) {
215                                 g_curundo = NULL;
216                         }
217
218                         uel->freedata(uel->undodata);
219                         BLI_freelinkN(&g_undobase, uel);
220                 }
221
222                 uel = uel_next;
223         }
224
225         if (g_curundo == NULL) {
226                 g_curundo = g_undobase.last;
227         }
228 }
229
230 /**
231  * 1 = an undo, -1 is a redo.
232  * we have to make sure 'g_curundo' remains at current situation
233  */
234 void undo_editmode_step(bContext *C, int step)
235 {
236         Object *obedit = CTX_data_edit_object(C);
237
238         /* prevent undo to happen on wrong object, stack can be a mix */
239         undo_clean_stack(C);
240
241         if (step == 0) {
242                 undo_restore(g_curundo, g_curundo->getdata(C), obedit->data);
243         }
244         else if (step == 1) {
245                 if (g_curundo == NULL || g_curundo->prev == NULL) {
246                         error("No more steps to undo");
247                 }
248                 else {
249                         if (G.debug & G_DEBUG) printf("undo %s\n", g_curundo->name);
250                         g_curundo = g_curundo->prev;
251                         undo_restore(g_curundo, g_curundo->getdata(C), obedit->data);
252                 }
253         }
254         else {
255                 /* g_curundo has to remain current situation! */
256                 if (g_curundo == NULL || g_curundo->next == NULL) {
257                         error("No more steps to redo");
258                 }
259                 else {
260                         undo_restore(g_curundo->next, g_curundo->getdata(C), obedit->data);
261                         g_curundo = g_curundo->next;
262                         if (G.debug & G_DEBUG) printf("redo %s\n", g_curundo->name);
263                 }
264         }
265
266         /* special case for editmesh, mode must be copied back to the scene */
267         if (obedit->type == OB_MESH) {
268                 EDBM_selectmode_to_scene(C);
269         }
270
271         DAG_id_tag_update(&obedit->id, OB_RECALC_DATA);
272
273         /* XXX notifiers */
274 }
275
276 void undo_editmode_clear(void)
277 {
278         UndoElem *uel;
279
280         uel = g_undobase.first;
281         while (uel) {
282                 uel->freedata(uel->undodata);
283                 uel = uel->next;
284         }
285         BLI_freelistN(&g_undobase);
286         g_curundo = NULL;
287 }
288
289 /* based on index nr it does a restore */
290 void undo_editmode_number(bContext *C, int nr)
291 {
292         UndoElem *uel;
293         int a = 1;
294
295         for (uel = g_undobase.first; uel; uel = uel->next, a++) {
296                 if (a == nr) {
297                         break;
298                 }
299         }
300         g_curundo = uel;
301         undo_editmode_step(C, 0);
302 }
303
304 void undo_editmode_name(bContext *C, const char *undoname)
305 {
306         UndoElem *uel;
307
308         for (uel = g_undobase.last; uel; uel = uel->prev) {
309                 if (STREQ(undoname, uel->name)) {
310                         break;
311                 }
312         }
313         if (uel && uel->prev) {
314                 g_curundo = uel->prev;
315                 undo_editmode_step(C, 0);
316         }
317 }
318
319 /**
320  * \a undoname is optional, when NULL it just checks for existing undo steps
321  */
322 bool undo_editmode_is_valid(const char *undoname)
323 {
324         if (undoname) {
325                 UndoElem *uel;
326
327                 for (uel = g_undobase.last; uel; uel = uel->prev) {
328                         if (STREQ(undoname, uel->name)) {
329                                 break;
330                         }
331                 }
332                 return uel != NULL;
333         }
334         return g_undobase.last != g_undobase.first;
335 }
336
337
338 /**
339  * Get name of undo item, return null if no item with this index.
340  *
341  * if active pointer, set it to 1 if true
342  */
343 const char *undo_editmode_get_name(bContext *C, int nr, bool *r_active)
344 {
345         UndoElem *uel;
346
347         /* prevent wrong numbers to be returned */
348         undo_clean_stack(C);
349
350         if (r_active) {
351                 *r_active = false;
352         }
353
354         uel = BLI_findlink(&g_undobase, nr);
355         if (uel) {
356                 if (r_active && (uel == g_curundo)) {
357                         *r_active = true;
358                 }
359                 return uel->name;
360         }
361         return NULL;
362 }
363
364
365 void *undo_editmode_get_prev(Object *ob)
366 {
367         UndoElem *ue = g_undobase.last;
368         if (ue && ue->prev && ue->prev->ob == ob) {
369                 return ue->prev->undodata;
370         }
371         return NULL;
372 }