Blender  V2.59
editmode_undo.c
Go to the documentation of this file.
00001 /*
00002  * $Id: editmode_undo.c 37185 2011-06-04 17:03:46Z ton $
00003  *
00004  * ***** BEGIN GPL LICENSE BLOCK *****
00005  *
00006  * This program is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * as published by the Free Software Foundation; either version 2
00009  * of the License, or (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software Foundation,
00018  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  *
00020  * The Original Code is Copyright (C) 2004 Blender Foundation
00021  * All rights reserved.
00022  *
00023  * The Original Code is: all of this file.
00024  *
00025  * Contributor(s): none yet.
00026  *
00027  * ***** END GPL LICENSE BLOCK *****
00028  */
00029 
00036 #include <stdlib.h>
00037 #include <string.h>
00038 #include <math.h>
00039 
00040 #include "MEM_guardedalloc.h"
00041 
00042 #include "DNA_object_types.h"
00043 #include "DNA_screen_types.h"
00044 
00045 #include "BLI_blenlib.h"
00046 #include "BLI_dynstr.h"
00047 #include "BLI_utildefines.h"
00048 
00049 
00050 #include "BKE_context.h"
00051 #include "BKE_depsgraph.h"
00052 #include "BKE_global.h"
00053 
00054 #include "ED_util.h"
00055 #include "ED_mesh.h"
00056 
00057 #include "UI_interface.h"
00058 #include "UI_resources.h"
00059 
00060 #include "util_intern.h"
00061 
00062 /* ***************** generic editmode undo system ********************* */
00063 /*
00064 
00065 Add this in your local code:
00066 
00067 void undo_editmode_push(bContext *C, const char *name, 
00068                 void * (*getdata)(bContext *C),     // use context to retrieve current editdata
00069                 void (*freedata)(void *),                       // pointer to function freeing data
00070                 void (*to_editmode)(void *, void *),        // data to editmode conversion
00071                 void * (*from_editmode)(void *))      // editmode to data conversion
00072                 int  (*validate_undo)(void *, void *))      // check if undo data is still valid
00073 
00074 
00075 Further exported for UI is:
00076 
00077 void undo_editmode_step(bContext *C, int step);  // undo and redo
00078 void undo_editmode_clear(void)                          // free & clear all data
00079 void undo_editmode_menu(void)                           // history menu
00080 
00081 
00082 */
00083 /* ********************************************************************* */
00084 
00085 /* ****** XXX ***** */
00086 static void error(const char *UNUSED(arg)) {}
00087 /* ****** XXX ***** */
00088 
00089 
00090 #define MAXUNDONAME     64
00091 typedef struct UndoElem {
00092         struct UndoElem *next, *prev;
00093         ID id;                  // copy of editmode object ID
00094         Object *ob;             // pointer to edited object
00095         int type;               // type of edited object
00096         void *undodata;
00097         uintptr_t undosize;
00098         char name[MAXUNDONAME];
00099         void * (*getdata)(bContext *C);
00100         void (*freedata)(void *);
00101         void (*to_editmode)(void *, void *);
00102         void * (*from_editmode)(void *);
00103         int (*validate_undo)(void *, void *);
00104 } UndoElem;
00105 
00106 static ListBase undobase={NULL, NULL};
00107 static UndoElem *curundo= NULL;
00108 
00109 
00110 /* ********************* xtern api calls ************* */
00111 
00112 static void undo_restore(UndoElem *undo, void *editdata)
00113 {
00114         if(undo) {
00115                 undo->to_editmode(undo->undodata, editdata);    
00116         }
00117 }
00118 
00119 /* name can be a dynamic string */
00120 void undo_editmode_push(bContext *C, const char *name, 
00121                                                 void * (*getdata)(bContext *C),
00122                                                 void (*freedata)(void *), 
00123                                                 void (*to_editmode)(void *, void *),  
00124                                                 void *(*from_editmode)(void *),
00125                                                 int (*validate_undo)(void *, void *))
00126 {
00127         UndoElem *uel;
00128         Object *obedit= CTX_data_edit_object(C);
00129         void *editdata;
00130         int nr;
00131         uintptr_t memused, totmem, maxmem;
00132 
00133         /* at first here was code to prevent an "original" key to be insterted twice
00134            this was giving conflicts for example when mesh changed due to keys or apply */
00135         
00136         /* remove all undos after (also when curundo==NULL) */
00137         while(undobase.last != curundo) {
00138                 uel= undobase.last;
00139                 uel->freedata(uel->undodata);
00140                 BLI_freelinkN(&undobase, uel);
00141         }
00142         
00143         /* make new */
00144         curundo= uel= MEM_callocN(sizeof(UndoElem), "undo editmode");
00145         strncpy(uel->name, name, MAXUNDONAME-1);
00146         BLI_addtail(&undobase, uel);
00147         
00148         uel->getdata= getdata;
00149         uel->freedata= freedata;
00150         uel->to_editmode= to_editmode;
00151         uel->from_editmode= from_editmode;
00152         uel->validate_undo= validate_undo;
00153         
00154         /* limit amount to the maximum amount*/
00155         nr= 0;
00156         uel= undobase.last;
00157         while(uel) {
00158                 nr++;
00159                 if(nr==U.undosteps) break;
00160                 uel= uel->prev;
00161         }
00162         if(uel) {
00163                 while(undobase.first!=uel) {
00164                         UndoElem *first= undobase.first;
00165                         first->freedata(first->undodata);
00166                         BLI_freelinkN(&undobase, first);
00167                 }
00168         }
00169 
00170         /* copy  */
00171         memused= MEM_get_memory_in_use();
00172         editdata= getdata(C);
00173         curundo->undodata= curundo->from_editmode(editdata);
00174         curundo->undosize= MEM_get_memory_in_use() - memused;
00175         curundo->ob= obedit;
00176         curundo->id= obedit->id;
00177         curundo->type= obedit->type;
00178 
00179         if(U.undomemory != 0) {
00180                 /* limit to maximum memory (afterwards, we can't know in advance) */
00181                 totmem= 0;
00182                 maxmem= ((uintptr_t)U.undomemory)*1024*1024;
00183 
00184                 uel= undobase.last;
00185                 while(uel && uel->prev) {
00186                         totmem+= uel->undosize;
00187                         if(totmem>maxmem) break;
00188                         uel= uel->prev;
00189                 }
00190 
00191                 if(uel) {
00192                         if(uel->prev && uel->prev->prev)
00193                                 uel= uel->prev;
00194 
00195                         while(undobase.first!=uel) {
00196                                 UndoElem *first= undobase.first;
00197                                 first->freedata(first->undodata);
00198                                 BLI_freelinkN(&undobase, first);
00199                         }
00200                 }
00201         }
00202 }
00203 
00204 /* helper to remove clean other objects from undo stack */
00205 static void undo_clean_stack(bContext *C)
00206 {
00207         UndoElem *uel, *next;
00208         Object *obedit= CTX_data_edit_object(C);
00209         
00210         /* global undo changes pointers, so we also allow identical names */
00211         /* side effect: when deleting/renaming object and start editing new one with same name */
00212         
00213         uel= undobase.first; 
00214         while(uel) {
00215                 void *editdata= uel->getdata(C);
00216                 int isvalid= 0;
00217                 next= uel->next;
00218                 
00219                 /* for when objects are converted, renamed, or global undo changes pointers... */
00220                 if(uel->type==obedit->type) {
00221                         if(strcmp(uel->id.name, obedit->id.name)==0) {
00222                                 if(uel->validate_undo==NULL)
00223                                         isvalid= 1;
00224                                 else if(uel->validate_undo(uel->undodata, editdata))
00225                                         isvalid= 1;
00226                         }
00227                 }
00228                 if(isvalid) 
00229                         uel->ob= obedit;
00230                 else {
00231                         if(uel == curundo)
00232                                 curundo= NULL;
00233 
00234                         uel->freedata(uel->undodata);
00235                         BLI_freelinkN(&undobase, uel);
00236                 }
00237                 
00238                 uel= next;
00239         }
00240         
00241         if(curundo == NULL) curundo= undobase.last;
00242 }
00243 
00244 /* 1= an undo, -1 is a redo. we have to make sure 'curundo' remains at current situation */
00245 void undo_editmode_step(bContext *C, int step)
00246 {
00247         Object *obedit= CTX_data_edit_object(C);
00248         
00249         /* prevent undo to happen on wrong object, stack can be a mix */
00250         undo_clean_stack(C);
00251         
00252         if(step==0) {
00253                 undo_restore(curundo, curundo->getdata(C));
00254         }
00255         else if(step==1) {
00256                 
00257                 if(curundo==NULL || curundo->prev==NULL) error("No more steps to undo");
00258                 else {
00259                         if(G.f & G_DEBUG) printf("undo %s\n", curundo->name);
00260                         curundo= curundo->prev;
00261                         undo_restore(curundo, curundo->getdata(C));
00262                 }
00263         }
00264         else {
00265                 /* curundo has to remain current situation! */
00266                 
00267                 if(curundo==NULL || curundo->next==NULL) error("No more steps to redo");
00268                 else {
00269                         undo_restore(curundo->next, curundo->getdata(C));
00270                         curundo= curundo->next;
00271                         if(G.f & G_DEBUG) printf("redo %s\n", curundo->name);
00272                 }
00273         }
00274         
00275         /* special case for editmesh, mode must be copied back to the scene */
00276         if(obedit->type == OB_MESH) {
00277                 EM_selectmode_to_scene(CTX_data_scene(C), obedit);
00278         }
00279 
00280         DAG_id_tag_update(&obedit->id, OB_RECALC_DATA);
00281 
00282         /* XXX notifiers */
00283 }
00284 
00285 void undo_editmode_clear(void)
00286 {
00287         UndoElem *uel;
00288         
00289         uel= undobase.first;
00290         while(uel) {
00291                 uel->freedata(uel->undodata);
00292                 uel= uel->next;
00293         }
00294         BLI_freelistN(&undobase);
00295         curundo= NULL;
00296 }
00297 
00298 /* based on index nr it does a restore */
00299 void undo_editmode_number(bContext *C, int nr)
00300 {
00301         UndoElem *uel;
00302         int a=1;
00303         
00304         for(uel= undobase.first; uel; uel= uel->next, a++) {
00305                 if(a==nr) break;
00306         }
00307         curundo= uel;
00308         undo_editmode_step(C, 0);
00309 }
00310 
00311 void undo_editmode_name(bContext *C, const char *undoname)
00312 {
00313         UndoElem *uel;
00314         
00315         for(uel= undobase.last; uel; uel= uel->prev) {
00316                 if(strcmp(undoname, uel->name)==0)
00317                         break;
00318         }
00319         if(uel && uel->prev) {
00320                 curundo= uel->prev;
00321                 undo_editmode_step(C, 0);
00322         }
00323 }
00324 
00325 /* undoname optionally, if NULL it just checks for existing undo steps */
00326 int undo_editmode_valid(const char *undoname)
00327 {
00328         if(undoname) {
00329                 UndoElem *uel;
00330                 
00331                 for(uel= undobase.last; uel; uel= uel->prev) {
00332                         if(strcmp(undoname, uel->name)==0)
00333                                 break;
00334                 }
00335                 return uel != NULL;
00336         }
00337         return undobase.last != undobase.first;
00338 }
00339 
00340 
00341 /* get name of undo item, return null if no item with this index */
00342 /* if active pointer, set it to 1 if true */
00343 char *undo_editmode_get_name(bContext *C, int nr, int *active)
00344 {
00345         UndoElem *uel;
00346         
00347         /* prevent wrong numbers to be returned */
00348         undo_clean_stack(C);
00349         
00350         if(active) *active= 0;
00351         
00352         uel= BLI_findlink(&undobase, nr);
00353         if(uel) {
00354                 if(active && uel==curundo)
00355                         *active= 1;
00356                 return uel->name;
00357         }
00358         return NULL;
00359 }
00360 
00361 
00362 void *undo_editmode_get_prev(Object *ob)
00363 {
00364         UndoElem *ue= undobase.last;
00365         if(ue && ue->prev && ue->prev->ob==ob) return ue->prev->undodata;
00366         return NULL;
00367 }