Blender V2.61 - r43446

editmode_undo.c

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