From 651b8fb14eb6ee5cbfa98bffe80a966a0753b14e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 19 Mar 2018 14:17:59 +0100 Subject: Undo: unified undo system w/ linear history - Use a single undo history for all operations. - UndoType's are registered and poll the context to check if they should be used when performing an undo push. - Mode switching is used to ensure the state is correct before undo data is restored. - Some undo types accumulate changes (image & text editing) others store the state multiple times (with de-duplication). This is supported by checking UndoStack.mode `ACCUMULATE` / `STORE`. - Each undo step stores ID datablocks they use with utilities to help manage restoring correct ID's. Needed since global undo is now mixed with other modes undo. - Currently performs each undo step when going up/down history Previously this wasn't done, making history fail in some cases. This can be optimized to skip some combinations of undo steps. grease-pencil is an exception which has not been updated since it integrates undo into the draw-session. See D3113 --- build_files/cmake/macros.cmake | 3 + source/blender/blenkernel/BKE_blender_undo.h | 17 +- source/blender/blenkernel/BKE_main.h | 6 +- source/blender/blenkernel/BKE_pointcache.h | 6 +- source/blender/blenkernel/BKE_undo_system.h | 187 +++++ source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/blender_undo.c | 303 +------- source/blender/blenkernel/intern/library.c | 3 + source/blender/blenkernel/intern/undo_system.c | 795 +++++++++++++++++++++ source/blender/blenloader/BLO_readfile.h | 2 + source/blender/blenloader/BLO_undofile.h | 14 +- source/blender/blenloader/intern/readfile.c | 3 +- source/blender/blenloader/intern/undofile.c | 78 ++ .../blender/editors/armature/editarmature_undo.c | 119 ++- source/blender/editors/curve/editcurve.c | 3 + source/blender/editors/curve/editcurve_undo.c | 160 ++++- source/blender/editors/curve/editfont_undo.c | 122 +++- source/blender/editors/include/ED_armature.h | 8 +- source/blender/editors/include/ED_curve.h | 8 +- source/blender/editors/include/ED_lattice.h | 4 +- source/blender/editors/include/ED_mball.h | 4 +- source/blender/editors/include/ED_mesh.h | 6 +- source/blender/editors/include/ED_object.h | 1 + source/blender/editors/include/ED_paint.h | 33 +- source/blender/editors/include/ED_particle.h | 11 +- source/blender/editors/include/ED_sculpt.h | 6 + source/blender/editors/include/ED_text.h | 7 +- source/blender/editors/include/ED_util.h | 17 +- source/blender/editors/lattice/editlattice_undo.c | 120 +++- source/blender/editors/mesh/editmesh_undo.c | 207 +++++- source/blender/editors/metaball/editmball_undo.c | 116 ++- source/blender/editors/object/object_edit.c | 1 + source/blender/editors/object/object_modes.c | 40 ++ source/blender/editors/physics/particle_edit.c | 7 +- .../blender/editors/physics/particle_edit_undo.c | 266 ++++--- source/blender/editors/physics/particle_object.c | 8 +- source/blender/editors/physics/physics_intern.h | 1 - source/blender/editors/render/render_internal.c | 5 +- source/blender/editors/sculpt_paint/CMakeLists.txt | 1 - source/blender/editors/sculpt_paint/paint_curve.c | 22 +- .../editors/sculpt_paint/paint_curve_undo.c | 162 +++-- source/blender/editors/sculpt_paint/paint_image.c | 15 +- .../blender/editors/sculpt_paint/paint_image_2d.c | 6 +- .../editors/sculpt_paint/paint_image_proj.c | 5 +- .../editors/sculpt_paint/paint_image_undo.c | 161 ++++- source/blender/editors/sculpt_paint/paint_intern.h | 10 +- source/blender/editors/sculpt_paint/paint_undo.c | 410 ----------- .../blender/editors/sculpt_paint/sculpt_intern.h | 2 + source/blender/editors/sculpt_paint/sculpt_undo.c | 171 ++++- source/blender/editors/space_image/image_ops.c | 5 +- source/blender/editors/space_text/CMakeLists.txt | 1 + source/blender/editors/space_text/text_ops.c | 70 +- source/blender/editors/space_text/text_undo.c | 170 +++++ source/blender/editors/util/CMakeLists.txt | 3 +- source/blender/editors/util/ed_util.c | 17 +- source/blender/editors/util/editmode_undo.c | 372 ---------- source/blender/editors/util/memfile_undo.c | 149 ++++ source/blender/editors/util/undo.c | 319 ++------- source/blender/editors/util/undo_system_types.c | 74 ++ source/blender/editors/util/util_intern.h | 14 +- source/blender/makesdna/DNA_windowmanager_types.h | 4 +- source/blender/windowmanager/WM_undo.h | 0 source/blender/windowmanager/intern/wm.c | 8 +- source/blender/windowmanager/intern/wm_files.c | 21 +- source/blender/windowmanager/intern/wm_init_exit.c | 19 +- source/creator/creator_signals.c | 40 +- 66 files changed, 3037 insertions(+), 1913 deletions(-) create mode 100644 source/blender/blenkernel/BKE_undo_system.h create mode 100644 source/blender/blenkernel/intern/undo_system.c delete mode 100644 source/blender/editors/sculpt_paint/paint_undo.c create mode 100644 source/blender/editors/space_text/text_undo.c delete mode 100644 source/blender/editors/util/editmode_undo.c create mode 100644 source/blender/editors/util/memfile_undo.c create mode 100644 source/blender/editors/util/undo_system_types.c create mode 100644 source/blender/windowmanager/WM_undo.h diff --git a/build_files/cmake/macros.cmake b/build_files/cmake/macros.cmake index f0cff75c417..3f0868229e2 100644 --- a/build_files/cmake/macros.cmake +++ b/build_files/cmake/macros.cmake @@ -559,6 +559,9 @@ function(SETUP_BLENDER_SORTED_LIBS) set(BLENDER_SORTED_LIBS bf_windowmanager + # needed twice because of text undo + bf_editor_util + bf_editor_space_api bf_editor_space_action bf_editor_space_buttons diff --git a/source/blender/blenkernel/BKE_blender_undo.h b/source/blender/blenkernel/BKE_blender_undo.h index 84a6d07be7d..a96f8af1fdb 100644 --- a/source/blender/blenkernel/BKE_blender_undo.h +++ b/source/blender/blenkernel/BKE_blender_undo.h @@ -31,22 +31,13 @@ extern "C" { struct bContext; struct Scene; struct Main; +struct MemFileUndoData; #define BKE_UNDO_STR_MAX 64 -/* global undo */ -extern void BKE_undo_write(struct bContext *C, const char *name); -extern void BKE_undo_step(struct bContext *C, int step); -extern void BKE_undo_name(struct bContext *C, const char *name); -extern bool BKE_undo_is_valid(const char *name); -extern void BKE_undo_reset(void); -extern void BKE_undo_number(struct bContext *C, int nr); -extern const char *BKE_undo_get_name(int nr, bool *r_active); -extern const char *BKE_undo_get_name_last(void); -extern bool BKE_undo_save_file(const char *filename); -extern struct Main *BKE_undo_get_main(struct Scene **r_scene); - -extern void BKE_undo_callback_wm_kill_jobs_set(void (*callback)(struct bContext *C)); +struct MemFileUndoData *BKE_memfile_undo_encode(struct Main *bmain, struct MemFileUndoData *mfu_prev); +bool BKE_memfile_undo_decode(struct MemFileUndoData *mfu, struct bContext *C); +void BKE_memfile_undo_free(struct MemFileUndoData *mfu); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_main.h b/source/blender/blenkernel/BKE_main.h index d8318bfcf5d..e224155726f 100644 --- a/source/blender/blenkernel/BKE_main.h +++ b/source/blender/blenkernel/BKE_main.h @@ -84,10 +84,12 @@ typedef struct Main { short minversionfile, minsubversionfile; uint64_t build_commit_timestamp; /* commit's timestamp from buildinfo */ char build_hash[16]; /* hash from buildinfo */ - short recovered; /* indicate the main->name (file) is the recovered one */ + char recovered; /* indicate the main->name (file) is the recovered one */ + /** All current ID's exist in the last memfile undo step. */ + char is_memfile_undo_written; BlendThumbnail *blen_thumb; - + struct Library *curlib; ListBase scene; ListBase library; diff --git a/source/blender/blenkernel/BKE_pointcache.h b/source/blender/blenkernel/BKE_pointcache.h index f31ae715539..cc60df1b2d6 100644 --- a/source/blender/blenkernel/BKE_pointcache.h +++ b/source/blender/blenkernel/BKE_pointcache.h @@ -227,7 +227,6 @@ typedef struct PTCacheEditPoint { } PTCacheEditPoint; typedef struct PTCacheUndo { - struct PTCacheUndo *next, *prev; struct PTCacheEditPoint *points; /* particles stuff */ @@ -240,12 +239,11 @@ typedef struct PTCacheUndo { struct ListBase mem_cache; int totpoint; - char name[64]; + + size_t undo_size; } PTCacheUndo; typedef struct PTCacheEdit { - ListBase undo; - struct PTCacheUndo *curundo; PTCacheEditPoint *points; struct PTCacheID pid; diff --git a/source/blender/blenkernel/BKE_undo_system.h b/source/blender/blenkernel/BKE_undo_system.h new file mode 100644 index 00000000000..6072ecfae4a --- /dev/null +++ b/source/blender/blenkernel/BKE_undo_system.h @@ -0,0 +1,187 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ +#ifndef __BKE_UNDO_SYSTEM_H__ +#define __BKE_UNDO_SYSTEM_H__ + +/** \file BKE_undo_system.h + * \ingroup bke + */ + +struct Main; +struct UndoStep; +struct bContext; + +/* ID's */ +struct Mesh; +struct Object; +struct Scene; +struct Text; + +#include "DNA_ID.h" +#include "DNA_listBase.h" + +typedef struct UndoRefID { struct ID *ptr; char name[MAX_ID_NAME]; } UndoRefID; +/* UndoRefID_Mesh & friends. */ +#define UNDO_REF_ID_TYPE(ptr_ty) \ + typedef struct UndoRefID_##ptr_ty { struct ptr_ty *ptr; char name[MAX_ID_NAME]; } UndoRefID_##ptr_ty +UNDO_REF_ID_TYPE(Mesh); +UNDO_REF_ID_TYPE(Object); +UNDO_REF_ID_TYPE(Scene); +UNDO_REF_ID_TYPE(Text); + +typedef struct UndoStack { + ListBase steps; + struct UndoStep *step_active; + + /** + * Some undo systems require begin/end, see: #UndoType.step_encode_init + * + * \note This is not included in the 'steps' list. + * That is done once end is called. + */ + struct UndoStep *step_init; +} UndoStack; + + +typedef struct UndoStep { + struct UndoStep *next, *prev; + char name[64]; + const struct UndoType *type; + /** Size in bytes of all data in step (not including the step). */ + size_t data_size; + /** Users should never see this step (only use for internal consistency). */ + bool skip; + /* Over alloc 'type->struct_size'. */ +} UndoStep; + +typedef enum eUndoTypeMode { + /** + * Each undo step stores a version of the state. + * This means we can simply load in a previous state at any time. + */ + BKE_UNDOTYPE_MODE_STORE = 1, + /** + * Each undo step is a series of edits. + * This means to change states we need to apply each edit. + * It also means the 'step_decode' callback needs to detect the difference between undo and redo. + * (Currently used for text edit and image & sculpt painting). + */ + BKE_UNDOTYPE_MODE_ACCUMULATE = 2, +} eUndoTypeMode; + +typedef void (*UndoTypeForEachIDRefFn)(void *user_data, struct UndoRefID *id_ref); + +typedef struct UndoType { + struct UndoType *next, *prev; + /** Only for debugging. */ + const char *name; + + /** + * When NULL, we don't consider this undo type for context checks. + * Operators must explicitly set the undo type and handle adding the undo step. + * This is needed when tools operate on data which isn't the primary mode (eg, paint-curve in sculpt mode). + */ + bool (*poll)(struct bContext *C); + + /** + * None of these callbacks manage list add/removal. + * + * Note that 'step_encode_init' is optional, + * some undo types need to perform operatons before undo push finishes. + */ + void (*step_encode_init)(struct bContext *C, UndoStep *us); + + bool (*step_encode)(struct bContext *C, UndoStep *us); + void (*step_decode)(struct bContext *C, UndoStep *us, int dir); + + /** + * \note When freeing all steps, + * free from the last since #MemFileUndoType will merge with the next undo type in the list. */ + void (*step_free)(UndoStep *us); + + void (*step_foreach_ID_ref)(UndoStep *us, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data); + + eUndoTypeMode mode; + bool use_context; + + int step_size; +} UndoType; + +/* expose since we need to perform operations on spesific undo types (rarely). */ +extern const UndoType *BKE_UNDOSYS_TYPE_MEMFILE; +extern const UndoType *BKE_UNDOSYS_TYPE_IMAGE; +extern const UndoType *BKE_UNDOSYS_TYPE_SCULPT; +extern const UndoType *BKE_UNDOSYS_TYPE_PARTICLE; +extern const UndoType *BKE_UNDOSYS_TYPE_PAINTCURVE; + +UndoStack *BKE_undosys_stack_create(void); +void BKE_undosys_stack_destroy(UndoStack *ustack); +void BKE_undosys_stack_clear(UndoStack *ustack); +bool BKE_undosys_stack_has_undo(UndoStack *ustack, const char *name); +void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain); +UndoStep *BKE_undosys_stack_active_with_type(UndoStack *ustack, const UndoType *ut); +UndoStep *BKE_undosys_stack_init_or_active_with_type(UndoStack *ustack, const UndoType *ut); +void BKE_undosys_stack_limit_steps_and_memory(UndoStack *ustack, int steps, size_t memory_limit); + +/* Only some UndoType's require init. */ +void BKE_undosys_step_push_init_with_type(UndoStack *ustack, struct bContext *C, const char *name, const UndoType *ut); +void BKE_undosys_step_push_init(UndoStack *ustack, struct bContext *C, const char *name); + +bool BKE_undosys_step_push_with_type(UndoStack *ustack, struct bContext *C, const char *name, const UndoType *ut); +bool BKE_undosys_step_push(UndoStack *ustack, struct bContext *C, const char *name); + +UndoStep *BKE_undosys_step_find_by_name_with_type(UndoStack *ustack, const char *name, const UndoType *ut); +UndoStep *BKE_undosys_step_find_by_name(UndoStack *ustack, const char *name); + +bool BKE_undosys_step_undo_with_data_ex(UndoStack *ustack, struct bContext *C, UndoStep *us, bool use_skip); +bool BKE_undosys_step_undo_with_data(UndoStack *ustack, struct bContext *C, UndoStep *us); +bool BKE_undosys_step_undo(UndoStack *ustack, struct bContext *C); + +bool BKE_undosys_step_redo_with_data_ex(UndoStack *ustack, struct bContext *C, UndoStep *us, bool use_skip); +bool BKE_undosys_step_redo_with_data(UndoStack *ustack, struct bContext *C, UndoStep *us); +bool BKE_undosys_step_redo(UndoStack *ustack, struct bContext *C); + +bool BKE_undosys_step_load_data(UndoStack *ustack, struct bContext *C, UndoStep *us); + +bool BKE_undosys_step_undo_compat_only(UndoStack *ustack, struct bContext *C, int step); +void BKE_undosys_step_undo_from_index(UndoStack *ustack, struct bContext *C, int index); +UndoStep *BKE_undosys_step_same_type_next(UndoStep *us); +UndoStep *BKE_undosys_step_same_type_prev(UndoStep *us); + +/* Type System */ +UndoType *BKE_undosys_type_append(void (*undosys_fn)(UndoType *)); +void BKE_undosys_type_free_all(void); + +/* ID Accessor */ +#if 0 /* functionality is only used internally for now. */ +void BKE_undosys_foreach_ID_ref(UndoStack *ustack, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data); +#endif + +/* Use when the undo step stores many arbitrary pointers. */ +struct UndoIDPtrMap; +struct UndoIDPtrMap *BKE_undosys_ID_map_create(void); +void BKE_undosys_ID_map_destroy(struct UndoIDPtrMap *map); +void BKE_undosys_ID_map_add(struct UndoIDPtrMap *map, ID *id); +struct ID *BKE_undosys_ID_map_lookup(const struct UndoIDPtrMap *map, const struct ID *id_src); +void BKE_undosys_ID_map_foreach_ID_ref( + struct UndoIDPtrMap *map, + UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data); + +#endif /* __BKE_UNDO_SYSTEM_H__ */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 25954f277b8..d789671ab24 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -188,6 +188,7 @@ set(SRC intern/tracking_solver.c intern/tracking_stabilize.c intern/tracking_util.c + intern/undo_system.c intern/unit.c intern/world.c intern/writeavi.c @@ -296,6 +297,7 @@ set(SRC BKE_text.h BKE_texture.h BKE_tracking.h + BKE_undo_system.h BKE_unit.h BKE_world.h BKE_writeavi.h diff --git a/source/blender/blenkernel/intern/blender_undo.c b/source/blender/blenkernel/intern/blender_undo.c index 4efca5bcc41..6b31c8c96f9 100644 --- a/source/blender/blenkernel/intern/blender_undo.c +++ b/source/blender/blenkernel/intern/blender_undo.c @@ -42,28 +42,19 @@ #include "DNA_scene_types.h" -#include "BLI_fileops.h" -#include "BLI_listbase.h" #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_utildefines.h" -#include "IMB_imbuf.h" -#include "IMB_moviecache.h" - #include "BKE_blender_undo.h" /* own include */ #include "BKE_blendfile.h" #include "BKE_appdir.h" -#include "BKE_brush.h" #include "BKE_context.h" #include "BKE_depsgraph.h" #include "BKE_global.h" -#include "BKE_image.h" #include "BKE_main.h" -#include "RE_pipeline.h" #include "BLO_undofile.h" -#include "BLO_readfile.h" #include "BLO_writefile.h" /* -------------------------------------------------------------------- */ @@ -73,46 +64,21 @@ #define UNDO_DISK 0 -typedef struct UndoElem { - struct UndoElem *next, *prev; - /* Only for 'UNDO_DISK' */ - char filename[FILE_MAX]; - char name[BKE_UNDO_STR_MAX]; - MemFile memfile; - size_t undo_size; -} UndoElem; - -static ListBase undobase = {NULL, NULL}; -static UndoElem *curundo = NULL; - -/** - * Avoid bad-level call to #WM_jobs_kill_all_except() - */ -static void (*undo_wm_job_kill_callback)(struct bContext *C) = NULL; - -void BKE_undo_callback_wm_kill_jobs_set(void (*callback)(struct bContext *C)) -{ - undo_wm_job_kill_callback = callback; -} - -static int read_undosave(bContext *C, UndoElem *uel) +bool BKE_memfile_undo_decode(MemFileUndoData *mfu, bContext *C) { char mainstr[sizeof(G.main->name)]; int success = 0, fileflags; - /* This is needed so undoing/redoing doesn't crash with threaded previews going */ - undo_wm_job_kill_callback(C); - BLI_strncpy(mainstr, G.main->name, sizeof(mainstr)); /* temporal store */ fileflags = G.fileflags; G.fileflags |= G_FILE_NO_UI; if (UNDO_DISK) { - success = (BKE_blendfile_read(C, uel->filename, NULL, 0) != BKE_BLENDFILE_READ_FAIL); + success = (BKE_blendfile_read(C, mfu->filename, NULL, 0) != BKE_BLENDFILE_READ_FAIL); } else { - success = BKE_blendfile_read_from_memfile(C, &uel->memfile, NULL, 0); + success = BKE_blendfile_read_from_memfile(C, &mfu->memfile, NULL, 0); } /* restore */ @@ -127,51 +93,9 @@ static int read_undosave(bContext *C, UndoElem *uel) return success; } -/* name can be a dynamic string */ -void BKE_undo_write(bContext *C, const char *name) +MemFileUndoData *BKE_memfile_undo_encode(Main *bmain, MemFileUndoData *mfu_prev) { - int nr /*, success */ /* UNUSED */; - UndoElem *uel; - - if ((U.uiflag & USER_GLOBALUNDO) == 0) { - return; - } - - if (U.undosteps == 0) { - return; - } - - /* remove all undos after (also when curundo == NULL) */ - while (undobase.last != curundo) { - uel = undobase.last; - BLI_remlink(&undobase, uel); - BLO_memfile_free(&uel->memfile); - MEM_freeN(uel); - } - - /* make new */ - curundo = uel = MEM_callocN(sizeof(UndoElem), "undo file"); - BLI_strncpy(uel->name, name, sizeof(uel->name)); - BLI_addtail(&undobase, uel); - - /* and limit amount to the maximum */ - nr = 0; - uel = undobase.last; - while (uel) { - nr++; - if (nr == U.undosteps) break; - uel = uel->prev; - } - if (uel) { - while (undobase.first != uel) { - UndoElem *first = undobase.first; - BLI_remlink(&undobase, first); - /* the merge is because of compression */ - BLO_memfile_merge(&first->memfile, &first->next->memfile); - MEM_freeN(first); - } - } - + MemFileUndoData *mfu = MEM_callocN(sizeof(MemFileUndoData), __func__); /* disk save version */ if (UNDO_DISK) { @@ -187,222 +111,25 @@ void BKE_undo_write(bContext *C, const char *name) BLI_snprintf(numstr, sizeof(numstr), "%d.blend", counter); BLI_make_file_string("/", filename, BKE_tempdir_session(), numstr); - /* success = */ /* UNUSED */ BLO_write_file(CTX_data_main(C), filename, fileflags, NULL, NULL); + /* success = */ /* UNUSED */ BLO_write_file(bmain, filename, fileflags, NULL, NULL); - BLI_strncpy(curundo->filename, filename, sizeof(curundo->filename)); + BLI_strncpy(mfu->filename, filename, sizeof(mfu->filename)); } else { - MemFile *prevfile = NULL; - - if (curundo->prev) prevfile = &(curundo->prev->memfile); - - /* success = */ /* UNUSED */ BLO_write_file_mem(CTX_data_main(C), prevfile, &curundo->memfile, G.fileflags); - curundo->undo_size = curundo->memfile.size; - } - - if (U.undomemory != 0) { - size_t maxmem, totmem; - /* limit to maximum memory (afterwards, we can't know in advance) */ - totmem = 0; - maxmem = ((size_t)U.undomemory) * 1024 * 1024; - - /* keep at least two (original + other) */ - uel = undobase.last; - while (uel && uel->prev) { - totmem += uel->undo_size; - if (totmem > maxmem) break; - uel = uel->prev; - } - - if (uel) { - if (uel->prev && uel->prev->prev) - uel = uel->prev; - - while (undobase.first != uel) { - UndoElem *first = undobase.first; - BLI_remlink(&undobase, first); - /* the merge is because of compression */ - BLO_memfile_merge(&first->memfile, &first->next->memfile); - MEM_freeN(first); - } - } - } -} - -/* 1 = an undo, -1 is a redo. we have to make sure 'curundo' remains at current situation */ -void BKE_undo_step(bContext *C, int step) -{ - - if (step == 0) { - read_undosave(C, curundo); - } - else if (step == 1) { - /* curundo should never be NULL, after restart or load file it should call undo_save */ - if (curundo == NULL || curundo->prev == NULL) { - // XXX error("No undo available"); - } - else { - if (G.debug & G_DEBUG) printf("undo %s\n", curundo->name); - curundo = curundo->prev; - read_undosave(C, curundo); - } - } - else { - /* curundo has to remain current situation! */ - - if (curundo == NULL || curundo->next == NULL) { - // XXX error("No redo available"); - } - else { - read_undosave(C, curundo->next); - curundo = curundo->next; - if (G.debug & G_DEBUG) printf("redo %s\n", curundo->name); - } - } -} - -void BKE_undo_reset(void) -{ - UndoElem *uel; - - uel = undobase.first; - while (uel) { - BLO_memfile_free(&uel->memfile); - uel = uel->next; - } - - BLI_freelistN(&undobase); - curundo = NULL; -} - -/* based on index nr it does a restore */ -void BKE_undo_number(bContext *C, int nr) -{ - curundo = BLI_findlink(&undobase, nr); - BKE_undo_step(C, 0); -} - -/* go back to the last occurance of name in stack */ -void BKE_undo_name(bContext *C, const char *name) -{ - UndoElem *uel = BLI_rfindstring(&undobase, name, offsetof(UndoElem, name)); - - if (uel && uel->prev) { - curundo = uel->prev; - BKE_undo_step(C, 0); - } -} - -/* name optional */ -bool BKE_undo_is_valid(const char *name) -{ - if (name) { - UndoElem *uel = BLI_rfindstring(&undobase, name, offsetof(UndoElem, name)); - return uel && uel->prev; + MemFile *prevfile = (mfu_prev) ? &(mfu_prev->memfile) : NULL; + /* success = */ /* UNUSED */ BLO_write_file_mem(bmain, prevfile, &mfu->memfile, G.fileflags); + mfu->undo_size = mfu->memfile.size; } - return undobase.last != undobase.first; -} - -/* get name of undo item, return null if no item with this index */ -/* if active pointer, set it to 1 if true */ -const char *BKE_undo_get_name(int nr, bool *r_active) -{ - UndoElem *uel = BLI_findlink(&undobase, nr); + bmain->is_memfile_undo_written = true; - if (r_active) *r_active = false; - - if (uel) { - if (r_active && (uel == curundo)) { - *r_active = true; - } - return uel->name; - } - return NULL; + return mfu; } -/* return the name of the last item */ -const char *BKE_undo_get_name_last(void) +void BKE_memfile_undo_free(MemFileUndoData *mfu) { - UndoElem *uel = undobase.last; - return (uel ? uel->name : NULL); -} - -/** - * Saves .blend using undo buffer. - * - * \return success. - */ -bool BKE_undo_save_file(const char *filename) -{ - UndoElem *uel; - MemFileChunk *chunk; - int file, oflags; - - if ((U.uiflag & USER_GLOBALUNDO) == 0) { - return false; - } - - uel = curundo; - if (uel == NULL) { - fprintf(stderr, "No undo buffer to save recovery file\n"); - return false; - } - - /* note: This is currently used for autosave and 'quit.blend', where _not_ following symlinks is OK, - * however if this is ever executed explicitly by the user, we may want to allow writing to symlinks. - */ - - oflags = O_BINARY | O_WRONLY | O_CREAT | O_TRUNC; -#ifdef O_NOFOLLOW - /* use O_NOFOLLOW to avoid writing to a symlink - use 'O_EXCL' (CVE-2008-1103) */ - oflags |= O_NOFOLLOW; -#else - /* TODO(sergey): How to deal with symlinks on windows? */ -# ifndef _MSC_VER -# warning "Symbolic links will be followed on undo save, possibly causing CVE-2008-1103" -# endif -#endif - file = BLI_open(filename, oflags, 0666); - - if (file == -1) { - fprintf(stderr, "Unable to save '%s': %s\n", - filename, errno ? strerror(errno) : "Unknown error opening file"); - return false; - } - - for (chunk = uel->memfile.chunks.first; chunk; chunk = chunk->next) { - if (write(file, chunk->buf, chunk->size) != chunk->size) { - break; - } - } - - close(file); - - if (chunk) { - fprintf(stderr, "Unable to save '%s': %s\n", - filename, errno ? strerror(errno) : "Unknown error writing file"); - return false; - } - return true; -} - -/* sets curscene */ -Main *BKE_undo_get_main(Scene **r_scene) -{ - Main *mainp = NULL; - BlendFileData *bfd = BLO_read_from_memfile(G.main, G.main->name, &curundo->memfile, NULL, BLO_READ_SKIP_NONE); - - if (bfd) { - mainp = bfd->main; - if (r_scene) { - *r_scene = bfd->curscene; - } - - MEM_freeN(bfd); - } - - return mainp; + BLO_memfile_free(&mfu->memfile); + MEM_freeN(mfu); } /** \} */ diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index 99423ee26a6..10b724f9f69 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -757,6 +757,7 @@ void BKE_libblock_management_main_add(Main *bmain, void *idv) new_id(lb, id, NULL); /* alphabetic insertion: is in new_id */ id->tag &= ~(LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT); + bmain->is_memfile_undo_written = false; BKE_main_unlock(bmain); } @@ -776,6 +777,7 @@ void BKE_libblock_management_main_remove(Main *bmain, void *idv) BKE_main_lock(bmain); BLI_remlink(lb, id); id->tag |= LIB_TAG_NO_MAIN; + bmain->is_memfile_undo_written = false; BKE_main_unlock(bmain); } @@ -1138,6 +1140,7 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl BKE_main_lock(bmain); BLI_addtail(lb, id); new_id(lb, id, name); + bmain->is_memfile_undo_written = false; /* alphabetic insertion: is in new_id */ BKE_main_unlock(bmain); diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c new file mode 100644 index 00000000000..ddcd16f998e --- /dev/null +++ b/source/blender/blenkernel/intern/undo_system.c @@ -0,0 +1,795 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/util/undo_system.c + * \ingroup edutil + * + * Used by ED_undo.h, internal implementation. + */ + +#include + +#include "CLG_log.h" + +#include "BLI_utildefines.h" +#include "BLI_sys_types.h" +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_sort_utils.h" + +#include "DNA_listBase.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_undo_system.h" + +#include "MEM_guardedalloc.h" + +#define undo_stack _wm_undo_stack_disallow /* pass in as a variable always. */ + +/** Odd requirement of Blender that we always keep a memfile undo in the stack. */ +#define WITH_GLOBAL_UNDO_KEEP_ONE + +/** Make sure all ID's created at the point we add an undo step that uses ID's. */ +#define WITH_GLOBAL_UNDO_ENSURE_UPDATED + +/** We only need this locally. */ +static CLG_LogRef LOG = {"bke.undosys"}; + +/* -------------------------------------------------------------------- */ +/** \name Internal Nested Undo Checks + * + * Make sure we're not running undo operations from 'step_encode', 'step_decode' callbacks. + * bugs caused by this situation aren't _that_ hard to spot but aren't always so obvious. + * Best we have a check which shows the problem immediately. + * + * \{ */ +#define WITH_NESTED_UNDO_CHECK + +#ifdef WITH_NESTED_UNDO_CHECK +static bool g_undo_callback_running = false; +# define UNDO_NESTED_ASSERT(state) BLI_assert(g_undo_callback_running == state) +# define UNDO_NESTED_CHECK_BEGIN { \ + UNDO_NESTED_ASSERT(false); \ + g_undo_callback_running = true; \ +} ((void)0) +# define UNDO_NESTED_CHECK_END { \ + UNDO_NESTED_ASSERT(true); \ + g_undo_callback_running = false; \ +} ((void)0) +#else +# define UNDO_NESTED_ASSERT(state) ((void)0) +# define UNDO_NESTED_CHECK_BEGIN ((void)0) +# define UNDO_NESTED_CHECK_END ((void)0) +#endif +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Undo Types + * + * Unfortunately we need this for a handful of places. + */ +const UndoType *BKE_UNDOSYS_TYPE_MEMFILE = NULL; +const UndoType *BKE_UNDOSYS_TYPE_IMAGE = NULL; +const UndoType *BKE_UNDOSYS_TYPE_SCULPT = NULL; +const UndoType *BKE_UNDOSYS_TYPE_PARTICLE = NULL; +const UndoType *BKE_UNDOSYS_TYPE_PAINTCURVE = NULL; +/** \} */ + +/* UndoType */ + +static ListBase g_undo_types = {NULL, NULL}; + +static const UndoType *BKE_undosys_type_from_context(bContext *C) +{ + for (const UndoType *ut = g_undo_types.first; ut; ut = ut->next) { + /* No poll means we don't check context. */ + if (ut->poll && ut->poll(C)) { + return ut; + } + } + return NULL; +} + +/* -------------------------------------------------------------------- */ +/** \name Internal Callback Wrappers + * + * #UndoRefID is simply a way to avoid inlining name copy and lookups, + * since it's easy to forget a single case when done inline (crashing in some cases). + * + * \{ */ + +static void undosys_id_ref_store(void *UNUSED(user_data), UndoRefID *id_ref) +{ + BLI_assert(id_ref->name[0] == '\0'); + if (id_ref->ptr) { + BLI_strncpy(id_ref->name, id_ref->ptr->name, sizeof(id_ref->name)); + /* Not needed, just prevents stale data access. */ + id_ref->ptr = NULL; + } +} + +static void undosys_id_ref_resolve(void *user_data, UndoRefID *id_ref) +{ + /* Note: we could optimize this, for now it's not too bad since it only runs when we access undo! */ + Main *bmain = user_data; + ListBase *lb = which_libbase(bmain, GS(id_ref->name)); + for (ID *id = lb->first; id; id = id->next) { + if (STREQ(id_ref->name, id->name) && (id->lib == NULL)) { + id_ref->ptr = id; + break; + } + } +} + +static bool undosys_step_encode(bContext *C, UndoStep *us) +{ + CLOG_INFO(&LOG, 2, "%p '%s', type='%s'", us, us->name, us->type->name); + UNDO_NESTED_CHECK_BEGIN; + bool ok = us->type->step_encode(C, us); + UNDO_NESTED_CHECK_END; + if (ok) { + if (us->type->step_foreach_ID_ref != NULL) { + /* Don't use from context yet because sometimes context is fake and not all members are filled in. */ + Main *bmain = G.main; + us->type->step_foreach_ID_ref(us, undosys_id_ref_store, bmain); + } + } + return ok; +} + +static void undosys_step_decode(bContext *C, UndoStep *us, int dir) +{ + CLOG_INFO(&LOG, 2, "%p '%s', type='%s'", us, us->name, us->type->name); + if (us->type->step_foreach_ID_ref) { + /* Don't use from context yet because sometimes context is fake and not all members are filled in. */ + Main *bmain = G.main; + us->type->step_foreach_ID_ref(us, undosys_id_ref_resolve, bmain); + } + + UNDO_NESTED_CHECK_BEGIN; + us->type->step_decode(C, us, dir); + UNDO_NESTED_CHECK_END; +} + +static void undosys_step_free_and_unlink(UndoStack *ustack, UndoStep *us) +{ + CLOG_INFO(&LOG, 2, "%p '%s', type='%s'", us, us->name, us->type->name); + UNDO_NESTED_CHECK_BEGIN; + us->type->step_free(us); + UNDO_NESTED_CHECK_END; + + BLI_remlink(&ustack->steps, us); + MEM_freeN(us); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Undo Stack + * \{ */ + +#ifndef NDEBUG +static void undosys_stack_validate(UndoStack *ustack, bool expect_non_empty) +{ + if (ustack->step_active != NULL) { + BLI_assert(!BLI_listbase_is_empty(&ustack->steps)); + BLI_assert(BLI_findindex(&ustack->steps, ustack->step_active) != -1); + } + if (expect_non_empty) { + BLI_assert(!BLI_listbase_is_empty(&ustack->steps)); + } +} +#else +static void undosys_stack_validate(UndoStack *ustack, bool expect_non_empty) +{ + UNUSED_VARS(ustack, expect_non_empty); +} +#endif + +UndoStack *BKE_undosys_stack_create(void) +{ + UndoStack *ustack = MEM_callocN(sizeof(UndoStack), __func__); + return ustack; +} + +void BKE_undosys_stack_destroy(UndoStack *ustack) +{ + BKE_undosys_stack_clear(ustack); + MEM_freeN(ustack); +} + +void BKE_undosys_stack_clear(UndoStack *ustack) +{ + UNDO_NESTED_ASSERT(false); + CLOG_INFO(&LOG, 1, "steps=%d", BLI_listbase_count(&ustack->steps)); + for (UndoStep *us = ustack->steps.last, *us_prev; us; us = us_prev) { + us_prev = us->prev; + undosys_step_free_and_unlink(ustack, us); + } + BLI_listbase_clear(&ustack->steps); + ustack->step_active = NULL; +} + +static bool undosys_stack_push_main(UndoStack *ustack, const char *name, struct Main *bmain) +{ + UNDO_NESTED_ASSERT(false); + CLOG_INFO(&LOG, 1, "'%s'", name); + bContext *C_temp = CTX_create(); + CTX_data_main_set(C_temp, bmain); + bool ok = BKE_undosys_step_push_with_type(ustack, C_temp, name, BKE_UNDOSYS_TYPE_MEMFILE); + CTX_free(C_temp); + return ok; +} + +void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain) +{ + UNDO_NESTED_ASSERT(false); + undosys_stack_push_main(ustack, "original", bmain); +} + +/* name optional */ +bool BKE_undosys_stack_has_undo(UndoStack *ustack, const char *name) +{ + if (name) { + UndoStep *us = BLI_rfindstring(&ustack->steps, name, offsetof(UndoStep, name)); + return us && us->prev; + } + + return !BLI_listbase_is_empty(&ustack->steps); +} + +UndoStep *BKE_undosys_stack_active_with_type(UndoStack *ustack, const UndoType *ut) +{ + UndoStep *us = ustack->step_active; + while (us && (us->type != ut)) { + us = us->prev; + } + return us; +} + +UndoStep *BKE_undosys_stack_init_or_active_with_type(UndoStack *ustack, const UndoType *ut) +{ + UNDO_NESTED_ASSERT(false); + CLOG_INFO(&LOG, 1, "type='%s'", ut->name); + if (ustack->step_init && (ustack->step_init->type == ut)) { + return ustack->step_init; + } + return BKE_undosys_stack_active_with_type(ustack, ut); +} + +/** + * \param steps: Limit the number of undo steps. + * \param memory_limit: Limit the amount of memory used by the undo stack. + */ +void BKE_undosys_stack_limit_steps_and_memory(UndoStack *ustack, int steps, size_t memory_limit) +{ + UNDO_NESTED_ASSERT(false); + if (!(steps || memory_limit)) { + return; + } + + CLOG_INFO(&LOG, 1, "steps=%d, memory_limit=%zu", steps, memory_limit); + UndoStep *us; +#ifdef WITH_GLOBAL_UNDO_KEEP_ONE + UndoStep *us_exclude = NULL; +#endif + /* keep at least two (original + other) */ + size_t data_size_all = 0; + size_t us_count = 0; + for (us = ustack->steps.last; us && us->prev; us = us->prev) { + if (memory_limit) { + data_size_all += us->data_size; + if (data_size_all > memory_limit) { + break; + } + } + if (steps) { + if (us_count == steps) { + break; + } + if (us->skip == false) { + us_count += 1; + } + } + } + + if (us) { + if (us->prev && us->prev->prev) { + us = us->prev; + } + +#ifdef WITH_GLOBAL_UNDO_KEEP_ONE + /* Hack, we need to keep at least one BKE_UNDOSYS_TYPE_MEMFILE. */ + if (us->type != BKE_UNDOSYS_TYPE_MEMFILE) { + us_exclude = us->prev; + while (us_exclude && us->type != BKE_UNDOSYS_TYPE_MEMFILE) { + us_exclude = us_exclude->prev; + } + if (us_exclude) { + BLI_remlink(&ustack->steps, us_exclude); + } + } +#endif + /* Free from first to last, free functions may update de-duplication info (see #MemFileUndoStep). */ + while (ustack->steps.first != us) { + UndoStep *us_first = ustack->steps.first; + BLI_assert(us_first != ustack->step_active); + undosys_step_free_and_unlink(ustack, us_first); + } + +#ifdef WITH_GLOBAL_UNDO_KEEP_ONE + if (us_exclude) { + BLI_addhead(&ustack->steps, us_exclude); + } +#endif + } +} + +/** \} */ + +void BKE_undosys_step_push_init_with_type(UndoStack *ustack, bContext *C, const char *name, const UndoType *ut) +{ + UNDO_NESTED_ASSERT(false); + /* We could detect and clean this up (but it should never happen!). */ + BLI_assert(ustack->step_init == NULL); + if (ut->step_encode_init) { + undosys_stack_validate(ustack, false); + UndoStep *us = MEM_callocN(ut->step_size, __func__); + CLOG_INFO(&LOG, 1, "%p, '%s', type='%s'", us, name, us->type->name); + if (name != NULL) { + BLI_strncpy(us->name, name, sizeof(us->name)); + } + us->type = ut; + ustack->step_init = us; + ut->step_encode_init(C, us); + undosys_stack_validate(ustack, true); + } +} + +void BKE_undosys_step_push_init(UndoStack *ustack, bContext *C, const char *name) +{ + UNDO_NESTED_ASSERT(false); + /* We could detect and clean this up (but it should never happen!). */ + BLI_assert(ustack->step_init == NULL); + const UndoType *ut = BKE_undosys_type_from_context(C); + if (ut == NULL) { + return; + } + return BKE_undosys_step_push_init_with_type(ustack, C, name, ut); +} + +bool BKE_undosys_step_push_with_type(UndoStack *ustack, bContext *C, const char *name, const UndoType *ut) +{ + UNDO_NESTED_ASSERT(false); + undosys_stack_validate(ustack, false); + bool is_not_empty = ustack->step_active != NULL; + + /* Remove all undos after (also when 'ustack->step_active == NULL'). */ + while (ustack->steps.last != ustack->step_active) { + UndoStep *us_iter = ustack->steps.last; + undosys_step_free_and_unlink(ustack, us_iter); + undosys_stack_validate(ustack, is_not_empty); + } + + if (ustack->step_active) { + BLI_assert(BLI_findindex(&ustack->steps, ustack->step_active) != -1); + } + +#ifdef WITH_GLOBAL_UNDO_ENSURE_UPDATED + if (ut->step_foreach_ID_ref != NULL) { + Main *bmain = G.main; + if (bmain->is_memfile_undo_written == false) { + const char *name_internal = "MemFile Internal"; + if (undosys_stack_push_main(ustack, name_internal, bmain)) { + UndoStep *us = ustack->steps.last; + BLI_assert(STREQ(us->name, name_internal)); + us->skip = true; + } + } + } +#endif + + UndoStep *us = ustack->step_init ? ustack->step_init : MEM_callocN(ut->step_size, __func__); + ustack->step_init = NULL; + if (us->name[0] == '\0') { + BLI_strncpy(us->name, name, sizeof(us->name)); + } + us->type = ut; + /* initialized, not added yet. */ + + if (undosys_step_encode(C, us)) { + ustack->step_active = us; + BLI_addtail(&ustack->steps, us); + undosys_stack_validate(ustack, true); + return true; + } + else { + MEM_freeN(us); + undosys_stack_validate(ustack, true); + return false; + } +} + +bool BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char *name) +{ + UNDO_NESTED_ASSERT(false); + const UndoType *ut = ustack->step_init ? ustack->step_init->type : BKE_undosys_type_from_context(C); + if (ut == NULL) { + return false; + } + return BKE_undosys_step_push_with_type(ustack, C, name, ut); +} + + +/** + * Useful when we want to diff against previous undo data but can't be sure the types match. + */ +UndoStep *BKE_undosys_step_same_type_next(UndoStep *us) +{ + if (us) { + const UndoType *ut = us->type; + while ((us = us->next)) { + if (us->type == ut) { + return us; + } + } + + } + return us; +} + +/** + * Useful when we want to diff against previous undo data but can't be sure the types match. + */ +UndoStep *BKE_undosys_step_same_type_prev(UndoStep *us) +{ + if (us) { + const UndoType *ut = us->type; + while ((us = us->prev)) { + if (us->type == ut) { + return us; + } + } + + } + return us; +} + +UndoStep *BKE_undosys_step_find_by_name_with_type(UndoStack *ustack, const char *name, const UndoType *ut) +{ + for (UndoStep *us = ustack->steps.last; us; us = us->prev) { + if (us->type == ut) { + if (STREQ(name, us->name)) { + return us; + } + } + } + return NULL; +} + +UndoStep *BKE_undosys_step_find_by_name(UndoStack *ustack, const char *name) +{ + return BLI_rfindstring(&ustack->steps, name, offsetof(UndoStep, name)); +} + +bool BKE_undosys_step_undo_with_data_ex( + UndoStack *ustack, bContext *C, UndoStep *us, + bool use_skip) +{ + UNDO_NESTED_ASSERT(false); + if (us) { + undosys_stack_validate(ustack, true); + } + UndoStep *us_prev = us ? us->prev : NULL; + if (us && us->type->mode == BKE_UNDOTYPE_MODE_STORE) { + /* The current state is a copy, we need to load the previous state. */ + us = us_prev; + } + + if (us != NULL) { + CLOG_INFO(&LOG, 1, "%p, '%s', type='%s'", us, us->name, us->type->name); + undosys_step_decode(C, us, -1); + ustack->step_active = us_prev; + undosys_stack_validate(ustack, true); + if (use_skip) { + if (ustack->step_active && ustack->step_active->skip) { + CLOG_INFO(&LOG, 2, "undo continue with skip %p '%s', type='%s'", us, us->name, us->type->name); + BKE_undosys_step_undo_with_data(ustack, C, ustack->step_active); + } + } + return true; + } + return false; +} +bool BKE_undosys_step_undo_with_data(UndoStack *ustack, bContext *C, UndoStep *us) +{ + return BKE_undosys_step_undo_with_data_ex(ustack, C, us, true); +} + +bool BKE_undosys_step_undo(UndoStack *ustack, bContext *C) +{ + return BKE_undosys_step_undo_with_data(ustack, C, ustack->step_active); +} + +void BKE_undosys_step_undo_from_index(UndoStack *ustack, bContext *C, int index) +{ + UndoStep *us = BLI_findlink(&ustack->steps, index); + BLI_assert(us->skip == false); + BKE_undosys_step_load_data(ustack, C, us); +} + +bool BKE_undosys_step_redo_with_data_ex( + UndoStack *ustack, bContext *C, UndoStep *us, + bool use_skip) +{ + UNDO_NESTED_ASSERT(false); + UndoStep *us_next = us ? us->next : NULL; + /* Unlike undo accumulate, we always use the next. */ + us = us_next; + + if (us != NULL) { + CLOG_INFO(&LOG, 1, "%p, '%s', type='%s'", us, us->name, us->type->name); + undosys_step_decode(C, us, 1); + ustack->step_active = us_next; + if (use_skip) { + if (ustack->step_active && ustack->step_active->skip) { + CLOG_INFO(&LOG, 2, "redo continue with skip %p '%s', type='%s'", us, us->name, us->type->name); + BKE_undosys_step_redo_with_data(ustack, C, ustack->step_active); + } + } + return true; + } + return false; +} +bool BKE_undosys_step_redo_with_data(UndoStack *ustack, bContext *C, UndoStep *us) +{ + return BKE_undosys_step_redo_with_data_ex(ustack, C, us, true); +} + +bool BKE_undosys_step_redo(UndoStack *ustack, bContext *C) +{ + return BKE_undosys_step_redo_with_data(ustack, C, ustack->step_active); +} + +bool BKE_undosys_step_load_data(UndoStack *ustack, bContext *C, UndoStep *us) +{ + UNDO_NESTED_ASSERT(false); + const int index_active = BLI_findindex(&ustack->steps, ustack->step_active); + const int index_target = BLI_findindex(&ustack->steps, us); + BLI_assert(!ELEM(-1, index_active, index_target)); + bool ok = true; + + if (index_target < index_active) { + uint i = index_active - index_target; + while (i-- && ok) { + ok = BKE_undosys_step_undo_with_data_ex(ustack, C, ustack->step_active, false); + } + } + else if (index_target > index_active) { + uint i = index_target - index_active; + while (i-- && ok) { + ok = BKE_undosys_step_redo_with_data_ex(ustack, C, ustack->step_active, false); + } + } + + if (ok) { + BLI_assert(ustack->step_active == us); + } + + return ok; +} + +bool BKE_undosys_step_undo_compat_only(UndoStack *ustack, bContext *C, int step) +{ + if (step == 0) { + return BKE_undosys_step_undo_with_data(ustack, C, ustack->step_active); + } + else if (step == 1) { + return BKE_undosys_step_undo(ustack, C); + } + else { + return BKE_undosys_step_redo(ustack, C); + } +} +/** + * Similar to #WM_operatortype_append + */ +UndoType *BKE_undosys_type_append(void (*undosys_fn)(UndoType *)) +{ + UndoType *ut; + + ut = MEM_callocN(sizeof(UndoType), __func__); + + undosys_fn(ut); + + BLI_assert(ut->mode != 0); + + BLI_addtail(&g_undo_types, ut); + + return ut; +} + +void BKE_undosys_type_free_all(void) +{ + UndoType *ut; + while ((ut = BLI_pophead(&g_undo_types))) { + MEM_freeN(ut); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ID Reference Utilities + * + * Unfortunately we need this for a handful of places. + */ + +static void UNUSED_FUNCTION(BKE_undosys_foreach_ID_ref( + UndoStack *ustack, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)) +{ + for (UndoStep *us = ustack->steps.first; us; us = us->next) { + const UndoType *ut = us->type; + if (ut->step_foreach_ID_ref != NULL) { + ut->step_foreach_ID_ref(us, foreach_ID_ref_fn, user_data); + } + } +} + +typedef struct UndoIDPtrMapItem { + /** Never changes (matches undo data). Use as sort key for binary search. */ + const void *ptr; + /** Write the new pointers here. */ + uint index; +} UndoIDPtrMapItem; + +typedef struct UndoIDPtrMap { + UndoRefID *refs; + /** + * Pointer map, update 'dst' members before use. + * This is always sorted (adds some overhead when adding, in practice it's acceptable since). + */ + UndoIDPtrMapItem *pmap; + + /** Length for both 'refs' & 'pmap' */ + uint len; + uint len_alloc; +} UndoIDPtrMap; + +#ifdef DEBUG +# define PMAP_DEFAULT_ALLOC 1 +#else +# define PMAP_DEFAULT_ALLOC 32 +#endif + +void BKE_undosys_ID_map_foreach_ID_ref( + UndoIDPtrMap *map, + UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + for (uint i = 0; i < map->len; i++) { + foreach_ID_ref_fn(user_data, &map->refs[i]); + } +} + +/** + * Return true when found, otherwise index is set to the index we should insert. + */ +static bool undosys_ID_map_lookup_index(const UndoIDPtrMap *map, const void *key, uint *r_index) +{ + const UndoIDPtrMapItem *pmap = map->pmap; + const uint len = map->len; + if (len == 0) { + if (*r_index) { + *r_index = 0; + } + return false; + } + int min = 0, max = len - 1; + while (min <= max) { + const uint mid = (min + max) / 2; + if (pmap[mid].ptr < key) { + min = mid + 1; + } + else if (pmap[mid].ptr == key) { + if (r_index) { + *r_index = mid; + } + return true; + } + else if (pmap[mid].ptr > key) { + max = mid - 1; + } + } + return false; +} + +/** + * A set of ID's use for efficient decoding, so we can map pointers back to the newly loaded data + * without performing full look ups each time. + * + * This can be used as an old_pointer -> new_pointer lookup. + */ +UndoIDPtrMap *BKE_undosys_ID_map_create(void) +{ + UndoIDPtrMap *map = MEM_mallocN(sizeof(*map), __func__); + map->len_alloc = PMAP_DEFAULT_ALLOC; + map->refs = MEM_mallocN(sizeof(*map->refs) * map->len_alloc, __func__); + map->pmap = MEM_mallocN(sizeof(*map->pmap) * map->len_alloc, __func__); + map->len = 0; + return map; +} +void BKE_undosys_ID_map_destroy(UndoIDPtrMap *idpmap) +{ + MEM_SAFE_FREE(idpmap->refs); + MEM_SAFE_FREE(idpmap->pmap); + MEM_freeN(idpmap); +} + +void BKE_undosys_ID_map_add(UndoIDPtrMap *map, ID *id) +{ + uint index; + if (id->lib != NULL) { + return; + } + + if (undosys_ID_map_lookup_index(map, id, &index)) { + return; /* exists. */ + } + + const uint len_src = map->len; + const uint len_dst = map->len + 1; + if (len_dst > map->len_alloc) { + map->len_alloc *= 2; + BLI_assert(map->len_alloc >= len_dst); + map->pmap = MEM_reallocN(map->pmap, sizeof(*map->pmap) * map->len_alloc); + map->refs = MEM_reallocN(map->refs, sizeof(*map->refs) * map->len_alloc); + } + +#if 0 /* Will be done automatically in callback. */ + BLI_strncpy(map->refs[len_src].name, id->name, sizeof(id->name)); +#else + map->refs[len_src].name[0] = '\0'; +#endif + map->refs[len_src].ptr = id; + + map->pmap[len_src].ptr = id; + map->pmap[len_src].index = len_src; + map->len = len_dst; + + qsort(map->pmap, map->len, sizeof(*map->pmap), BLI_sortutil_cmp_ptr); +} + +ID *BKE_undosys_ID_map_lookup(const UndoIDPtrMap *map, const ID *id_src) +{ + /* We should only ever lookup indices which exist! */ + uint index; + if (!undosys_ID_map_lookup_index(map, id_src, &index)) { + BLI_assert(0); + } + ID *id_dst = map->refs[index].ptr; + BLI_assert(id_dst != NULL); + BLI_assert(STREQ(id_dst->name, map->refs[index].name)); + return id_dst; +} + +/** \} */ diff --git a/source/blender/blenloader/BLO_readfile.h b/source/blender/blenloader/BLO_readfile.h index 4fd2e227bc5..0b4ff13c7bd 100644 --- a/source/blender/blenloader/BLO_readfile.h +++ b/source/blender/blenloader/BLO_readfile.h @@ -147,6 +147,8 @@ void BLO_update_defaults_startup_blend(struct Main *mainvar); struct BlendThumbnail *BLO_thumbnail_from_file(const char *filepath); +struct Main *BLO_main_from_memfile(struct MemFile *memfile, struct Main *bmain, struct Scene **r_scene); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenloader/BLO_undofile.h b/source/blender/blenloader/BLO_undofile.h index d3c0130a63b..b713b963056 100644 --- a/source/blender/blenloader/BLO_undofile.h +++ b/source/blender/blenloader/BLO_undofile.h @@ -33,6 +33,8 @@ * \ingroup blenloader */ +struct Scene; + typedef struct { void *next, *prev; const char *buf; @@ -47,6 +49,12 @@ typedef struct MemFile { size_t size; } MemFile; +typedef struct MemFileUndoData { + char filename[1024]; /* FILE_MAX */ + MemFile memfile; + size_t undo_size; +} MemFileUndoData; + /* actually only used writefile.c */ extern void memfile_chunk_add(MemFile *compare, MemFile *current, const char *buf, unsigned int size); @@ -54,5 +62,9 @@ extern void memfile_chunk_add(MemFile *compare, MemFile *current, const char *bu extern void BLO_memfile_free(MemFile *memfile); extern void BLO_memfile_merge(MemFile *first, MemFile *second); -#endif +/* utilities */ +extern struct Main *BLO_memfile_main_get(struct MemFile *memfile, struct Main *bmain, struct Scene **r_scene); +extern bool BLO_memfile_write_file(struct MemFile *memfile, const char *filename); + +#endif /* __BLO_UNDOFILE_H__ */ diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index e85af67b105..7b51ddcce92 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -6300,7 +6300,8 @@ static void direct_link_windowmanager(FileData *fd, wmWindowManager *wm) wm->defaultconf = NULL; wm->addonconf = NULL; wm->userconf = NULL; - + wm->undo_stack = NULL; + BLI_listbase_clear(&wm->jobs); BLI_listbase_clear(&wm->drags); diff --git a/source/blender/blenloader/intern/undofile.c b/source/blender/blenloader/intern/undofile.c index ffc7d7c83f5..287936e276e 100644 --- a/source/blender/blenloader/intern/undofile.c +++ b/source/blender/blenloader/intern/undofile.c @@ -34,6 +34,15 @@ #include #include #include +#include +#include + +/* open/close */ +#ifndef _WIN32 +# include +#else +# include +#endif #include "MEM_guardedalloc.h" @@ -42,6 +51,9 @@ #include "BLI_blenlib.h" #include "BLO_undofile.h" +#include "BLO_readfile.h" + +#include "BKE_main.h" /* keep last */ #include "BLI_strict_flags.h" @@ -124,3 +136,69 @@ void memfile_chunk_add(MemFile *compare, MemFile *current, const char *buf, unsi current->size += size; } } + +struct Main *BLO_memfile_main_get(struct MemFile *memfile, struct Main *oldmain, struct Scene **r_scene) +{ + struct Main *bmain_undo = NULL; + BlendFileData *bfd = BLO_read_from_memfile(oldmain, oldmain->name, memfile, NULL, BLO_READ_SKIP_NONE); + + if (bfd) { + bmain_undo = bfd->main; + if (r_scene) { + *r_scene = bfd->curscene; + } + + MEM_freeN(bfd); + } + + return bmain_undo; +} + + +/** + * Saves .blend using undo buffer. + * + * \return success. + */ +bool BLO_memfile_write_file(struct MemFile *memfile, const char *filename) +{ + MemFileChunk *chunk; + int file, oflags; + + /* note: This is currently used for autosave and 'quit.blend', where _not_ following symlinks is OK, + * however if this is ever executed explicitly by the user, we may want to allow writing to symlinks. + */ + + oflags = O_BINARY | O_WRONLY | O_CREAT | O_TRUNC; +#ifdef O_NOFOLLOW + /* use O_NOFOLLOW to avoid writing to a symlink - use 'O_EXCL' (CVE-2008-1103) */ + oflags |= O_NOFOLLOW; +#else + /* TODO(sergey): How to deal with symlinks on windows? */ +# ifndef _MSC_VER +# warning "Symbolic links will be followed on undo save, possibly causing CVE-2008-1103" +# endif +#endif + file = BLI_open(filename, oflags, 0666); + + if (file == -1) { + fprintf(stderr, "Unable to save '%s': %s\n", + filename, errno ? strerror(errno) : "Unknown error opening file"); + return false; + } + + for (chunk = memfile->chunks.first; chunk; chunk = chunk->next) { + if (write(file, chunk->buf, chunk->size) != chunk->size) { + break; + } + } + + close(file); + + if (chunk) { + fprintf(stderr, "Unable to save '%s': %s\n", + filename, errno ? strerror(errno) : "Unknown error writing file"); + return false; + } + return true; +} diff --git a/source/blender/editors/armature/editarmature_undo.c b/source/blender/editors/armature/editarmature_undo.c index 36e6ec4ba7f..f27d68d0634 100644 --- a/source/blender/editors/armature/editarmature_undo.c +++ b/source/blender/editors/armature/editarmature_undo.c @@ -33,21 +33,31 @@ #include "MEM_guardedalloc.h" #include "BLI_math.h" +#include "BLI_array_utils.h" #include "BKE_context.h" +#include "BKE_depsgraph.h" +#include "BKE_undo_system.h" #include "ED_armature.h" +#include "ED_object.h" #include "ED_util.h" +#include "WM_types.h" +#include "WM_api.h" + +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ + typedef struct UndoArmature { EditBone *act_edbone; ListBase lb; + size_t undo_size; } UndoArmature; -static void undoBones_to_editBones(void *uarmv, void *armv, void *UNUSED(data)) +static void undoarm_to_editarm(UndoArmature *uarm, bArmature *arm) { - UndoArmature *uarm = uarmv; - bArmature *arm = armv; EditBone *ebone; ED_armature_ebone_listbase_free(arm->edbo); @@ -65,48 +75,117 @@ static void undoBones_to_editBones(void *uarmv, void *armv, void *UNUSED(data)) ED_armature_ebone_listbase_temp_clear(arm->edbo); } -static void *editBones_to_undoBones(void *armv, void *UNUSED(obdata)) +static void *undoarm_from_editarm(UndoArmature *uarm, bArmature *arm) { - bArmature *arm = armv; - UndoArmature *uarm; - EditBone *ebone; + BLI_assert(BLI_array_is_zeroed(uarm, 1)); - uarm = MEM_callocN(sizeof(UndoArmature), "listbase undo"); + /* TODO: include size of ID-properties. */ + uarm->undo_size = 0; ED_armature_ebone_listbase_copy(&uarm->lb, arm->edbo); /* active bone */ if (arm->act_edbone) { - ebone = arm->act_edbone; + EditBone *ebone = arm->act_edbone; uarm->act_edbone = ebone->temp.ebone; } ED_armature_ebone_listbase_temp_clear(&uarm->lb); + for (EditBone *ebone = uarm->lb.first; ebone; ebone = ebone->next) { + uarm->undo_size += sizeof(EditBone); + } + return uarm; } -static void free_undoBones(void *uarmv) +static void undoarm_free_data(UndoArmature *uarm) { - UndoArmature *uarm = uarmv; - ED_armature_ebone_listbase_free(&uarm->lb); - - MEM_freeN(uarm); } -static void *get_armature_edit(bContext *C) +static Object *editarm_object_from_context(bContext *C) { Object *obedit = CTX_data_edit_object(C); if (obedit && obedit->type == OB_ARMATURE) { - return obedit->data; + bArmature *arm = obedit->data; + if (arm->edbo != NULL) { + return obedit; + } } return NULL; } -/* and this is all the undo system needs to know */ -void undo_push_armature(bContext *C, const char *name) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct ArmatureUndoStep { + UndoStep step; + /* note: will split out into list for multi-object-editmode. */ + UndoRefID_Object obedit_ref; + UndoArmature data; +} ArmatureUndoStep; + +static bool armature_undosys_poll(bContext *C) { - // XXX solve getdata() - undo_editmode_push(C, name, get_armature_edit, free_undoBones, undoBones_to_editBones, editBones_to_undoBones, NULL); + return editarm_object_from_context(C) != NULL; } + +static bool armature_undosys_step_encode(struct bContext *C, UndoStep *us_p) +{ + ArmatureUndoStep *us = (ArmatureUndoStep *)us_p; + us->obedit_ref.ptr = editarm_object_from_context(C); + bArmature *arm = us->obedit_ref.ptr->data; + undoarm_from_editarm(&us->data, arm); + us->step.data_size = us->data.undo_size; + return true; +} + +static void armature_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_EDIT); + BLI_assert(armature_undosys_poll(C)); + + ArmatureUndoStep *us = (ArmatureUndoStep *)us_p; + Object *obedit = us->obedit_ref.ptr; + bArmature *arm = obedit->data; + undoarm_to_editarm(&us->data, arm); + DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); +} + +static void armature_undosys_step_free(UndoStep *us_p) +{ + ArmatureUndoStep *us = (ArmatureUndoStep *)us_p; + undoarm_free_data(&us->data); +} + +static void armature_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + ArmatureUndoStep *us = (ArmatureUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref)); +} + +/* Export for ED_undo_sys. */ +void ED_armature_undosys_type(UndoType *ut) +{ + ut->name = "Edit Armature"; + ut->poll = armature_undosys_poll; + ut->step_encode = armature_undosys_step_encode; + ut->step_decode = armature_undosys_step_decode; + ut->step_free = armature_undosys_step_free; + + ut->step_foreach_ID_ref = armature_undosys_foreach_ID_ref; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(ArmatureUndoStep); +} + +/** \} */ diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index 0dbe526117c..ebf2b63bb49 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -1229,7 +1229,10 @@ void ED_curve_editnurb_make(Object *obedit) if (actkey) { // XXX strcpy(G.editModeTitleExtra, "(Key) "); + /* TODO(campbell): undo_system: investigate why this was needed. */ +#if 0 undo_editmode_clear(); +#endif } if (editnurb) { diff --git a/source/blender/editors/curve/editcurve_undo.c b/source/blender/editors/curve/editcurve_undo.c index f8f96eb3bc9..1bc2d6219b9 100644 --- a/source/blender/editors/curve/editcurve_undo.c +++ b/source/blender/editors/curve/editcurve_undo.c @@ -30,18 +30,29 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" +#include "BLI_array_utils.h" #include "BKE_context.h" #include "BKE_curve.h" #include "BKE_fcurve.h" #include "BKE_library.h" #include "BKE_animsys.h" +#include "BKE_depsgraph.h" +#include "BKE_undo_system.h" +#include "ED_object.h" #include "ED_util.h" #include "ED_curve.h" +#include "WM_types.h" +#include "WM_api.h" + #include "curve_intern.h" +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ + typedef struct { ListBase nubase; int actvert; @@ -49,13 +60,12 @@ typedef struct { ListBase fcurves, drivers; int actnu; int flag; + size_t undo_size; } UndoCurve; -static void undoCurve_to_editCurve(void *ucu, void *UNUSED(edata), void *cu_v) +static void undocurve_to_editcurve(UndoCurve *ucu, Curve *cu) { - Curve *cu = cu_v; - UndoCurve *undoCurve = ucu; - ListBase *undobase = &undoCurve->nubase; + ListBase *undobase = &ucu->nubase; ListBase *editbase = BKE_curve_editNurbs_get(cu); Nurb *nu, *newnu; EditNurb *editnurb = cu->editnurb; @@ -63,19 +73,19 @@ static void undoCurve_to_editCurve(void *ucu, void *UNUSED(edata), void *cu_v) BKE_nurbList_free(editbase); - if (undoCurve->undoIndex) { + if (ucu->undoIndex) { BKE_curve_editNurb_keyIndex_free(&editnurb->keyindex); - editnurb->keyindex = ED_curve_keyindex_hash_duplicate(undoCurve->undoIndex); + editnurb->keyindex = ED_curve_keyindex_hash_duplicate(ucu->undoIndex); } if (ad) { if (ad->action) { free_fcurves(&ad->action->curves); - copy_fcurves(&ad->action->curves, &undoCurve->fcurves); + copy_fcurves(&ad->action->curves, &ucu->fcurves); } free_fcurves(&ad->drivers); - copy_fcurves(&ad->drivers, &undoCurve->drivers); + copy_fcurves(&ad->drivers, &ucu->drivers); } /* copy */ @@ -89,75 +99,149 @@ static void undoCurve_to_editCurve(void *ucu, void *UNUSED(edata), void *cu_v) BLI_addtail(editbase, newnu); } - cu->actvert = undoCurve->actvert; - cu->actnu = undoCurve->actnu; - cu->flag = undoCurve->flag; + cu->actvert = ucu->actvert; + cu->actnu = ucu->actnu; + cu->flag = ucu->flag; ED_curve_updateAnimPaths(cu); } -static void *editCurve_to_undoCurve(void *UNUSED(edata), void *cu_v) +static void undocurve_from_editcurve(UndoCurve *ucu, Curve *cu) { - Curve *cu = cu_v; + BLI_assert(BLI_array_is_zeroed(ucu, 1)); ListBase *nubase = BKE_curve_editNurbs_get(cu); - UndoCurve *undoCurve; EditNurb *editnurb = cu->editnurb, tmpEditnurb; Nurb *nu, *newnu; AnimData *ad = BKE_animdata_from_id(&cu->id); - undoCurve = MEM_callocN(sizeof(UndoCurve), "undoCurve"); + /* TODO: include size of fcurve & undoIndex */ + // ucu->undo_size = 0; if (editnurb->keyindex) { - undoCurve->undoIndex = ED_curve_keyindex_hash_duplicate(editnurb->keyindex); - tmpEditnurb.keyindex = undoCurve->undoIndex; + ucu->undoIndex = ED_curve_keyindex_hash_duplicate(editnurb->keyindex); + tmpEditnurb.keyindex = ucu->undoIndex; } if (ad) { if (ad->action) - copy_fcurves(&undoCurve->fcurves, &ad->action->curves); + copy_fcurves(&ucu->fcurves, &ad->action->curves); - copy_fcurves(&undoCurve->drivers, &ad->drivers); + copy_fcurves(&ucu->drivers, &ad->drivers); } /* copy */ for (nu = nubase->first; nu; nu = nu->next) { newnu = BKE_nurb_duplicate(nu); - if (undoCurve->undoIndex) { + if (ucu->undoIndex) { ED_curve_keyindex_update_nurb(&tmpEditnurb, nu, newnu); } - BLI_addtail(&undoCurve->nubase, newnu); + BLI_addtail(&ucu->nubase, newnu); + + ucu->undo_size += ( + (nu->bezt ? (sizeof(BezTriple) * nu->pntsu) : 0) + + (nu->bp ? (sizeof(BPoint) * (nu->pntsu * nu->pntsv)) : 0) + + (nu->knotsu ? (sizeof(float) * KNOTSU(nu)) : 0) + + (nu->knotsv ? (sizeof(float) * KNOTSV(nu)) : 0) + + sizeof(Nurb)); } - undoCurve->actvert = cu->actvert; - undoCurve->actnu = cu->actnu; - undoCurve->flag = cu->flag; + ucu->actvert = cu->actvert; + ucu->actnu = cu->actnu; + ucu->flag = cu->flag; +} + +static void undocurve_free_data(UndoCurve *uc) +{ + BKE_nurbList_free(&uc->nubase); + + BKE_curve_editNurb_keyIndex_free(&uc->undoIndex); - return undoCurve; + free_fcurves(&uc->fcurves); + free_fcurves(&uc->drivers); } -static void free_undoCurve(void *ucv) +static Object *editcurve_object_from_context(bContext *C) { - UndoCurve *undoCurve = ucv; + Object *obedit = CTX_data_edit_object(C); + if (obedit && ELEM(obedit->type, OB_CURVE, OB_SURF)) { + Curve *cu = obedit->data; + if (BKE_curve_editNurbs_get(cu) != NULL) { + return obedit; + } + } + return NULL; +} - BKE_nurbList_free(&undoCurve->nubase); +/** \} */ - BKE_curve_editNurb_keyIndex_free(&undoCurve->undoIndex); +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ - free_fcurves(&undoCurve->fcurves); - free_fcurves(&undoCurve->drivers); +typedef struct CurveUndoStep { + UndoStep step; + /* note: will split out into list for multi-object-editmode. */ + UndoRefID_Object obedit_ref; + UndoCurve data; +} CurveUndoStep; - MEM_freeN(undoCurve); +static bool curve_undosys_poll(bContext *C) +{ + Object *obedit = editcurve_object_from_context(C); + return (obedit != NULL); } -static void *get_data(bContext *C) +static bool curve_undosys_step_encode(struct bContext *C, UndoStep *us_p) { - Object *obedit = CTX_data_edit_object(C); - return obedit; + CurveUndoStep *us = (CurveUndoStep *)us_p; + us->obedit_ref.ptr = editcurve_object_from_context(C); + undocurve_from_editcurve(&us->data, us->obedit_ref.ptr->data); + us->step.data_size = us->data.undo_size; + return true; } -/* and this is all the undo system needs to know */ -void undo_push_curve(bContext *C, const char *name) +static void curve_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) { - undo_editmode_push(C, name, get_data, free_undoCurve, undoCurve_to_editCurve, editCurve_to_undoCurve, NULL); + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_EDIT); + BLI_assert(curve_undosys_poll(C)); + + CurveUndoStep *us = (CurveUndoStep *)us_p; + Object *obedit = us->obedit_ref.ptr; + undocurve_to_editcurve(&us->data, obedit->data); + DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); } + +static void curve_undosys_step_free(UndoStep *us_p) +{ + CurveUndoStep *us = (CurveUndoStep *)us_p; + undocurve_free_data(&us->data); +} + +static void curve_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + CurveUndoStep *us = (CurveUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref)); +} + +/* Export for ED_undo_sys. */ +void ED_curve_undosys_type(UndoType *ut) +{ + ut->name = "Edit Curve"; + ut->poll = curve_undosys_poll; + ut->step_encode = curve_undosys_step_encode; + ut->step_decode = curve_undosys_step_decode; + ut->step_free = curve_undosys_step_free; + + ut->step_foreach_ID_ref = curve_undosys_foreach_ID_ref; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(CurveUndoStep); +} + +/** \} */ diff --git a/source/blender/editors/curve/editfont_undo.c b/source/blender/editors/curve/editfont_undo.c index a61f863b61e..3a76d0333f9 100644 --- a/source/blender/editors/curve/editfont_undo.c +++ b/source/blender/editors/curve/editfont_undo.c @@ -29,6 +29,8 @@ #include "MEM_guardedalloc.h" #include "BLI_utildefines.h" +#include "BLI_array_utils.h" + #include "DNA_curve_types.h" #include "DNA_object_types.h" @@ -36,10 +38,16 @@ #include "BKE_context.h" #include "BKE_font.h" +#include "BKE_depsgraph.h" +#include "BKE_undo_system.h" +#include "ED_object.h" #include "ED_curve.h" #include "ED_util.h" +#include "WM_types.h" +#include "WM_api.h" + #define USE_ARRAY_STORE #ifdef USE_ARRAY_STORE @@ -50,6 +58,10 @@ # define ARRAY_CHUNK_SIZE 32 #endif +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ + typedef struct UndoFont { wchar_t *textbuf; struct CharInfo *textbufinfo; @@ -62,6 +74,8 @@ typedef struct UndoFont { BArrayState *textbufinfo; } store; #endif + + size_t undo_size; } UndoFont; @@ -202,23 +216,20 @@ static void uf_arraystore_free(UndoFont *uf) BLI_array_store_at_size_clear(&uf_arraystore.bs_stride); } - } /** \} */ #endif /* USE_ARRAY_STORE */ -static void undoFont_to_editFont(void *uf_v, void *ecu, void *UNUSED(obdata)) +static void undofont_to_editfont(UndoFont *uf, Curve *cu) { - Curve *cu = (Curve *)ecu; EditFont *ef = cu->editfont; - const UndoFont *uf = uf_v; size_t final_size; #ifdef USE_ARRAY_STORE - uf_arraystore_expand(uf_v); + uf_arraystore_expand(uf); #endif final_size = sizeof(wchar_t) * (uf->len + 1); @@ -233,16 +244,17 @@ static void undoFont_to_editFont(void *uf_v, void *ecu, void *UNUSED(obdata)) ef->selstart = ef->selend = 0; #ifdef USE_ARRAY_STORE - uf_arraystore_expand_clear(uf_v); + uf_arraystore_expand_clear(uf); #endif } -static void *editFont_to_undoFont(void *ecu, void *UNUSED(obdata)) +static void *undofont_from_editfont(UndoFont *uf, Curve *cu) { - Curve *cu = (Curve *)ecu; + BLI_assert(BLI_array_is_zeroed(uf, 1)); + EditFont *ef = cu->editfont; - UndoFont *uf = MEM_callocN(sizeof(*uf), __func__); + size_t mem_used_prev = MEM_get_memory_in_use(); size_t final_size; @@ -269,13 +281,15 @@ static void *editFont_to_undoFont(void *ecu, void *UNUSED(obdata)) } #endif + size_t mem_used_curr = MEM_get_memory_in_use(); + + uf->undo_size = mem_used_prev < mem_used_curr ? mem_used_curr - mem_used_prev : sizeof(UndoFont); + return uf; } -static void free_undoFont(void *uf_v) +static void undofont_free_data(UndoFont *uf) { - UndoFont *uf = uf_v; - #ifdef USE_ARRAY_STORE { LinkData *link = BLI_findptr(&uf_arraystore.local_links, uf, offsetof(LinkData, data)); @@ -291,21 +305,91 @@ static void free_undoFont(void *uf_v) if (uf->textbufinfo) { MEM_freeN(uf->textbufinfo); } - - MEM_freeN(uf); } -static void *get_undoFont(bContext *C) +static Object *editfont_object_from_context(bContext *C) { Object *obedit = CTX_data_edit_object(C); if (obedit && obedit->type == OB_FONT) { - return obedit->data; + Curve *cu = obedit->data; + EditFont *ef = cu->editfont; + if (ef != NULL) { + return obedit; + } } return NULL; } -/* and this is all the undo system needs to know */ -void undo_push_font(bContext *C, const char *name) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct FontUndoStep { + UndoStep step; + /* note: will split out into list for multi-object-editmode. */ + UndoRefID_Object obedit_ref; + UndoFont data; +} FontUndoStep; + +static bool font_undosys_poll(bContext *C) +{ + return editfont_object_from_context(C) != NULL; +} + +static bool font_undosys_step_encode(struct bContext *C, UndoStep *us_p) +{ + FontUndoStep *us = (FontUndoStep *)us_p; + us->obedit_ref.ptr = editfont_object_from_context(C); + Curve *cu = us->obedit_ref.ptr->data; + undofont_from_editfont(&us->data, cu); + us->step.data_size = us->data.undo_size; + return true; +} + +static void font_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_EDIT); + BLI_assert(font_undosys_poll(C)); + + FontUndoStep *us = (FontUndoStep *)us_p; + Object *obedit = us->obedit_ref.ptr; + Curve *cu = obedit->data; + undofont_to_editfont(&us->data, cu); + DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); +} + +static void font_undosys_step_free(UndoStep *us_p) +{ + FontUndoStep *us = (FontUndoStep *)us_p; + undofont_free_data(&us->data); +} + +static void font_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) { - undo_editmode_push(C, name, get_undoFont, free_undoFont, undoFont_to_editFont, editFont_to_undoFont, NULL); + FontUndoStep *us = (FontUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref)); } + +/* Export for ED_undo_sys. */ +void ED_font_undosys_type(UndoType *ut) +{ + ut->name = "Edit Font"; + ut->poll = font_undosys_poll; + ut->step_encode = font_undosys_step_encode; + ut->step_decode = font_undosys_step_decode; + ut->step_free = font_undosys_step_free; + + ut->step_foreach_ID_ref = font_undosys_foreach_ID_ref; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(FontUndoStep); +} + +/** \} */ diff --git a/source/blender/editors/include/ED_armature.h b/source/blender/editors/include/ED_armature.h index 91c9a36c31f..241bcbea4af 100644 --- a/source/blender/editors/include/ED_armature.h +++ b/source/blender/editors/include/ED_armature.h @@ -49,6 +49,7 @@ struct Scene; struct ViewContext; struct wmKeyConfig; struct wmOperator; +struct UndoType; typedef struct EditBone { struct EditBone *next, *prev; @@ -139,7 +140,7 @@ bool ED_armature_select_pick(struct bContext *C, const int mval[2], bool extend, int join_armature_exec(struct bContext *C, struct wmOperator *op); struct Bone *get_indexed_bone(struct Object *ob, int index); float ED_rollBoneToVector(EditBone *bone, const float new_up_axis[3], const bool axis_only); -EditBone *ED_armature_bone_find_name(const ListBase *edbo, const char *name); +EditBone *ED_armature_bone_find_name(const struct ListBase *edbo, const char *name); EditBone *ED_armature_bone_get_mirrored(const struct ListBase *edbo, EditBone *ebo); void ED_armature_sync_selection(struct ListBase *edbo); void ED_armature_validate_active(struct bArmature *arm); @@ -178,8 +179,6 @@ void unique_editbone_name(struct ListBase *ebones, char *name, EditBone *bone); void ED_armature_bone_rename(struct bArmature *arm, const char *oldnamep, const char *newnamep); void ED_armature_bones_flip_names(struct bArmature *arm, struct ListBase *bones_names, const bool do_strip_numbers); -void undo_push_armature(struct bContext *C, const char *name); - /* low level selection functions which handle */ int ED_armature_ebone_selectflag_get(const EditBone *ebone); void ED_armature_ebone_selectflag_set(EditBone *ebone, int flag); @@ -187,6 +186,9 @@ void ED_armature_ebone_select_set(EditBone *ebone, bool select); void ED_armature_ebone_selectflag_enable(EditBone *ebone, int flag); void ED_armature_ebone_selectflag_disable(EditBone *ebone, int flag); +/* editarmature_undo.c */ +void ED_armature_undosys_type(struct UndoType *ut); + /* armature_utils.c */ void ED_armature_ebone_listbase_temp_clear(struct ListBase *lb); void ED_armature_ebone_listbase_free(struct ListBase *lb); diff --git a/source/blender/editors/include/ED_curve.h b/source/blender/editors/include/ED_curve.h index d45e52d4c5a..da726cb8000 100644 --- a/source/blender/editors/include/ED_curve.h +++ b/source/blender/editors/include/ED_curve.h @@ -41,6 +41,7 @@ struct Curve; struct EditNurb; struct BezTriple; struct BPoint; +struct UndoType; /* curve_ops.c */ void ED_operatortypes_curve(void); @@ -48,7 +49,7 @@ void ED_operatormacros_curve(void); void ED_keymap_curve(struct wmKeyConfig *keyconf); /* editcurve.c */ -ListBase *object_editcurve_get(struct Object *ob); +struct ListBase *object_editcurve_get(struct Object *ob); void ED_curve_editnurb_load(struct Object *obedit); void ED_curve_editnurb_make(struct Object *obedit); @@ -72,7 +73,7 @@ void ED_curve_select_all(struct EditNurb *editnurb); void ED_curve_select_swap(struct EditNurb *editnurb, bool hide_handles); /* editcurve_undo.c */ -void undo_push_curve(struct bContext *C, const char *name); +void ED_curve_undosys_type(struct UndoType *ut); /* editfont.c */ void ED_curve_editfont_load(struct Object *obedit); @@ -91,7 +92,8 @@ bool ED_curve_active_center(struct Curve *cu, float center[3]); bool ED_curve_editfont_select_pick(struct bContext *C, const int mval[2], bool extend, bool deselect, bool toggle); /* editfont_undo.c */ -void undo_push_font(struct bContext *C, const char *name); +void ED_font_undosys_type(struct UndoType *ut); + #if 0 /* debug only */ diff --git a/source/blender/editors/include/ED_lattice.h b/source/blender/editors/include/ED_lattice.h index b652fb4c00b..b30929f5307 100644 --- a/source/blender/editors/include/ED_lattice.h +++ b/source/blender/editors/include/ED_lattice.h @@ -31,6 +31,8 @@ #define __ED_LATTICE_H__ struct wmKeyConfig; +struct UndoType; +struct Object; /* lattice_ops.c */ void ED_operatortypes_lattice(void); @@ -41,6 +43,6 @@ void ED_lattice_flags_set(struct Object *obedit, int flag); bool ED_lattice_select_pick(struct bContext *C, const int mval[2], bool extend, bool deselect, bool toggle); /* editlattice_undo.c */ -void undo_push_lattice(struct bContext *C, const char *name); +void ED_lattice_undosys_type(struct UndoType *ut); #endif /* __ED_LATTICE_H__ */ diff --git a/source/blender/editors/include/ED_mball.h b/source/blender/editors/include/ED_mball.h index 232d7d1d234..9982c87a764 100644 --- a/source/blender/editors/include/ED_mball.h +++ b/source/blender/editors/include/ED_mball.h @@ -34,6 +34,7 @@ struct bContext; struct Object; struct wmKeyConfig; +struct UndoType; void ED_operatortypes_metaball(void); void ED_operatormacros_metaball(void); @@ -47,6 +48,7 @@ void ED_mball_editmball_free(struct Object *obedit); void ED_mball_editmball_make(struct Object *obedit); void ED_mball_editmball_load(struct Object *obedit); -void undo_push_mball(struct bContext *C, const char *name); +/* editmball_undo.c */ +void ED_mball_undosys_type(struct UndoType *ut); #endif /* __ED_MBALL_H__ */ diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 40d796aeed1..349a1944a23 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -62,6 +62,7 @@ struct UvMapVert; struct ToolSettings; struct Object; struct rcti; +struct UndoType; /* editmesh_utils.c */ void EDBM_verts_mirror_cache_begin_ex(struct BMEditMesh *em, const int axis, @@ -98,8 +99,6 @@ void EDBM_selectmode_flush(struct BMEditMesh *em); void EDBM_deselect_flush(struct BMEditMesh *em); void EDBM_select_flush(struct BMEditMesh *em); -void undo_push_mesh(struct bContext *C, const char *name); - bool EDBM_vert_color_check(struct BMEditMesh *em); void EDBM_mesh_hide(struct BMEditMesh *em, bool swap); @@ -130,6 +129,9 @@ void EDBM_flag_disable_all(struct BMEditMesh *em, const char hflag); bool BMBVH_EdgeVisible(struct BMBVHTree *tree, struct BMEdge *e, struct ARegion *ar, struct View3D *v3d, struct Object *obedit); +/* editmesh_undo.c */ +void ED_mesh_undosys_type(struct UndoType *ut); + /* editmesh_select.c */ void EDBM_select_mirrored( struct BMEditMesh *em, const int axis, const bool extend, diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h index 5b2fdf29dd5..7faf975ea91 100644 --- a/source/blender/editors/include/ED_object.h +++ b/source/blender/editors/include/ED_object.h @@ -196,6 +196,7 @@ void ED_object_constraint_dependency_tag_update(struct Main *bmain, struct Objec bool ED_object_mode_compat_test(const struct Object *ob, eObjectMode mode); bool ED_object_mode_compat_set(struct bContext *C, struct Object *ob, eObjectMode mode, struct ReportList *reports); void ED_object_mode_toggle(struct bContext *C, eObjectMode mode); +void ED_object_mode_set(struct bContext *C, eObjectMode mode); /* object_modifier.c */ enum { diff --git a/source/blender/editors/include/ED_paint.h b/source/blender/editors/include/ED_paint.h index 79aa0a3a5ed..246419d64aa 100644 --- a/source/blender/editors/include/ED_paint.h +++ b/source/blender/editors/include/ED_paint.h @@ -28,31 +28,16 @@ struct bContext; struct wmKeyConfig; struct wmOperator; +struct ImBuf; +struct Image; +struct UndoStep; +struct UndoType; /* paint_ops.c */ void ED_operatortypes_paint(void); void ED_operatormacros_paint(void); void ED_keymap_paint(struct wmKeyConfig *keyconf); -/* paint_undo.c */ -enum { - UNDO_PAINT_IMAGE = 0, - UNDO_PAINT_MESH = 1, -}; - -typedef void (*UndoRestoreCb)(struct bContext *C, struct ListBase *lb); -typedef void (*UndoFreeCb)(struct ListBase *lb); -typedef bool (*UndoCleanupCb)(struct bContext *C, struct ListBase *lb); - -int ED_undo_paint_step(struct bContext *C, int type, int step, const char *name); -void ED_undo_paint_step_num(struct bContext *C, int type, int num); -const char *ED_undo_paint_get_name(struct bContext *C, int type, int nr, bool *r_active); -void ED_undo_paint_free(void); -bool ED_undo_paint_is_valid(int type, const char *name); -bool ED_undo_paint_empty(int type); -void ED_undo_paint_push_begin(int type, const char *name, UndoRestoreCb restore, UndoFreeCb free, UndoCleanupCb cleanup); -void ED_undo_paint_push_end(int type); - /* paint_image.c */ void ED_imapaint_clear_partial_redraw(void); void ED_imapaint_dirty_region(struct Image *ima, struct ImBuf *ibuf, int x, int y, int w, int h, bool find_old); @@ -61,6 +46,14 @@ void ED_imapaint_bucket_fill(struct bContext *C, float color[3], struct wmOperat /* paint_image_undo.c */ void ED_image_undo_push_begin(const char *name); void ED_image_undo_push_end(void); -void ED_image_undo_restore(void); +void ED_image_undo_restore(struct UndoStep *us); + +void ED_image_undosys_type(struct UndoType *ut); + +/* paint_curve_undo.c */ +void ED_paintcurve_undo_push_begin(const char *name); +void ED_paintcurve_undo_push_end(void); + +void ED_paintcurve_undosys_type(struct UndoType *ut); #endif /* __ED_PAINT_H__ */ diff --git a/source/blender/editors/include/ED_particle.h b/source/blender/editors/include/ED_particle.h index 6cb8c0cfb19..fa5abb8e1dd 100644 --- a/source/blender/editors/include/ED_particle.h +++ b/source/blender/editors/include/ED_particle.h @@ -38,6 +38,7 @@ struct ParticleEditSettings; struct rcti; struct PTCacheEdit; struct Scene; +struct UndoType; /* particle edit mode */ void PE_free_ptcache_edit(struct PTCacheEdit *edit); @@ -61,14 +62,10 @@ int PE_circle_select(struct bContext *C, int selecting, const int mval[2], float int PE_lasso_select(struct bContext *C, const int mcords[][2], const short moves, bool extend, bool select); void PE_deselect_all_visible(struct PTCacheEdit *edit); -/* undo */ +/* particle_edit_undo.c */ +void ED_particle_undosys_type(struct UndoType *ut); + void PE_undo_push(struct Scene *scene, const char *str); -void PE_undo_step(struct Scene *scene, int step); -void PE_undo(struct Scene *scene); -void PE_redo(struct Scene *scene); -bool PE_undo_is_valid(struct Scene *scene); -void PE_undo_number(struct Scene *scene, int nr); -const char *PE_undo_get_name(struct Scene *scene, int nr, bool *r_active); #endif /* __ED_PARTICLE_H__ */ diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h index 6daaac5bb42..7c17e7b68c3 100644 --- a/source/blender/editors/include/ED_sculpt.h +++ b/source/blender/editors/include/ED_sculpt.h @@ -36,6 +36,9 @@ struct Object; struct RegionView3D; struct ViewContext; struct rcti; +struct UndoStep; +struct UndoType; +struct ListBase; /* sculpt.c */ void ED_operatortypes_sculpt(void); @@ -43,4 +46,7 @@ void ED_sculpt_redraw_planes_get(float planes[4][4], struct ARegion *ar, struct RegionView3D *rv3d, struct Object *ob); int ED_sculpt_mask_box_select(struct bContext *C, struct ViewContext *vc, const struct rcti *rect, bool select, bool extend); +/* sculpt_undo.c */ +void ED_sculpt_undosys_type(struct UndoType *ut); + #endif /* __ED_SCULPT_H__ */ diff --git a/source/blender/editors/include/ED_text.h b/source/blender/editors/include/ED_text.h index 5df7d9cfaef..5517e50aef4 100644 --- a/source/blender/editors/include/ED_text.h +++ b/source/blender/editors/include/ED_text.h @@ -30,12 +30,13 @@ #ifndef __ED_TEXT_H__ #define __ED_TEXT_H__ -struct bContext; struct SpaceText; struct ARegion; +struct UndoType; -void ED_text_undo_step(struct bContext *C, int step); bool ED_text_region_location_from_cursor(struct SpaceText *st, struct ARegion *ar, const int cursor_co[2], int r_pixel_co[2]); -#endif /* __ED_TEXT_H__ */ +/* text_undo.c */ +void ED_text_undosys_type(struct UndoType *ut); +#endif /* __ED_TEXT_H__ */ diff --git a/source/blender/editors/include/ED_util.h b/source/blender/editors/include/ED_util.h index 60c4b3593aa..5a373cebac1 100644 --- a/source/blender/editors/include/ED_util.h +++ b/source/blender/editors/include/ED_util.h @@ -35,6 +35,9 @@ struct bContext; struct SpaceLink; struct wmOperator; struct wmOperatorType; +struct UndoStack; +struct ScrArea; +struct PackedFile; /* ed_util.c */ @@ -70,16 +73,12 @@ void ED_undo_operator_repeat_cb_evt(struct bContext *C, void *arg_op, int arg bool ED_undo_is_valid(const struct bContext *C, const char *undoname); -/* undo_editmode.c */ -void undo_editmode_push(struct bContext *C, const char *name, - void * (*getdata)(struct bContext *C), - void (*freedata)(void *), - void (*to_editmode)(void *, void *, void *), - void *(*from_editmode)(void *, void *), - int (*validate_undo)(void *, void *)); +/* undo_system_types.c */ +void ED_undosys_type_init(void); +void ED_undosys_type_free(void); - -void undo_editmode_clear(void); +/* memfile_undo.c */ +struct MemFile *ED_undosys_stack_memfile_get_active(struct UndoStack *ustack); /* ************** XXX OLD CRUFT WARNING ************* */ diff --git a/source/blender/editors/lattice/editlattice_undo.c b/source/blender/editors/lattice/editlattice_undo.c index aa817928f92..7a7372f5a6a 100644 --- a/source/blender/editors/lattice/editlattice_undo.c +++ b/source/blender/editors/lattice/editlattice_undo.c @@ -34,6 +34,7 @@ #include "MEM_guardedalloc.h" #include "BLI_utildefines.h" +#include "BLI_array_utils.h" #include "DNA_curve_types.h" #include "DNA_lattice_types.h" @@ -41,31 +42,39 @@ #include "DNA_scene_types.h" #include "BKE_context.h" +#include "BKE_depsgraph.h" +#include "BKE_undo_system.h" +#include "ED_object.h" #include "ED_lattice.h" #include "ED_util.h" +#include "WM_types.h" +#include "WM_api.h" + #include "lattice_intern.h" +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ + typedef struct UndoLattice { BPoint *def; int pntsu, pntsv, pntsw, actbp; + size_t undo_size; } UndoLattice; -static void undoLatt_to_editLatt(void *data, void *edata, void *UNUSED(obdata)) +static void undolatt_to_editlatt(UndoLattice *ult, EditLatt *editlatt) { - UndoLattice *ult = (UndoLattice *)data; - EditLatt *editlatt = (EditLatt *)edata; - int a = editlatt->latt->pntsu * editlatt->latt->pntsv * editlatt->latt->pntsw; + int len = editlatt->latt->pntsu * editlatt->latt->pntsv * editlatt->latt->pntsw; - memcpy(editlatt->latt->def, ult->def, a * sizeof(BPoint)); + memcpy(editlatt->latt->def, ult->def, sizeof(BPoint) * len); editlatt->latt->actbp = ult->actbp; } -static void *editLatt_to_undoLatt(void *edata, void *UNUSED(obdata)) +static void *undolatt_from_editlatt(UndoLattice *ult, EditLatt *editlatt) { - UndoLattice *ult = MEM_callocN(sizeof(UndoLattice), "UndoLattice"); - EditLatt *editlatt = (EditLatt *)edata; + BLI_assert(BLI_array_is_zeroed(ult, 1)); ult->def = MEM_dupallocN(editlatt->latt->def); ult->pntsu = editlatt->latt->pntsu; @@ -73,17 +82,19 @@ static void *editLatt_to_undoLatt(void *edata, void *UNUSED(obdata)) ult->pntsw = editlatt->latt->pntsw; ult->actbp = editlatt->latt->actbp; + ult->undo_size += sizeof(*ult->def) * ult->pntsu * ult->pntsv * ult->pntsw; + return ult; } -static void free_undoLatt(void *data) +static void undolatt_free_data(UndoLattice *ult) { - UndoLattice *ult = (UndoLattice *)data; - - if (ult->def) MEM_freeN(ult->def); - MEM_freeN(ult); + if (ult->def) { + MEM_freeN(ult->def); + } } +#if 0 static int validate_undoLatt(void *data, void *edata) { UndoLattice *ult = (UndoLattice *)data; @@ -93,21 +104,92 @@ static int validate_undoLatt(void *data, void *edata) ult->pntsv == editlatt->latt->pntsv && ult->pntsw == editlatt->latt->pntsw); } +#endif -static void *get_editlatt(bContext *C) +static Object *editlatt_object_from_context(bContext *C) { Object *obedit = CTX_data_edit_object(C); - if (obedit && obedit->type == OB_LATTICE) { Lattice *lt = obedit->data; - return lt->editlatt; + if (lt->editlatt != NULL) { + return obedit; + } } return NULL; } -/* and this is all the undo system needs to know */ -void undo_push_lattice(bContext *C, const char *name) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct LatticeUndoStep { + UndoStep step; + /* note: will split out into list for multi-object-editmode. */ + UndoRefID_Object obedit_ref; + UndoLattice data; +} LatticeUndoStep; + +static bool lattice_undosys_poll(bContext *C) +{ + return editlatt_object_from_context(C) != NULL; +} + +static bool lattice_undosys_step_encode(struct bContext *C, UndoStep *us_p) { - undo_editmode_push(C, name, get_editlatt, free_undoLatt, undoLatt_to_editLatt, editLatt_to_undoLatt, validate_undoLatt); + LatticeUndoStep *us = (LatticeUndoStep *)us_p; + us->obedit_ref.ptr = editlatt_object_from_context(C); + Lattice *lt = us->obedit_ref.ptr->data; + undolatt_from_editlatt(&us->data, lt->editlatt); + us->step.data_size = us->data.undo_size; + return true; } + +static void lattice_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_EDIT); + BLI_assert(lattice_undosys_poll(C)); + + LatticeUndoStep *us = (LatticeUndoStep *)us_p; + Object *obedit = us->obedit_ref.ptr; + Lattice *lt = obedit->data; + EditLatt *editlatt = lt->editlatt; + undolatt_to_editlatt(&us->data, editlatt); + DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); +} + +static void lattice_undosys_step_free(UndoStep *us_p) +{ + LatticeUndoStep *us = (LatticeUndoStep *)us_p; + undolatt_free_data(&us->data); +} + +static void lattice_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + LatticeUndoStep *us = (LatticeUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref)); +} + +/* Export for ED_undo_sys. */ +void ED_lattice_undosys_type(UndoType *ut) +{ + ut->name = "Edit Lattice"; + ut->poll = lattice_undosys_poll; + ut->step_encode = lattice_undosys_step_encode; + ut->step_decode = lattice_undosys_step_decode; + ut->step_free = lattice_undosys_step_free; + + ut->step_foreach_ID_ref = lattice_undosys_foreach_ID_ref; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(LatticeUndoStep); +} + +/** \} */ diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index ab91f4b34c7..220f9cc0287 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -29,16 +29,24 @@ #include "DNA_key_types.h" #include "BLI_listbase.h" +#include "BLI_array_utils.h" +#include "BLI_alloca.h" #include "BKE_DerivedMesh.h" #include "BKE_context.h" #include "BKE_key.h" #include "BKE_mesh.h" #include "BKE_editmesh.h" +#include "BKE_depsgraph.h" +#include "BKE_undo_system.h" +#include "ED_object.h" #include "ED_mesh.h" #include "ED_util.h" +#include "WM_types.h" +#include "WM_api.h" + #define USE_ARRAY_STORE #ifdef USE_ARRAY_STORE @@ -60,6 +68,9 @@ # include "BLI_task.h" #endif +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ #ifdef USE_ARRAY_STORE @@ -95,6 +106,8 @@ typedef struct UndoMesh { BArrayState *mselect; } store; #endif /* USE_ARRAY_STORE */ + + size_t undo_size; } UndoMesh; @@ -474,23 +487,17 @@ static void um_arraystore_free(UndoMesh *um) /* for callbacks */ /* undo simply makes copies of a bmesh */ -static void *editbtMesh_to_undoMesh(void *emv, void *obdata) +static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key) { - + BLI_assert(BLI_array_is_zeroed(um, 1)); #ifdef USE_ARRAY_STORE_THREAD /* changes this waits is low, but must have finished */ if (um_arraystore.task_pool) { BLI_task_pool_work_and_wait(um_arraystore.task_pool); } #endif - - BMEditMesh *em = emv; - Mesh *obme = obdata; - - UndoMesh *um = MEM_callocN(sizeof(UndoMesh), "undo Mesh"); - /* make sure shape keys work */ - um->me.key = obme->key ? BKE_key_copy_nolib(obme->key) : NULL; + um->me.key = key ? BKE_key_copy_nolib(key) : NULL; /* BM_mesh_validate(em->bm); */ /* for troubleshooting */ @@ -536,13 +543,12 @@ static void *editbtMesh_to_undoMesh(void *emv, void *obdata) return um; } -static void undoMesh_to_editbtMesh(void *um_v, void *em_v, void *obdata) +static void undomesh_to_editmesh(UndoMesh *um, BMEditMesh *em, Mesh *obmesh) { - BMEditMesh *em = em_v, *em_tmp; + BMEditMesh *em_tmp; Object *ob = em->ob; - UndoMesh *um = um_v; BMesh *bm; - Key *key = ((Mesh *) obdata)->key; + Key *key = obmesh->key; #ifdef USE_ARRAY_STORE #ifdef USE_ARRAY_STORE_THREAD @@ -615,9 +621,8 @@ static void undoMesh_to_editbtMesh(void *um_v, void *em_v, void *obdata) #endif } -static void free_undo(void *um_v) +static void undomesh_free_data(UndoMesh *um) { - UndoMesh *um = um_v; Mesh *me = &um->me; #ifdef USE_ARRAY_STORE @@ -644,28 +649,176 @@ static void free_undo(void *um_v) } BKE_mesh_free(me); - MEM_freeN(me); } -static void *getEditMesh(bContext *C) +static Object *editmesh_object_from_context(bContext *C) { Object *obedit = CTX_data_edit_object(C); if (obedit && obedit->type == OB_MESH) { Mesh *me = obedit->data; - return me->edit_btmesh; + if (me->edit_btmesh != NULL) { + return obedit; + } } return NULL; } -/* and this is all the undo system needs to know */ -void undo_push_mesh(bContext *C, const char *name) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct MeshUndoStep { + UndoStep step; + /* Use for all ID lookups (can be NULL). */ + struct UndoIDPtrMap *id_map; + + /* note: will split out into list for multi-object-editmode. */ + UndoRefID_Object obedit_ref; + /* Needed for MTexPoly's image use. */ + UndoRefID_Object *image_array_ref; + UndoMesh data; +} MeshUndoStep; + +static void mesh_undosys_step_encode_store_ids(MeshUndoStep *us) +{ + Mesh *me = us->obedit_ref.ptr->data; + BMesh *bm = me->edit_btmesh->bm; + const int mtex_len = CustomData_number_of_layers(&bm->pdata, CD_MTEXPOLY); + if (mtex_len != 0) { + ID **id_src = BLI_array_alloca(id_src, mtex_len); + memset(id_src, 0x0, sizeof(*id_src) * mtex_len); + + BMIter iter; + BMFace *efa; + + if (us->id_map == NULL) { + us->id_map = BKE_undosys_ID_map_create(); + } + + uint cd_poly_tex_offset_first = CustomData_get_n_offset(&bm->pdata, CD_MTEXPOLY, 0); + uint cd_poly_tex_offset_end = cd_poly_tex_offset_first + (sizeof(MTexPoly) * mtex_len); + BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) { + for (uint cd_poly_tex_offset = cd_poly_tex_offset_first, i = 0; + cd_poly_tex_offset < cd_poly_tex_offset_end; + cd_poly_tex_offset += sizeof(MTexPoly), i++) + { + const MTexPoly *tf = BM_ELEM_CD_GET_VOID_P(efa, cd_poly_tex_offset); + if (tf->tpage != NULL) { + if ((ID *)tf->tpage != id_src[i]) { + BKE_undosys_ID_map_add(us->id_map, (ID *)tf->tpage); + id_src[i] = (ID *)tf->tpage; + } + } + } + } + } +} + +static void mesh_undosys_step_decode_restore_ids(MeshUndoStep *us) { - /* em->ob gets out of date and crashes on mesh undo, - * this is an easy way to ensure its OK - * though we could investigate the matter further. */ - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = BKE_editmesh_from_object(obedit); - em->ob = obedit; + Mesh *me = us->obedit_ref.ptr->data; + BMesh *bm = me->edit_btmesh->bm; + const int mtex_len = CustomData_number_of_layers(&bm->pdata, CD_MTEXPOLY); + if (mtex_len != 0 && us->id_map) { + BMIter iter; + BMFace *efa; + + ID **id_dst = BLI_array_alloca(id_dst, mtex_len); + ID **id_src = BLI_array_alloca(id_src, mtex_len); + memset(id_src, 0x0, sizeof(*id_src) * mtex_len); + + uint cd_poly_tex_offset_first = CustomData_get_n_offset(&bm->pdata, CD_MTEXPOLY, 0); + uint cd_poly_tex_offset_end = cd_poly_tex_offset_first + (sizeof(MTexPoly) * mtex_len); + BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) { + for (uint cd_poly_tex_offset = cd_poly_tex_offset_first, i = 0; + cd_poly_tex_offset < cd_poly_tex_offset_end; + cd_poly_tex_offset += sizeof(MTexPoly), i++) + { + MTexPoly *tf = BM_ELEM_CD_GET_VOID_P(efa, cd_poly_tex_offset); + if (tf->tpage != NULL) { + if ((ID *)tf->tpage == id_src[i]) { + tf->tpage = (Image *)id_dst[i]; + } + else { + id_src[i] = (ID *)tf->tpage; + tf->tpage = (Image *)BKE_undosys_ID_map_lookup(us->id_map, (ID *)tf->tpage); + id_dst[i] = (ID *)tf->tpage; + } + } + } + } + } +} + +static bool mesh_undosys_poll(bContext *C) +{ + return editmesh_object_from_context(C) != NULL; +} + +static bool mesh_undosys_step_encode(struct bContext *C, UndoStep *us_p) +{ + MeshUndoStep *us = (MeshUndoStep *)us_p; + us->obedit_ref.ptr = editmesh_object_from_context(C); + Mesh *me = us->obedit_ref.ptr->data; + undomesh_from_editmesh(&us->data, me->edit_btmesh, me->key); + mesh_undosys_step_encode_store_ids(us); + us->step.data_size = us->data.undo_size; + return true; +} + +static void mesh_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_EDIT); + BLI_assert(mesh_undosys_poll(C)); + + MeshUndoStep *us = (MeshUndoStep *)us_p; + Object *obedit = us->obedit_ref.ptr; + Mesh *me = obedit->data; + BMEditMesh *em = me->edit_btmesh; + undomesh_to_editmesh(&us->data, em, obedit->data); + mesh_undosys_step_decode_restore_ids(us); + DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); +} + +static void mesh_undosys_step_free(UndoStep *us_p) +{ + MeshUndoStep *us = (MeshUndoStep *)us_p; + undomesh_free_data(&us->data); + + if (us->id_map != NULL) { + BKE_undosys_ID_map_destroy(us->id_map); + } +} + +static void mesh_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + MeshUndoStep *us = (MeshUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref)); + if (us->id_map != NULL) { + BKE_undosys_ID_map_foreach_ID_ref(us->id_map, foreach_ID_ref_fn, user_data); + } +} + +/* Export for ED_undo_sys. */ +void ED_mesh_undosys_type(UndoType *ut) +{ + ut->name = "Edit Mesh"; + ut->poll = mesh_undosys_poll; + ut->step_encode = mesh_undosys_step_encode; + ut->step_decode = mesh_undosys_step_decode; + ut->step_free = mesh_undosys_step_free; + + ut->step_foreach_ID_ref = mesh_undosys_foreach_ID_ref; - undo_editmode_push(C, name, getEditMesh, free_undo, undoMesh_to_editbtMesh, editbtMesh_to_undoMesh, NULL); + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(MeshUndoStep); } + +/** \} */ diff --git a/source/blender/editors/metaball/editmball_undo.c b/source/blender/editors/metaball/editmball_undo.c index 974bfb237d3..dc64f61a916 100644 --- a/source/blender/editors/metaball/editmball_undo.c +++ b/source/blender/editors/metaball/editmball_undo.c @@ -29,19 +29,31 @@ #include "BLI_utildefines.h" #include "BLI_listbase.h" +#include "BLI_array_utils.h" #include "DNA_defs.h" #include "DNA_meta_types.h" #include "DNA_object_types.h" #include "BKE_context.h" +#include "BKE_depsgraph.h" +#include "BKE_undo_system.h" +#include "ED_object.h" #include "ED_mball.h" #include "ED_util.h" +#include "WM_types.h" +#include "WM_api.h" + +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ + typedef struct UndoMBall { ListBase editelems; int lastelem_index; + size_t undo_size; } UndoMBall; /* free all MetaElems from ListBase */ @@ -58,11 +70,8 @@ static void freeMetaElemlist(ListBase *lb) } } -static void undoMball_to_editMball(void *umb_v, void *mb_v, void *UNUSED(obdata)) +static void undomball_to_editmball(UndoMBall *umb, MetaBall *mb) { - MetaBall *mb = mb_v; - UndoMBall *umb = umb_v; - freeMetaElemlist(mb->editelems); mb->lastelem = NULL; @@ -75,18 +84,15 @@ static void undoMball_to_editMball(void *umb_v, void *mb_v, void *UNUSED(obdata) mb->lastelem = ml_edit; } } - } -static void *editMball_to_undoMball(void *mb_v, void *UNUSED(obdata)) +static void *editmball_from_undomball(UndoMBall *umb, MetaBall *mb) { - MetaBall *mb = mb_v; - UndoMBall *umb; + BLI_assert(BLI_array_is_zeroed(umb, 1)); /* allocate memory for undo ListBase */ - umb = MEM_callocN(sizeof(UndoMBall), __func__); umb->lastelem_index = -1; - + /* copy contents of current ListBase to the undo ListBase */ int index = 0; for (MetaElem *ml_edit = mb->editelems->first; ml_edit; ml_edit = ml_edit->next, index += 1) { @@ -95,37 +101,99 @@ static void *editMball_to_undoMball(void *mb_v, void *UNUSED(obdata)) if (ml_edit == mb->lastelem) { umb->lastelem_index = index; } + umb->undo_size += sizeof(MetaElem); } - + return umb; } /* free undo ListBase of MetaElems */ -static void free_undoMball(void *umb_v) +static void undomball_free_data(UndoMBall *umb) { - UndoMBall *umb = umb_v; - freeMetaElemlist(&umb->editelems); - MEM_freeN(umb); } -static MetaBall *metaball_get_obdata(Object *ob) +static Object *editmball_object_from_context(bContext *C) { - if (ob && ob->type == OB_MBALL) { - return ob->data; + Object *obedit = CTX_data_edit_object(C); + if (obedit && obedit->type == OB_MBALL) { + MetaBall *mb = obedit->data; + if (mb->editelems != NULL) { + return obedit; + } } return NULL; } +/** \} */ -static void *get_data(bContext *C) +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct MBallUndoStep { + UndoStep step; + /* note: will split out into list for multi-object-editmode. */ + UndoRefID_Object obedit_ref; + UndoMBall data; +} MBallUndoStep; + +static bool mball_undosys_poll(bContext *C) { - Object *obedit = CTX_data_edit_object(C); - return metaball_get_obdata(obedit); + return editmball_object_from_context(C) != NULL; } -/* this is undo system for MetaBalls */ -void undo_push_mball(bContext *C, const char *name) +static bool mball_undosys_step_encode(struct bContext *C, UndoStep *us_p) { - undo_editmode_push(C, name, get_data, free_undoMball, undoMball_to_editMball, editMball_to_undoMball, NULL); + MBallUndoStep *us = (MBallUndoStep *)us_p; + us->obedit_ref.ptr = editmball_object_from_context(C); + MetaBall *mb = us->obedit_ref.ptr->data; + editmball_from_undomball(&us->data, mb); + us->step.data_size = us->data.undo_size; + return true; } + +static void mball_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + ED_object_mode_set(C, OB_MODE_EDIT); + + MBallUndoStep *us = (MBallUndoStep *)us_p; + Object *obedit = us->obedit_ref.ptr; + MetaBall *mb = obedit->data; + undomball_to_editmball(&us->data, mb); + DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); +} + +static void mball_undosys_step_free(UndoStep *us_p) +{ + MBallUndoStep *us = (MBallUndoStep *)us_p; + undomball_free_data(&us->data); +} + +static void mball_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + MBallUndoStep *us = (MBallUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref)); +} + +/* Export for ED_undo_sys. */ +void ED_mball_undosys_type(UndoType *ut) +{ + ut->name = "Edit MBall"; + ut->poll = mball_undosys_poll; + ut->step_encode = mball_undosys_step_encode; + ut->step_decode = mball_undosys_step_decode; + ut->step_free = mball_undosys_step_free; + + ut->step_foreach_ID_ref = mball_undosys_foreach_ID_ref; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(MBallUndoStep); + +} + +/** \} */ diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index f67ecbce1d0..cbdc9685f2c 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -85,6 +85,7 @@ #include "BKE_editlattice.h" #include "BKE_editmesh.h" #include "BKE_report.h" +#include "BKE_undo_system.h" #include "ED_armature.h" #include "ED_curve.h" diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index fda342f8a4d..d70a69e30f8 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -136,3 +136,43 @@ void ED_object_mode_toggle(bContext *C, eObjectMode mode) } } } + +/* Wrapper for operator */ +void ED_object_mode_set(bContext *C, eObjectMode mode) +{ +#if 0 + wmOperatorType *ot = WM_operatortype_find("OBJECT_OT_mode_set", false); + PointerRNA ptr; + + WM_operator_properties_create_ptr(&ptr, ot); + RNA_enum_set(&ptr, "mode", mode); + RNA_boolean_set(&ptr, "toggle", false); + WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &ptr); + WM_operator_properties_free(&ptr); +#else + Object *ob = CTX_data_active_object(C); + if (ob == NULL) { + return; + } + if (ob->mode == mode) { + /* pass */ + } + else if (mode != OB_MODE_OBJECT) { + if (ob && (ob->mode & mode) == 0) { + /* needed so we don't do undo pushes. */ + wmWindowManager *wm = CTX_wm_manager(C); + wm->op_undo_depth++; + ED_object_mode_toggle(C, mode); + wm->op_undo_depth--; + } + } + else { + /* needed so we don't do undo pushes. */ + wmWindowManager *wm = CTX_wm_manager(C); + wm->op_undo_depth++; + ED_object_mode_toggle(C, ob->mode); + wm->op_undo_depth--; + + } +#endif +} diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c index 93bfd156707..fce43fa6425 100644 --- a/source/blender/editors/physics/particle_edit.c +++ b/source/blender/editors/physics/particle_edit.c @@ -126,8 +126,6 @@ void PE_free_ptcache_edit(PTCacheEdit *edit) if (edit==0) return; - PTCacheUndo_clear(edit); - if (edit->points) { LOOP_POINTS { if (point->keys) @@ -4380,8 +4378,11 @@ void PE_create_particle_edit(Scene *scene, Object *ob, PointCache *cache, Partic recalc_emitter_field(ob, psys); PE_update_object(scene, ob, 1); - PTCacheUndo_clear(edit); + /* Causes issues, adding undo pushes while performing undo history. + * Seems not to like this isn't needed anyway - Campbell. */ +#if 0 PE_undo_push(scene, "Original"); +#endif } } diff --git a/source/blender/editors/physics/particle_edit_undo.c b/source/blender/editors/physics/particle_edit_undo.c index 288e59a8671..329658a56e1 100644 --- a/source/blender/editors/physics/particle_edit_undo.c +++ b/source/blender/editors/physics/particle_edit_undo.c @@ -38,6 +38,7 @@ #include "DNA_scene_types.h" #include "DNA_meshdata_types.h" +#include "DNA_windowmanager_types.h" #include "BLI_listbase.h" #include "BLI_string.h" @@ -47,38 +48,29 @@ #include "BKE_global.h" #include "BKE_particle.h" #include "BKE_pointcache.h" +#include "BKE_context.h" +#include "BKE_main.h" +#include "BKE_undo_system.h" +#include "ED_object.h" #include "ED_particle.h" +#include "ED_physics.h" #include "particle_edit_utildefines.h" #include "physics_intern.h" -static void free_PTCacheUndo(PTCacheUndo *undo) -{ - PTCacheEditPoint *point; - int i; - - for (i=0, point=undo->points; itotpoint; i++, point++) { - if (undo->particles && (undo->particles + i)->hair) - MEM_freeN((undo->particles + i)->hair); - if (point->keys) - MEM_freeN(point->keys); - } - if (undo->points) - MEM_freeN(undo->points); +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ - if (undo->particles) - MEM_freeN(undo->particles); - - BKE_ptcache_free_mem(&undo->mem_cache); -} - -static void make_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) +static void undoptcache_from_editcache(PTCacheUndo *undo, PTCacheEdit *edit) { PTCacheEditPoint *point; int i; + size_t mem_used_prev = MEM_get_memory_in_use(); + undo->totpoint= edit->totpoint; if (edit->psys) { @@ -86,8 +78,9 @@ static void make_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) pa= undo->particles= MEM_dupallocN(edit->psys->particles); - for (i=0; itotpoint; i++, pa++) + for (i=0; itotpoint; i++, pa++) { pa->hair= MEM_dupallocN(pa->hair); + } undo->psys_flag = edit->psys->flag; } @@ -98,8 +91,9 @@ static void make_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) pm = undo->mem_cache.first; for (; pm; pm=pm->next) { - for (i=0; idata[i] = MEM_dupallocN(pm->data[i]); + } } } @@ -110,9 +104,13 @@ static void make_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) point->keys= MEM_dupallocN(point->keys); /* no need to update edit key->co & key->time pointers here */ } + + size_t mem_used_curr = MEM_get_memory_in_use(); + + undo->undo_size = mem_used_prev < mem_used_curr ? mem_used_curr - mem_used_prev : sizeof(PTCacheUndo); } -static void get_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) +static void undoptcache_to_editcache(PTCacheUndo *undo, PTCacheEdit *edit) { ParticleSystem *psys = edit->psys; ParticleData *pa; @@ -120,16 +118,20 @@ static void get_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) POINT_P; KEY_K; LOOP_POINTS { - if (psys && psys->particles[p].hair) + if (psys && psys->particles[p].hair) { MEM_freeN(psys->particles[p].hair); + } - if (point->keys) + if (point->keys) { MEM_freeN(point->keys); + } } - if (psys && psys->particles) + if (psys && psys->particles) { MEM_freeN(psys->particles); - if (edit->points) + } + if (edit->points) { MEM_freeN(edit->points); + } if (edit->mirror_cache) { MEM_freeN(edit->mirror_cache); edit->mirror_cache= NULL; @@ -171,9 +173,9 @@ static void get_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) pm = edit->pid.cache->mem_cache.first; for (; pm; pm=pm->next) { - for (i=0; idata[i] = MEM_dupallocN(pm->data[i]); - + } BKE_ptcache_mem_pointers_init(pm); LOOP_POINTS { @@ -191,150 +193,122 @@ static void get_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo) } } -void PE_undo_push(Scene *scene, const char *str) +static void undoptcache_free_data(PTCacheUndo *undo) { - PTCacheEdit *edit= PE_get_current(scene, OBACT); - PTCacheUndo *undo; - int nr; - - if (!edit) return; - - /* remove all undos after (also when curundo==NULL) */ - while (edit->undo.last != edit->curundo) { - undo= edit->undo.last; - BLI_remlink(&edit->undo, undo); - free_PTCacheUndo(undo); - MEM_freeN(undo); - } + PTCacheEditPoint *point; + int i; - /* make new */ - edit->curundo= undo= MEM_callocN(sizeof(PTCacheUndo), "particle undo file"); - BLI_strncpy(undo->name, str, sizeof(undo->name)); - BLI_addtail(&edit->undo, undo); - - /* and limit amount to the maximum */ - nr= 0; - undo= edit->undo.last; - while (undo) { - nr++; - if (nr==U.undosteps) break; - undo= undo->prev; - } - if (undo) { - while (edit->undo.first != undo) { - PTCacheUndo *first= edit->undo.first; - BLI_remlink(&edit->undo, first); - free_PTCacheUndo(first); - MEM_freeN(first); + for (i = 0, point=undo->points; i < undo->totpoint; i++, point++) { + if (undo->particles && (undo->particles + i)->hair) { + MEM_freeN((undo->particles + i)->hair); + } + if (point->keys) { + MEM_freeN(point->keys); } } - - /* copy */ - make_PTCacheUndo(edit, edit->curundo); + if (undo->points) { + MEM_freeN(undo->points); + } + if (undo->particles) { + MEM_freeN(undo->particles); + } + BKE_ptcache_free_mem(&undo->mem_cache); } -void PE_undo_step(Scene *scene, int step) -{ - PTCacheEdit *edit= PE_get_current(scene, OBACT); +/** \} */ - if (!edit) return; +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ - if (step==0) { - get_PTCacheUndo(edit, edit->curundo); - } - else if (step==1) { - - if (edit->curundo==NULL || edit->curundo->prev==NULL) { - /* pass */ - } - else { - if (G.debug & G_DEBUG) printf("undo %s\n", edit->curundo->name); - edit->curundo= edit->curundo->prev; - get_PTCacheUndo(edit, edit->curundo); - } - } - else { - /* curundo has to remain current situation! */ - - if (edit->curundo==NULL || edit->curundo->next==NULL) { - /* pass */ - } - else { - get_PTCacheUndo(edit, edit->curundo->next); - edit->curundo= edit->curundo->next; - if (G.debug & G_DEBUG) printf("redo %s\n", edit->curundo->name); - } - } +typedef struct ParticleUndoStep { + UndoStep step; + UndoRefID_Scene scene_ref; + UndoRefID_Object object_ref; + PTCacheUndo data; +} ParticleUndoStep; - DAG_id_tag_update(&OBACT->id, OB_RECALC_DATA); +static bool particle_undosys_poll(struct bContext *C) +{ + Scene *scene = CTX_data_scene(C); + Object *ob = OBACT; + PTCacheEdit *edit = PE_get_current(scene, ob); + return (edit != NULL); } -bool PE_undo_is_valid(Scene *scene) +static bool particle_undosys_step_encode(struct bContext *C, UndoStep *us_p) { - PTCacheEdit *edit= PE_get_current(scene, OBACT); - - if (edit) { - return (edit->undo.last != edit->undo.first); - } - return 0; + ParticleUndoStep *us = (ParticleUndoStep *)us_p; + us->scene_ref.ptr = CTX_data_scene(C); + us->object_ref.ptr = us->scene_ref.ptr->basact->object; + PTCacheEdit *edit = PE_get_current(us->scene_ref.ptr, us->object_ref.ptr); + undoptcache_from_editcache(&us->data, edit); + return true; } -void PTCacheUndo_clear(PTCacheEdit *edit) +static void particle_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) { - PTCacheUndo *undo; - - if (edit==NULL) return; - - undo= edit->undo.first; - while (undo) { - free_PTCacheUndo(undo); - undo= undo->next; + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_PARTICLE_EDIT); + BLI_assert(particle_undosys_poll(C)); + + ParticleUndoStep *us = (ParticleUndoStep *)us_p; + Scene *scene = us->scene_ref.ptr; + Object *ob = us->object_ref.ptr; + PTCacheEdit *edit = PE_get_current(scene, ob); + if (edit) { + undoptcache_to_editcache(&us->data, edit); + DAG_id_tag_update(&ob->id, OB_RECALC_DATA); + } + else { + BLI_assert(0); } - BLI_freelistN(&edit->undo); - edit->curundo= NULL; } -void PE_undo(Scene *scene) +static void particle_undosys_step_free(UndoStep *us_p) { - PE_undo_step(scene, 1); + ParticleUndoStep *us = (ParticleUndoStep *)us_p; + undoptcache_free_data(&us->data); } -void PE_redo(Scene *scene) +static void particle_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) { - PE_undo_step(scene, -1); + ParticleUndoStep *us = (ParticleUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->scene_ref)); + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->object_ref)); } -void PE_undo_number(Scene *scene, int nr) +/* Export for ED_undo_sys. */ +void ED_particle_undosys_type(UndoType *ut) { - PTCacheEdit *edit= PE_get_current(scene, OBACT); - PTCacheUndo *undo; - int a=0; - - for (undo= edit->undo.first; undo; undo= undo->next, a++) { - if (a==nr) break; - } - edit->curundo= undo; - PE_undo_step(scene, 0); + ut->name = "Edit Particle"; + ut->poll = particle_undosys_poll; + ut->step_encode = particle_undosys_step_encode; + ut->step_decode = particle_undosys_step_decode; + ut->step_free = particle_undosys_step_free; + + ut->step_foreach_ID_ref = particle_undosys_foreach_ID_ref; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(ParticleUndoStep); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ -/* get name of undo item, return null if no item with this index */ -/* if active pointer, set it to 1 if true */ -const char *PE_undo_get_name(Scene *scene, int nr, bool *r_active) +void PE_undo_push(struct Scene *scene, const char *str) { - PTCacheEdit *edit= PE_get_current(scene, OBACT); - PTCacheUndo *undo; - - if (r_active) *r_active = false; - - if (edit) { - undo= BLI_findlink(&edit->undo, nr); - if (undo) { - if (r_active && (undo == edit->curundo)) { - *r_active = true; - } - return undo->name; - } - } - return NULL; + wmWindowManager *wm = G.main->wm.first; + bContext *C_temp = CTX_create(); + CTX_data_scene_set(C_temp, scene); + BKE_undosys_step_push_with_type(wm->undo_stack, C_temp, str, BKE_UNDOSYS_TYPE_PARTICLE); + CTX_free(C_temp); } + +/** \} */ diff --git a/source/blender/editors/physics/particle_object.c b/source/blender/editors/physics/particle_object.c index d85720f956c..63b84df9fce 100644 --- a/source/blender/editors/physics/particle_object.c +++ b/source/blender/editors/physics/particle_object.c @@ -911,10 +911,7 @@ static void copy_particle_edit(Scene *scene, Object *ob, ParticleSystem *psys, P edit->emitter_field = NULL; edit->emitter_cosnos = NULL; - - BLI_listbase_clear(&edit->undo); - edit->curundo = NULL; - + edit->points = MEM_dupallocN(edit_from->points); pa = psys->particles; LOOP_POINTS { @@ -943,8 +940,7 @@ static void copy_particle_edit(Scene *scene, Object *ob, ParticleSystem *psys, P recalc_lengths(edit); recalc_emitter_field(ob, psys); PE_update_object(scene, ob, true); - - PTCacheUndo_clear(edit); + PE_undo_push(scene, "Original"); } diff --git a/source/blender/editors/physics/physics_intern.h b/source/blender/editors/physics/physics_intern.h index cb281936634..f3f3697caaa 100644 --- a/source/blender/editors/physics/physics_intern.h +++ b/source/blender/editors/physics/physics_intern.h @@ -68,7 +68,6 @@ void PARTICLE_OT_edited_clear(struct wmOperatorType *ot); void PARTICLE_OT_unify_length(struct wmOperatorType *ot); -void PTCacheUndo_clear(struct PTCacheEdit *edit); void PE_create_particle_edit(struct Scene *scene, struct Object *ob, struct PointCache *cache, struct ParticleSystem *psys); void recalc_lengths(struct PTCacheEdit *edit); void recalc_emitter_field(struct Object *ob, struct ParticleSystem *psys); diff --git a/source/blender/editors/render/render_internal.c b/source/blender/editors/render/render_internal.c index c27570aabc5..92fb140243f 100644 --- a/source/blender/editors/render/render_internal.c +++ b/source/blender/editors/render/render_internal.c @@ -65,6 +65,7 @@ #include "BKE_sequencer.h" #include "BKE_screen.h" #include "BKE_scene.h" +#include "BKE_undo_system.h" #include "WM_api.h" #include "WM_types.h" @@ -88,6 +89,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "BLO_undofile.h" #include "render_intern.h" @@ -866,7 +868,8 @@ static int screen_render_invoke(bContext *C, wmOperator *op, const wmEvent *even /* get main */ if (G.debug_value == 101) { /* thread-safety experiment, copy main from the undo buffer */ - mainp = BKE_undo_get_main(&scene); + struct MemFile *memfile = ED_undosys_stack_memfile_get_active(CTX_wm_manager(C)->undo_stack); + mainp = BLO_memfile_main_get(memfile, CTX_data_main(C), &scene); } else mainp = CTX_data_main(C); diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index adead9a8b9e..9527dc4fe83 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -51,7 +51,6 @@ set(SRC paint_mask.c paint_ops.c paint_stroke.c - paint_undo.c paint_utils.c paint_vertex.c paint_vertex_color_ops.c diff --git a/source/blender/editors/sculpt_paint/paint_curve.c b/source/blender/editors/sculpt_paint/paint_curve.c index 8d9812f41d9..120514762f4 100644 --- a/source/blender/editors/sculpt_paint/paint_curve.c +++ b/source/blender/editors/sculpt_paint/paint_curve.c @@ -40,6 +40,7 @@ #include "BKE_paint.h" #include "ED_view3d.h" +#include "ED_paint.h" #include "WM_api.h" #include "WM_types.h" @@ -203,7 +204,7 @@ static void paintcurve_point_add(bContext *C, wmOperator *op, const int loc[2]) br->paint_curve = pc = BKE_paint_curve_add(bmain, "PaintCurve"); } - ED_paintcurve_undo_push(C, op, pc); + ED_paintcurve_undo_push_begin(op->type->name); pcp = MEM_mallocN((pc->tot_points + 1) * sizeof(PaintCurvePoint), "PaintCurvePoint"); add_index = pc->add_index; @@ -241,6 +242,8 @@ static void paintcurve_point_add(bContext *C, wmOperator *op, const int loc[2]) pcp[add_index].bez.h1 = HD_ALIGN; } + ED_paintcurve_undo_push_end(); + WM_paint_cursor_tag_redraw(window, ar); } @@ -302,7 +305,7 @@ static int paintcurve_delete_point_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - ED_paintcurve_undo_push(C, op, pc); + ED_paintcurve_undo_push_begin(op->type->name); #define DELETE_TAG 2 @@ -342,6 +345,8 @@ static int paintcurve_delete_point_exec(bContext *C, wmOperator *op) #undef DELETE_TAG + ED_paintcurve_undo_push_end(); + WM_paint_cursor_tag_redraw(window, ar); return OPERATOR_FINISHED; @@ -379,7 +384,7 @@ static bool paintcurve_point_select(bContext *C, wmOperator *op, const int loc[2 if (!pc) return false; - ED_paintcurve_undo_push(C, op, pc); + ED_paintcurve_undo_push_begin(op->type->name); if (toggle) { PaintCurvePoint *pcp; @@ -444,10 +449,14 @@ static bool paintcurve_point_select(bContext *C, wmOperator *op, const int loc[2 } } - if (!pcp) + if (!pcp) { + ED_paintcurve_undo_push_end(); return false; + } } + ED_paintcurve_undo_push_end(); + WM_paint_cursor_tag_redraw(window, ar); return true; @@ -562,9 +571,6 @@ static int paintcurve_slide_invoke(bContext *C, wmOperator *op, const wmEvent *e psd->align = align; op->customdata = psd; - if (do_select) - ED_paintcurve_undo_push(C, op, pc); - /* first, clear all selection from points */ for (i = 0; i < pc->tot_points; i++) pc->points[i].bez.f1 = pc->points[i].bez.f3 = pc->points[i].bez.f2 = 0; @@ -587,6 +593,8 @@ static int paintcurve_slide_modal(bContext *C, wmOperator *op, const wmEvent *ev if (event->type == psd->event && event->val == KM_RELEASE) { MEM_freeN(psd); + ED_paintcurve_undo_push_begin(op->type->name); + ED_paintcurve_undo_push_end(); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/sculpt_paint/paint_curve_undo.c b/source/blender/editors/sculpt_paint/paint_curve_undo.c index 70f92999864..d5b7496fa3e 100644 --- a/source/blender/editors/sculpt_paint/paint_curve_undo.c +++ b/source/blender/editors/sculpt_paint/paint_curve_undo.c @@ -30,9 +30,13 @@ #include "DNA_space_types.h" #include "BLI_string.h" +#include "BLI_array_utils.h" #include "BKE_context.h" #include "BKE_paint.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_undo_system.h" #include "ED_paint.h" @@ -41,89 +45,125 @@ #include "paint_intern.h" -typedef struct UndoCurve { - struct UndoImageTile *next, *prev; +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ +typedef struct UndoCurve { PaintCurvePoint *points; /* points of curve */ int tot_points; - int active_point; - - char idname[MAX_ID_NAME]; /* name instead of pointer*/ + int add_index; } UndoCurve; -static void paintcurve_undo_restore(bContext *C, ListBase *lb) +static void undocurve_from_paintcurve(UndoCurve *uc, const PaintCurve *pc) { - Paint *p = BKE_paint_get_active_from_context(C); - UndoCurve *uc; - PaintCurve *pc = NULL; + BLI_assert(BLI_array_is_zeroed(uc, 1)); + uc->points = MEM_dupallocN(pc->points); + uc->tot_points = pc->tot_points; + uc->add_index = pc->add_index; +} - if (p->brush) { - pc = p->brush->paint_curve; - } +static void undocurve_to_paintcurve(const UndoCurve *uc, PaintCurve *pc) +{ + MEM_SAFE_FREE(pc->points); + pc->points = MEM_dupallocN(uc->points); + pc->tot_points = uc->tot_points; + pc->add_index = uc->add_index; +} - if (!pc) { - return; - } +static void undocurve_free_data(UndoCurve *uc) +{ + MEM_SAFE_FREE(uc->points); +} - uc = (UndoCurve *)lb->first; +/** \} */ - if (STREQLEN(uc->idname, pc->id.name, BLI_strnlen(uc->idname, sizeof(uc->idname)))) { - SWAP(PaintCurvePoint *, pc->points, uc->points); - SWAP(int, pc->tot_points, uc->tot_points); - SWAP(int, pc->add_index, uc->active_point); - } -} +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ -static void paintcurve_undo_delete(ListBase *lb) +typedef struct PaintCurveUndoStep { + UndoStep step; + PaintCurve *pc; + UndoCurve data; +} PaintCurveUndoStep; + +static bool paintcurve_undosys_poll(bContext *C) { - UndoCurve *uc; - uc = (UndoCurve *)lb->first; + Paint *p = BKE_paint_get_active_from_context(C); + return (p->brush && p->brush->paint_curve); +} - if (uc->points) - MEM_freeN(uc->points); - uc->points = NULL; +static void paintcurve_undosys_step_encode_init(struct bContext *C, UndoStep *us_p) +{ + /* XXX, use to set the undo type only. */ + UNUSED_VARS(C, us_p); } -/** - * \note This is called before executing steps (not after). - */ -void ED_paintcurve_undo_push(bContext *C, wmOperator *op, PaintCurve *pc) +static bool paintcurve_undosys_step_encode(struct bContext *C, UndoStep *us_p) { - ePaintMode mode = BKE_paintmode_get_active_from_context(C); - ListBase *lb = NULL; - int undo_stack_id; - UndoCurve *uc; - - switch (mode) { - case ePaintTexture2D: - case ePaintTextureProjective: - undo_stack_id = UNDO_PAINT_IMAGE; - break; - - case ePaintSculpt: - undo_stack_id = UNDO_PAINT_MESH; - break; - - default: - /* do nothing, undo is handled by global */ - return; + Paint *p = BKE_paint_get_active_from_context(C); + PaintCurve *pc = p ? (p->brush ? p->brush->paint_curve : NULL) : NULL; + if (pc == NULL) { + return false; } + PaintCurveUndoStep *us = (PaintCurveUndoStep *)us_p; + BLI_assert(us->step.data_size == 0); - ED_undo_paint_push_begin(undo_stack_id, op->type->name, - paintcurve_undo_restore, paintcurve_undo_delete, NULL); - lb = undo_paint_push_get_list(undo_stack_id); + us->pc = pc; + undocurve_from_paintcurve(&us->data, pc); - uc = MEM_callocN(sizeof(*uc), "Undo_curve"); + return true; +} - lb->first = uc; +static void paintcurve_undosys_step_decode(struct bContext *UNUSED(C), UndoStep *us_p, int UNUSED(dir)) +{ + PaintCurveUndoStep *us = (PaintCurveUndoStep *)us_p; + undocurve_to_paintcurve(&us->data, us->pc); +} - BLI_strncpy(uc->idname, pc->id.name, sizeof(uc->idname)); - uc->tot_points = pc->tot_points; - uc->active_point = pc->add_index; - uc->points = MEM_dupallocN(pc->points); +static void paintcurve_undosys_step_free(UndoStep *us_p) +{ + PaintCurveUndoStep *us = (PaintCurveUndoStep *)us_p; + undocurve_free_data(&us->data); +} + +/* Export for ED_undo_sys. */ +void ED_paintcurve_undosys_type(UndoType *ut) +{ + ut->name = "Paint Curve"; + /* don't poll for now */ + ut->poll = paintcurve_undosys_poll; + ut->step_encode_init = paintcurve_undosys_step_encode_init; + ut->step_encode = paintcurve_undosys_step_encode; + ut->step_decode = paintcurve_undosys_step_decode; + ut->step_free = paintcurve_undosys_step_free; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = false; + + ut->step_size = sizeof(PaintCurveUndoStep); +} + +/** \} */ - undo_paint_push_count_alloc(undo_stack_id, sizeof(*uc) + sizeof(*pc->points) * pc->tot_points); - ED_undo_paint_push_end(undo_stack_id); +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ + +void ED_paintcurve_undo_push_begin(const char *name) +{ + bContext *C = NULL; /* special case, we never read from this. */ + wmWindowManager *wm = G.main->wm.first; + BKE_undosys_step_push_init_with_type(wm->undo_stack, C, name, BKE_UNDOSYS_TYPE_PAINTCURVE); } + +void ED_paintcurve_undo_push_end(void) +{ + wmWindowManager *wm = G.main->wm.first; /* XXX, avoids adding extra arg. */ + BKE_undosys_step_push(wm->undo_stack, NULL, NULL); +} + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index c6472b258ca..5d133ee622f 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -58,6 +58,9 @@ #include "BKE_material.h" #include "BKE_node.h" #include "BKE_paint.h" +#include "BKE_undo_system.h" + +#include "BKE_global.h" #include "UI_interface.h" #include "UI_view2d.h" @@ -145,9 +148,11 @@ void ED_imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int imapaint_region_tiles(ibuf, x, y, w, h, &tilex, &tiley, &tilew, &tileh); + ListBase *undo_tiles = ED_image_undo_get_tiles(); + for (ty = tiley; ty <= tileh; ty++) for (tx = tilex; tx <= tilew; tx++) - image_undo_push_tile(ima, ibuf, &tmpibuf, tx, ty, NULL, NULL, false, find_old); + image_undo_push_tile(undo_tiles, ima, ibuf, &tmpibuf, tx, ty, NULL, NULL, false, find_old); ibuf->userflags |= IB_BITMAPDIRTY; @@ -491,7 +496,8 @@ static void paint_stroke_update_step(bContext *C, struct PaintStroke *stroke, Po BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * alphafac)); if ((brush->flag & BRUSH_DRAG_DOT) || (brush->flag & BRUSH_ANCHORED)) { - ED_image_undo_restore(); + UndoStack *ustack = CTX_wm_manager(C)->undo_stack; + ED_image_undo_restore(ustack->step_init); } if (pop->mode == PAINT_MODE_3D_PROJECT) { @@ -1174,14 +1180,17 @@ void PAINT_OT_brush_colors_flip(wmOperatorType *ot) void ED_imapaint_bucket_fill(struct bContext *C, float color[3], wmOperator *op) { + wmWindowManager *wm = CTX_wm_manager(C); SpaceImage *sima = CTX_wm_space_image(C); Image *ima = sima->image; + BKE_undosys_step_push_init_with_type(wm->undo_stack, C, op->type->name, BKE_UNDOSYS_TYPE_IMAGE); + ED_image_undo_push_begin(op->type->name); paint_2d_bucket_fill(C, color, NULL, NULL, NULL); - ED_undo_paint_push_end(UNDO_PAINT_IMAGE); + BKE_undosys_step_push(wm->undo_stack, C, op->type->name); DAG_id_tag_update(&ima->id, 0); } diff --git a/source/blender/editors/sculpt_paint/paint_image_2d.c b/source/blender/editors/sculpt_paint/paint_image_2d.c index 894277402a7..c1dd31ea055 100644 --- a/source/blender/editors/sculpt_paint/paint_image_2d.c +++ b/source/blender/editors/sculpt_paint/paint_image_2d.c @@ -1035,6 +1035,8 @@ static void paint_2d_do_making_brush(ImagePaintState *s, ImBuf tmpbuf; IMB_initImBuf(&tmpbuf, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, 0); + ListBase *undo_tiles = ED_image_undo_get_tiles(); + for (int ty = tiley; ty <= tileh; ty++) { for (int tx = tilex; tx <= tilew; tx++) { /* retrieve original pixels + mask from undo buffer */ @@ -1043,9 +1045,9 @@ static void paint_2d_do_making_brush(ImagePaintState *s, int origy = region->desty - ty * IMAPAINT_TILE_SIZE; if (s->canvas->rect_float) - tmpbuf.rect_float = image_undo_find_tile(s->image, s->canvas, tx, ty, &mask, false); + tmpbuf.rect_float = image_undo_find_tile(undo_tiles, s->image, s->canvas, tx, ty, &mask, false); else - tmpbuf.rect = image_undo_find_tile(s->image, s->canvas, tx, ty, &mask, false); + tmpbuf.rect = image_undo_find_tile(undo_tiles, s->image, s->canvas, tx, ty, &mask, false); IMB_rectblend(s->canvas, &tmpbuf, frombuf, mask, curveb, texmaskb, mask_max, diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 60fe8555ba2..e5bddae971d 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -1499,15 +1499,16 @@ static int project_paint_undo_subtiles(const TileInfo *tinf, int tx, int ty) if (generate_tile) { + ListBase *undo_tiles = ED_image_undo_get_tiles(); volatile void *undorect; if (tinf->masked) { undorect = image_undo_push_tile( - pjIma->ima, pjIma->ibuf, tinf->tmpibuf, + undo_tiles, pjIma->ima, pjIma->ibuf, tinf->tmpibuf, tx, ty, &pjIma->maskRect[tile_index], &pjIma->valid[tile_index], true, false); } else { undorect = image_undo_push_tile( - pjIma->ima, pjIma->ibuf, tinf->tmpibuf, + undo_tiles, pjIma->ima, pjIma->ibuf, tinf->tmpibuf, tx, ty, NULL, &pjIma->valid[tile_index], true, false); } diff --git a/source/blender/editors/sculpt_paint/paint_image_undo.c b/source/blender/editors/sculpt_paint/paint_image_undo.c index b5b64eb3e16..9d1987943a5 100644 --- a/source/blender/editors/sculpt_paint/paint_image_undo.c +++ b/source/blender/editors/sculpt_paint/paint_image_undo.c @@ -29,6 +29,10 @@ #include "BLI_threads.h" #include "DNA_image_types.h" +#include "DNA_windowmanager_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -37,6 +41,8 @@ #include "BKE_depsgraph.h" #include "BKE_image.h" #include "BKE_main.h" +#include "BKE_global.h" +#include "BKE_undo_system.h" #include "ED_paint.h" @@ -44,10 +50,14 @@ #include "paint_intern.h" +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ + typedef struct UndoImageTile { struct UndoImageTile *next, *prev; - char idname[MAX_ID_NAME]; /* name instead of pointer*/ + char idname[MAX_ID_NAME]; /* name instead of pointer */ char ibufname[IMB_FILENAME_SIZE]; union { @@ -64,6 +74,8 @@ typedef struct UndoImageTile { short source, use_float; char gen_type; bool valid; + + size_t undo_size; } UndoImageTile; /* this is a static resource for non-globality, @@ -129,13 +141,14 @@ static void undo_copy_tile(UndoImageTile *tile, ImBuf *tmpibuf, ImBuf *ibuf, Cop } } -void *image_undo_find_tile(Image *ima, ImBuf *ibuf, int x_tile, int y_tile, unsigned short **mask, bool validate) +void *image_undo_find_tile( + ListBase *undo_tiles, + Image *ima, ImBuf *ibuf, int x_tile, int y_tile, unsigned short **mask, bool validate) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_IMAGE); UndoImageTile *tile; short use_float = ibuf->rect_float ? 1 : 0; - for (tile = lb->first; tile; tile = tile->next) { + for (tile = undo_tiles->first; tile; tile = tile->next) { if (tile->x == x_tile && tile->y == y_tile && ima->gen_type == tile->gen_type && ima->source == tile->source) { if (tile->use_float == use_float) { if (STREQ(tile->idname, ima->id.name) && STREQ(tile->ibufname, ibuf->name)) { @@ -161,10 +174,10 @@ void *image_undo_find_tile(Image *ima, ImBuf *ibuf, int x_tile, int y_tile, unsi } void *image_undo_push_tile( + ListBase *undo_tiles, Image *ima, ImBuf *ibuf, ImBuf **tmpibuf, int x_tile, int y_tile, unsigned short **mask, bool **valid, bool proj, bool find_prev) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_IMAGE); UndoImageTile *tile; int allocsize; short use_float = ibuf->rect_float ? 1 : 0; @@ -174,7 +187,7 @@ void *image_undo_push_tile( /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */ if (find_prev) { - data = image_undo_find_tile(ima, ibuf, x_tile, y_tile, mask, true); + data = image_undo_find_tile(undo_tiles, ima, ibuf, x_tile, y_tile, mask, true); if (data) { return data; } @@ -214,8 +227,7 @@ void *image_undo_push_tile( if (proj) { BLI_spin_lock(&undolock); } - undo_paint_push_count_alloc(UNDO_PAINT_IMAGE, allocsize); - BLI_addtail(lb, tile); + BLI_addtail(undo_tiles, tile); if (proj) { BLI_spin_unlock(&undolock); @@ -225,10 +237,10 @@ void *image_undo_push_tile( void image_undo_remove_masks(void) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_IMAGE); + ListBase *undo_tiles = ED_image_undo_get_tiles(); UndoImageTile *tile; - for (tile = lb->first; tile; tile = tile->next) { + for (tile = undo_tiles->first; tile; tile = tile->next) { if (tile->mask) { MEM_freeN(tile->mask); tile->mask = NULL; @@ -346,50 +358,145 @@ static void image_undo_free_list(ListBase *lb) void ED_image_undo_push_begin(const char *name) { - ED_undo_paint_push_begin(UNDO_PAINT_IMAGE, name, image_undo_restore_list, image_undo_free_list, NULL); + bContext *C = NULL; /* special case, we never read from this. */ + wmWindowManager *wm = G.main->wm.first; + BKE_undosys_step_push_init_with_type(wm->undo_stack, C, name, BKE_UNDOSYS_TYPE_IMAGE); } void ED_image_undo_push_end(void) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_IMAGE); + wmWindowManager *wm = G.main->wm.first; /* XXX, avoids adding extra arg. */ + BKE_undosys_step_push(wm->undo_stack, NULL, NULL); +} + +static void image_undo_invalidate(void) +{ UndoImageTile *tile; - int deallocsize = 0; + ListBase *lb = ED_image_undo_get_tiles(); + + for (tile = lb->first; tile; tile = tile->next) { + tile->valid = false; + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct ImageUndoStep { + UndoStep step; + ListBase tiles; +} ImageUndoStep; + +static bool image_undosys_poll(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + + ScrArea *sa = CTX_wm_area(C); + if (sa && (sa->spacetype == SPACE_IMAGE)) { + SpaceImage *sima = (SpaceImage *)sa->spacedata.first; + if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { + return true; + } + } + else if (sa && (sa->spacetype == SPACE_VIEW3D)) { + if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { + return true; + } + } + return false; +} + +static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) +{ + ImageUndoStep *us = (ImageUndoStep *)us_p; + /* dummy, memory is cleared anyway. */ + BLI_listbase_clear(&us->tiles); +} + +static bool image_undosys_step_encode(struct bContext *UNUSED(C), UndoStep *us_p) +{ + /* dummy, encoding is done along the way by adding tiles + * to the current 'ImageUndoStep' added by encode_init. */ + ImageUndoStep *us = (ImageUndoStep *)us_p; + + BLI_assert(us->step.data_size == 0); + int allocsize = IMAPAINT_TILE_SIZE * IMAPAINT_TILE_SIZE * 4; /* first dispose of invalid tiles (may happen due to drag dot for instance) */ - for (tile = lb->first; tile;) { + for (UndoImageTile *tile = us->tiles.first; tile;) { if (!tile->valid) { UndoImageTile *tmp_tile = tile->next; - deallocsize += allocsize * ((tile->use_float) ? sizeof(float) : sizeof(char)); MEM_freeN(tile->rect.pt); - BLI_freelinkN(lb, tile); + BLI_freelinkN(&us->tiles, tile); tile = tmp_tile; } else { + us->step.data_size += allocsize * ((tile->use_float) ? sizeof(float) : sizeof(char)); tile = tile->next; } } - /* don't forget to remove the size of deallocated tiles */ - undo_paint_push_count_alloc(UNDO_PAINT_IMAGE, -deallocsize); + return true; +} - ED_undo_paint_push_end(UNDO_PAINT_IMAGE); +static void image_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + ImageUndoStep *us = (ImageUndoStep *)us_p; + image_undo_restore_list(C, &us->tiles); } -static void image_undo_invalidate(void) +static void image_undosys_step_free(UndoStep *us_p) { - UndoImageTile *tile; - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_IMAGE); + ImageUndoStep *us = (ImageUndoStep *)us_p; + image_undo_free_list(&us->tiles); +} - for (tile = lb->first; tile; tile = tile->next) { - tile->valid = false; - } +/* Export for ED_undo_sys. */ +void ED_image_undosys_type(UndoType *ut) +{ + ut->name = "Image"; + ut->poll = image_undosys_poll; + ut->step_encode_init = image_undosys_step_encode_init; + ut->step_encode = image_undosys_step_encode; + ut->step_decode = image_undosys_step_decode; + ut->step_free = image_undosys_step_free; + + ut->mode = BKE_UNDOTYPE_MODE_ACCUMULATE; + ut->use_context = true; + + ut->step_size = sizeof(ImageUndoStep); +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ + +ListBase *ED_image_undosys_step_get_tiles(UndoStep *us_p) +{ + ImageUndoStep *us = (ImageUndoStep *)us_p; + return &us->tiles; +} + +ListBase *ED_image_undo_get_tiles(void) +{ + wmWindowManager *wm = G.main->wm.first; /* XXX, avoids adding extra arg. */ + UndoStep *us = BKE_undosys_stack_init_or_active_with_type(wm->undo_stack, BKE_UNDOSYS_TYPE_IMAGE); + return ED_image_undosys_step_get_tiles(us); } /* restore painting image to previous state. Used for anchored and drag-dot style brushes*/ -void ED_image_undo_restore(void) +void ED_image_undo_restore(UndoStep *us) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_IMAGE); + ListBase *lb = ED_image_undosys_step_get_tiles(us); image_undo_restore_runtime(lb); image_undo_invalidate(); } + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 52be4be4c0d..63a12b3c145 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -55,6 +55,7 @@ struct wmOperator; struct wmOperatorType; struct wmWindowManager; struct DMCoNo; +struct UndoStep; enum ePaintMode; /* paint_stroke.c */ @@ -221,15 +222,20 @@ void PAINT_OT_add_simple_uvs(struct wmOperatorType *ot); /* paint_image_undo.c */ void *image_undo_find_tile( + ListBase *undo_tiles, struct Image *ima, struct ImBuf *ibuf, int x_tile, int y_tile, unsigned short **mask, bool validate); void *image_undo_push_tile( + ListBase *undo_tiles, struct Image *ima, struct ImBuf *ibuf, struct ImBuf **tmpibuf, int x_tile, int y_tile, unsigned short **, bool **valid, bool proj, bool find_prev); void image_undo_remove_masks(void); void image_undo_init_locks(void); void image_undo_end_locks(void); +struct ListBase *ED_image_undosys_step_get_tiles(struct UndoStep *us_p); +struct ListBase *ED_image_undo_get_tiles(void); + /* sculpt_uv.c */ int uv_sculpt_poll(struct bContext *C); int uv_sculpt_keymap_poll(struct bContext *C); @@ -304,10 +310,6 @@ typedef enum { void set_brush_rc_props(struct PointerRNA *ptr, const char *paint, const char *prop, const char *secondary_prop, RCFlags flags); -/* paint_undo.c */ -struct ListBase *undo_paint_push_get_list(int type); -void undo_paint_push_count_alloc(int type, int size); - /* paint_hide.c */ typedef enum { diff --git a/source/blender/editors/sculpt_paint/paint_undo.c b/source/blender/editors/sculpt_paint/paint_undo.c deleted file mode 100644 index 27d3f6648a2..00000000000 --- a/source/blender/editors/sculpt_paint/paint_undo.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * ***** BEGIN GPL LICENSE BLOCK ***** - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * ***** END GPL LICENSE BLOCK ***** - */ - -/** \file blender/editors/sculpt_paint/paint_undo.c - * \ingroup edsculpt - * \brief Undo system for painting and sculpting. - */ - -#include -#include - -#include "MEM_guardedalloc.h" - -#include "BLI_listbase.h" -#include "BLI_utildefines.h" -#include "BLI_string.h" - -#include "DNA_userdef_types.h" - -#include "BKE_blender_undo.h" -#include "BKE_context.h" -#include "BKE_global.h" - -#include "ED_paint.h" - -#include "paint_intern.h" - -typedef struct UndoElem { - struct UndoElem *next, *prev; - char name[BKE_UNDO_STR_MAX]; - uintptr_t undosize; - - ListBase elems; - - UndoRestoreCb restore; - UndoFreeCb free; - UndoCleanupCb cleanup; -} UndoElem; - -typedef struct UndoStack { - int type; - ListBase elems; - UndoElem *current; -} UndoStack; - -static UndoStack ImageUndoStack = {UNDO_PAINT_IMAGE, {NULL, NULL}, NULL}; -static UndoStack MeshUndoStack = {UNDO_PAINT_MESH, {NULL, NULL}, NULL}; - -/* Generic */ - -static void undo_restore(bContext *C, UndoStack *UNUSED(stack), UndoElem *uel) -{ - if (uel && uel->restore) - uel->restore(C, &uel->elems); -} - -static void undo_elem_free(UndoStack *UNUSED(stack), UndoElem *uel) -{ - if (uel && uel->free) { - uel->free(&uel->elems); - BLI_freelistN(&uel->elems); - } -} - -static void undo_stack_push_begin(UndoStack *stack, const char *name, UndoRestoreCb restore, UndoFreeCb free, UndoCleanupCb cleanup) -{ - UndoElem *uel; - int nr; - - /* Undo push is split up in begin and end, the reason is that as painting - * happens more tiles/nodes are added to the list, and at the very end we - * know how much memory the undo used to remove old undo elements */ - - /* remove all undos after (also when stack->current==NULL) */ - while (stack->elems.last != stack->current) { - uel = stack->elems.last; - undo_elem_free(stack, uel); - BLI_freelinkN(&stack->elems, uel); - } - - /* make new */ - stack->current = uel = MEM_callocN(sizeof(UndoElem), "undo file"); - uel->restore = restore; - uel->free = free; - uel->cleanup = cleanup; - BLI_addtail(&stack->elems, uel); - - /* name can be a dynamic string */ - BLI_strncpy(uel->name, name, sizeof(uel->name)); - - /* limit amount to the maximum amount*/ - nr = 0; - uel = stack->elems.last; - while (uel) { - nr++; - if (nr == U.undosteps) break; - uel = uel->prev; - } - if (uel) { - while (stack->elems.first != uel) { - UndoElem *first = stack->elems.first; - undo_elem_free(stack, first); - BLI_freelinkN(&stack->elems, first); - } - } -} - -static void undo_stack_push_end(UndoStack *stack) -{ - UndoElem *uel; - uintptr_t totmem, maxmem; - int totundo = 0; - - /* first limit to undo steps */ - uel = stack->elems.last; - - while (uel) { - totundo++; - if (totundo > U.undosteps) break; - uel = uel->prev; - } - - if (uel) { - UndoElem *first; - - /* in case the undo steps are zero, the current pointer will be invalid */ - if (uel == stack->current) - stack->current = NULL; - - do { - first = stack->elems.first; - undo_elem_free(stack, first); - BLI_freelinkN(&stack->elems, first); - } while (first != uel); - } - - if (U.undomemory != 0) { - /* limit to maximum memory (afterwards, we can't know in advance) */ - totmem = 0; - maxmem = ((uintptr_t)U.undomemory) * 1024 * 1024; - - uel = stack->elems.last; - while (uel) { - totmem += uel->undosize; - if (totmem > maxmem) break; - uel = uel->prev; - } - - if (uel) { - while (stack->elems.first != uel) { - UndoElem *first = stack->elems.first; - undo_elem_free(stack, first); - BLI_freelinkN(&stack->elems, first); - } - } - } -} - -static void undo_stack_cleanup(UndoStack *stack, bContext *C) -{ - UndoElem *uel = stack->elems.first; - bool stack_reset = false; - - while (uel) { - if (uel->cleanup && uel->cleanup(C, &uel->elems)) { - UndoElem *uel_tmp = uel->next; - if (stack->current == uel) { - stack->current = NULL; - stack_reset = true; - } - undo_elem_free(stack, uel); - BLI_freelinkN(&stack->elems, uel); - uel = uel_tmp; - } - else - uel = uel->next; - } - if (stack_reset) { - stack->current = stack->elems.last; - } - -} - -static int undo_stack_step(bContext *C, UndoStack *stack, int step, const char *name) -{ - UndoElem *undo; - - /* first cleanup any old undo steps that may belong to invalid data */ - undo_stack_cleanup(stack, C); - - if (step == 1) { - if (stack->current == NULL) { - /* pass */ - } - else { - if (!name || STREQ(stack->current->name, name)) { - if (G.debug & G_DEBUG_WM) { - printf("%s: undo '%s'\n", __func__, stack->current->name); - } - undo_restore(C, stack, stack->current); - stack->current = stack->current->prev; - return 1; - } - } - } - else if (step == -1) { - if ((stack->current != NULL && stack->current->next == NULL) || BLI_listbase_is_empty(&stack->elems)) { - /* pass */ - } - else { - if (!name || STREQ(stack->current->name, name)) { - undo = (stack->current && stack->current->next) ? stack->current->next : stack->elems.first; - undo_restore(C, stack, undo); - stack->current = undo; - if (G.debug & G_DEBUG_WM) { - printf("%s: redo %s\n", __func__, undo->name); - } - return 1; - } - } - } - - return 0; -} - -static void undo_stack_free(UndoStack *stack) -{ - UndoElem *uel; - - for (uel = stack->elems.first; uel; uel = uel->next) - undo_elem_free(stack, uel); - - BLI_freelistN(&stack->elems); - stack->current = NULL; -} - -/* Exported Functions */ - -void ED_undo_paint_push_begin(int type, const char *name, UndoRestoreCb restore, UndoFreeCb free, UndoCleanupCb cleanup) -{ - if (type == UNDO_PAINT_IMAGE) - undo_stack_push_begin(&ImageUndoStack, name, restore, free, cleanup); - else if (type == UNDO_PAINT_MESH) - undo_stack_push_begin(&MeshUndoStack, name, restore, free, cleanup); -} - -ListBase *undo_paint_push_get_list(int type) -{ - if (type == UNDO_PAINT_IMAGE) { - if (ImageUndoStack.current) { - return &ImageUndoStack.current->elems; - } - } - else if (type == UNDO_PAINT_MESH) { - if (MeshUndoStack.current) { - return &MeshUndoStack.current->elems; - } - } - - return NULL; -} - -void undo_paint_push_count_alloc(int type, int size) -{ - if (type == UNDO_PAINT_IMAGE) - ImageUndoStack.current->undosize += size; - else if (type == UNDO_PAINT_MESH) - MeshUndoStack.current->undosize += size; -} - -void ED_undo_paint_push_end(int type) -{ - if (type == UNDO_PAINT_IMAGE) - undo_stack_push_end(&ImageUndoStack); - else if (type == UNDO_PAINT_MESH) - undo_stack_push_end(&MeshUndoStack); -} - -int ED_undo_paint_step(bContext *C, int type, int step, const char *name) -{ - if (type == UNDO_PAINT_IMAGE) - return undo_stack_step(C, &ImageUndoStack, step, name); - else if (type == UNDO_PAINT_MESH) - return undo_stack_step(C, &MeshUndoStack, step, name); - - return 0; -} - -static void undo_step_num(bContext *C, UndoStack *stack, int step) -{ - UndoElem *uel; - int a = 0; - int curnum = BLI_findindex(&stack->elems, stack->current); - - for (uel = stack->elems.first; uel; uel = uel->next, a++) { - if (a == step) break; - } - - if (curnum > a) { - while (a++ != curnum) - undo_stack_step(C, stack, 1, NULL); - } - else if (curnum < a) { - while (a-- != curnum) - undo_stack_step(C, stack, -1, NULL); - } -} - -void ED_undo_paint_step_num(bContext *C, int type, int step) -{ - if (type == UNDO_PAINT_IMAGE) - undo_step_num(C, &ImageUndoStack, step); - else if (type == UNDO_PAINT_MESH) - undo_step_num(C, &MeshUndoStack, step); -} - -static char *undo_stack_get_name(UndoStack *stack, int nr, bool *r_active) -{ - UndoElem *uel; - - if (r_active) *r_active = false; - - uel = BLI_findlink(&stack->elems, nr); - if (uel) { - if (r_active && (uel == stack->current)) { - *r_active = true; - } - return uel->name; - } - - return NULL; -} - -const char *ED_undo_paint_get_name(bContext *C, int type, int nr, bool *r_active) -{ - - if (type == UNDO_PAINT_IMAGE) { - undo_stack_cleanup(&ImageUndoStack, C); - return undo_stack_get_name(&ImageUndoStack, nr, r_active); - } - else if (type == UNDO_PAINT_MESH) { - undo_stack_cleanup(&MeshUndoStack, C); - return undo_stack_get_name(&MeshUndoStack, nr, r_active); - } - return NULL; -} - -bool ED_undo_paint_empty(int type) -{ - UndoStack *stack; - - if (type == UNDO_PAINT_IMAGE) - stack = &ImageUndoStack; - else if (type == UNDO_PAINT_MESH) - stack = &MeshUndoStack; - else - return true; - - if (stack->current == NULL) { - return true; - } - - return false; -} - -bool ED_undo_paint_is_valid(int type, const char *name) -{ - UndoStack *stack; - - if (type == UNDO_PAINT_IMAGE) - stack = &ImageUndoStack; - else if (type == UNDO_PAINT_MESH) - stack = &MeshUndoStack; - else - return 0; - - if (stack->current == NULL) { - /* pass */ - } - else { - if (name && STREQ(stack->current->name, name)) - return 1; - else - return stack->elems.first != stack->elems.last; - } - return 0; -} - -void ED_undo_paint_free(void) -{ - undo_stack_free(&ImageUndoStack); - undo_stack_free(&MeshUndoStack); -} diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index cf1937f14d4..e46760258e1 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -122,6 +122,8 @@ typedef struct SculptUndoNode { /* shape keys */ char shapeName[sizeof(((KeyBlock *)0))->name]; + + size_t undo_size; } SculptUndoNode; /* Factor of brush to have rake point following behind diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.c b/source/blender/editors/sculpt_paint/sculpt_undo.c index 794ac14483a..4e2bcab9f36 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.c +++ b/source/blender/editors/sculpt_paint/sculpt_undo.c @@ -48,6 +48,8 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_mesh_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" #include "BKE_ccg.h" #include "BKE_context.h" @@ -57,6 +59,9 @@ #include "BKE_key.h" #include "BKE_mesh.h" #include "BKE_subsurf.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_undo_system.h" #include "WM_api.h" #include "WM_types.h" @@ -64,12 +69,21 @@ #include "GPU_buffers.h" #include "ED_paint.h" +#include "ED_object.h" +#include "ED_sculpt.h" #include "bmesh.h" #include "paint_intern.h" #include "sculpt_intern.h" -/************************** Undo *************************/ + +typedef struct UndoSculpt { + ListBase nodes; + + size_t undo_size; +} UndoSculpt; + +static UndoSculpt *sculpt_undo_get_nodes(void); static void update_cb(PBVHNode *node, void *rebuild) { @@ -454,7 +468,7 @@ static int sculpt_undo_bmesh_restore(bContext *C, return false; } -static void sculpt_undo_restore(bContext *C, ListBase *lb) +static void sculpt_undo_restore_list(bContext *C, ListBase *lb) { Scene *scene = CTX_data_scene(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; @@ -574,7 +588,7 @@ static void sculpt_undo_restore(bContext *C, ListBase *lb) } } -static void sculpt_undo_free(ListBase *lb) +static void sculpt_undo_free_list(ListBase *lb) { SculptUndoNode *unode; int i; @@ -617,6 +631,8 @@ static void sculpt_undo_free(ListBase *lb) } } +/* Most likely we don't need this. */ +#if 0 static bool sculpt_undo_cleanup(bContext *C, ListBase *lb) { Object *ob = CTX_data_active_object(C); @@ -633,16 +649,17 @@ static bool sculpt_undo_cleanup(bContext *C, ListBase *lb) return false; } +#endif SculptUndoNode *sculpt_undo_get_node(PBVHNode *node) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_MESH); + UndoSculpt *usculpt = sculpt_undo_get_nodes(); - if (!lb) { + if (usculpt == NULL) { return NULL; } - return BLI_findptr(lb, node, offsetof(SculptUndoNode, node)); + return BLI_findptr(&usculpt->nodes, node, offsetof(SculptUndoNode, node)); } static void sculpt_undo_alloc_and_store_hidden(PBVH *pbvh, @@ -668,10 +685,11 @@ static void sculpt_undo_alloc_and_store_hidden(PBVH *pbvh, } } -static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, - SculptUndoType type) +static SculptUndoNode *sculpt_undo_alloc_node( + Object *ob, PBVHNode *node, + SculptUndoType type) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_MESH); + UndoSculpt *usculpt = sculpt_undo_get_nodes(); SculptUndoNode *unode; SculptSession *ss = ob->sculpt; int totvert, allvert, totgrid, maxgrid, gridsize, *grids; @@ -696,23 +714,23 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, /* general TODO, fix count_alloc */ switch (type) { case SCULPT_UNDO_COORDS: - unode->co = MEM_mapallocN(sizeof(float) * 3 * allvert, "SculptUndoNode.co"); - unode->no = MEM_mapallocN(sizeof(short) * 3 * allvert, "SculptUndoNode.no"); - undo_paint_push_count_alloc(UNDO_PAINT_MESH, - (sizeof(float) * 3 + - sizeof(short) * 3 + - sizeof(int)) * allvert); + unode->co = MEM_mapallocN(sizeof(float[3]) * allvert, "SculptUndoNode.co"); + unode->no = MEM_mapallocN(sizeof(short[3]) * allvert, "SculptUndoNode.no"); + + usculpt->undo_size = (sizeof(float[3]) + sizeof(short[3]) + sizeof(int)) * allvert; break; case SCULPT_UNDO_HIDDEN: if (maxgrid) sculpt_undo_alloc_and_store_hidden(ss->pbvh, unode); else unode->vert_hidden = BLI_BITMAP_NEW(allvert, "SculptUndoNode.vert_hidden"); - + break; case SCULPT_UNDO_MASK: unode->mask = MEM_mapallocN(sizeof(float) * allvert, "SculptUndoNode.mask"); - undo_paint_push_count_alloc(UNDO_PAINT_MESH, (sizeof(float) * sizeof(int)) * allvert); + + usculpt->undo_size += (sizeof(float) * sizeof(int)) * allvert; + break; case SCULPT_UNDO_DYNTOPO_BEGIN: case SCULPT_UNDO_DYNTOPO_END: @@ -721,7 +739,7 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, break; } - BLI_addtail(lb, unode); + BLI_addtail(&usculpt->nodes, unode); if (maxgrid) { /* multires */ @@ -798,12 +816,13 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, SculptUndoType type) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_MESH); - SculptUndoNode *unode = lb->first; + UndoSculpt *usculpt = sculpt_undo_get_nodes(); SculptSession *ss = ob->sculpt; PBVHVertexIter vd; - if (!lb->first) { + SculptUndoNode *unode = usculpt->nodes.first; + + if (unode == NULL) { unode = MEM_callocN(sizeof(*unode), __func__); BLI_strncpy(unode->idname, ob->id.name, sizeof(unode->idname)); @@ -842,7 +861,7 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, unode->bm_entry = BM_log_entry_add(ss->bm_log); } - BLI_addtail(lb, unode); + BLI_addtail(&usculpt->nodes, unode); } if (node) { @@ -883,8 +902,9 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, return unode; } -SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, - SculptUndoType type) +SculptUndoNode *sculpt_undo_push_node( + Object *ob, PBVHNode *node, + SculptUndoType type) { SculptSession *ss = ob->sculpt; SculptUndoNode *unode; @@ -954,17 +974,18 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, void sculpt_undo_push_begin(const char *name) { - ED_undo_paint_push_begin(UNDO_PAINT_MESH, name, - sculpt_undo_restore, sculpt_undo_free, sculpt_undo_cleanup); + bContext *C = NULL; /* special case, we never read from this. */ + wmWindowManager *wm = G.main->wm.first; + BKE_undosys_step_push_init_with_type(wm->undo_stack, C, name, BKE_UNDOSYS_TYPE_SCULPT); } void sculpt_undo_push_end(void) { - ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_MESH); + UndoSculpt *usculpt = sculpt_undo_get_nodes(); SculptUndoNode *unode; /* we don't need normals in the undo stack */ - for (unode = lb->first; unode; unode = unode->next) { + for (unode = usculpt->nodes.first; unode; unode = unode->next) { if (unode->no) { MEM_freeN(unode->no); unode->no = NULL; @@ -974,7 +995,97 @@ void sculpt_undo_push_end(void) BKE_pbvh_node_layer_disp_free(unode->node); } - ED_undo_paint_push_end(UNDO_PAINT_MESH); + wmWindowManager *wm = G.main->wm.first; /* XXX, avoids adding extra arg. */ + BKE_undosys_step_push(wm->undo_stack, NULL, NULL); +} + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct SculptUndoStep { + UndoStep step; + /* note: will split out into list for multi-object-sculpt-mode. */ + UndoSculpt data; +} SculptUndoStep; + +static bool sculpt_undosys_poll(bContext *C) +{ + ScrArea *sa = CTX_wm_area(C); + if (sa && (sa->spacetype == SPACE_VIEW3D)) { + Object *obact = CTX_data_active_object(C); + if (obact && (obact->mode & OB_MODE_SCULPT)) { + return true; + } + } + return false; +} + +static void sculpt_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) +{ + SculptUndoStep *us = (SculptUndoStep *)us_p; + /* dummy, memory is cleared anyway. */ + BLI_listbase_clear(&us->data.nodes); +} + +static bool sculpt_undosys_step_encode(struct bContext *UNUSED(C), UndoStep *us_p) +{ + /* dummy, encoding is done along the way by adding tiles + * to the current 'SculptUndoStep' added by encode_init. */ + SculptUndoStep *us = (SculptUndoStep *)us_p; + us->step.data_size = us->data.undo_size; + return true; +} + +static void sculpt_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_SCULPT); + BLI_assert(sculpt_undosys_poll(C)); + + SculptUndoStep *us = (SculptUndoStep *)us_p; + sculpt_undo_restore_list(C, &us->data.nodes); +} - WM_file_tag_modified(); +static void sculpt_undosys_step_free(UndoStep *us_p) +{ + SculptUndoStep *us = (SculptUndoStep *)us_p; + sculpt_undo_free_list(&us->data.nodes); } + +/* Export for ED_undo_sys. */ +void ED_sculpt_undosys_type(UndoType *ut) +{ + ut->name = "Sculpt"; + ut->poll = sculpt_undosys_poll; + ut->step_encode_init = sculpt_undosys_step_encode_init; + ut->step_encode = sculpt_undosys_step_encode; + ut->step_decode = sculpt_undosys_step_decode; + ut->step_free = sculpt_undosys_step_free; + + ut->mode = BKE_UNDOTYPE_MODE_ACCUMULATE; + ut->use_context = true; + + ut->step_size = sizeof(SculptUndoStep); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ + +static UndoSculpt *sculpt_undosys_step_get_nodes(UndoStep *us_p) +{ + SculptUndoStep *us = (SculptUndoStep *)us_p; + return &us->data; +} + +static UndoSculpt *sculpt_undo_get_nodes(void) +{ + wmWindowManager *wm = G.main->wm.first; /* XXX, avoids adding extra arg. */ + UndoStep *us = BKE_undosys_stack_init_or_active_with_type(wm->undo_stack, BKE_UNDOSYS_TYPE_SCULPT); + return sculpt_undosys_step_get_nodes(us); +} + +/** \} */ diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 9740a62d4f0..c1586964b1f 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -2658,8 +2658,9 @@ static int image_invert_exec(bContext *C, wmOperator *op) if (ibuf->mipmap[0]) ibuf->userflags |= IB_MIPMAP_INVALID; - if (support_undo) - ED_undo_paint_push_end(UNDO_PAINT_IMAGE); + if (support_undo) { + ED_image_undo_push_end(); + } /* force GPU reupload, all image is invalid */ GPU_free_image(ima); diff --git a/source/blender/editors/space_text/CMakeLists.txt b/source/blender/editors/space_text/CMakeLists.txt index 39b48f5b52c..91420a5d63a 100644 --- a/source/blender/editors/space_text/CMakeLists.txt +++ b/source/blender/editors/space_text/CMakeLists.txt @@ -48,6 +48,7 @@ set(SRC text_format_py.c text_header.c text_ops.c + text_undo.c text_format.h text_intern.h diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c index f75cd129f67..7580f22321b 100644 --- a/source/blender/editors/space_text/text_ops.c +++ b/source/blender/editors/space_text/text_ops.c @@ -756,7 +756,10 @@ void TEXT_OT_paste(wmOperatorType *ot) /* api callbacks */ ot->exec = text_paste_exec; ot->poll = text_edit_poll; - + + /* flags */ + ot->flag = OPTYPE_UNDO; + /* properties */ RNA_def_boolean(ot->srna, "selection", 0, "Selection", "Paste text selected elsewhere rather than copied (X11 only)"); } @@ -785,10 +788,13 @@ void TEXT_OT_duplicate_line(wmOperatorType *ot) ot->name = "Duplicate Line"; ot->idname = "TEXT_OT_duplicate_line"; ot->description = "Duplicate the current line"; - + /* api callbacks */ ot->exec = text_duplicate_line_exec; ot->poll = text_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* copy operator *********************/ @@ -860,6 +866,9 @@ void TEXT_OT_cut(wmOperatorType *ot) /* api callbacks */ ot->exec = text_cut_exec; ot->poll = text_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* indent operator *********************/ @@ -895,6 +904,9 @@ void TEXT_OT_indent(wmOperatorType *ot) /* api callbacks */ ot->exec = text_indent_exec; ot->poll = text_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* unindent operator *********************/ @@ -926,6 +938,9 @@ void TEXT_OT_unindent(wmOperatorType *ot) /* api callbacks */ ot->exec = text_unindent_exec; ot->poll = text_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* line break operator *********************/ @@ -970,10 +985,13 @@ void TEXT_OT_line_break(wmOperatorType *ot) ot->name = "Line Break"; ot->idname = "TEXT_OT_line_break"; ot->description = "Insert line break at cursor position"; - + /* api callbacks */ ot->exec = text_line_break_exec; ot->poll = text_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* comment operator *********************/ @@ -1003,10 +1021,13 @@ void TEXT_OT_comment(wmOperatorType *ot) ot->name = "Comment"; ot->idname = "TEXT_OT_comment"; ot->description = "Convert selected text to comment"; - + /* api callbacks */ ot->exec = text_comment_exec; ot->poll = text_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* uncomment operator *********************/ @@ -1041,6 +1062,9 @@ void TEXT_OT_uncomment(wmOperatorType *ot) /* api callbacks */ ot->exec = text_uncomment_exec; ot->poll = text_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* convert whitespace operator *********************/ @@ -1174,6 +1198,9 @@ void TEXT_OT_convert_whitespace(wmOperatorType *ot) ot->exec = text_convert_whitespace_exec; ot->poll = text_edit_poll; + /* flags */ + ot->flag = OPTYPE_UNDO; + /* properties */ RNA_def_enum(ot->srna, "type", whitespace_type_items, TO_SPACES, "Type", "Type of whitespace to convert to"); } @@ -1295,6 +1322,9 @@ void TEXT_OT_move_lines(wmOperatorType *ot) ot->exec = move_lines_exec; ot->poll = text_edit_poll; + /* flags */ + ot->flag = OPTYPE_UNDO; + /* properties */ RNA_def_enum(ot->srna, "direction", direction_items, 1, "Direction", ""); } @@ -2919,6 +2949,9 @@ void TEXT_OT_insert(wmOperatorType *ot) ot->invoke = text_insert_invoke; ot->poll = text_edit_poll; + /* flags */ + ot->flag = OPTYPE_UNDO; + /* properties */ prop = RNA_def_string(ot->srna, "text", NULL, 0, "Text", "Text to insert at the cursor position"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); @@ -3024,6 +3057,9 @@ void TEXT_OT_replace(wmOperatorType *ot) /* api callbacks */ ot->exec = text_replace_exec; ot->poll = text_space_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /******************* find set selected *********************/ @@ -3081,6 +3117,9 @@ void TEXT_OT_replace_set_selected(wmOperatorType *ot) /* api callbacks */ ot->exec = text_replace_set_selected_exec; ot->poll = text_space_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; } /****************** resolve conflict operator ******************/ @@ -3201,26 +3240,3 @@ void TEXT_OT_to_3d_object(wmOperatorType *ot) /* properties */ RNA_def_boolean(ot->srna, "split_lines", 0, "Split Lines", "Create one object per line in the text"); } - - -/************************ undo ******************************/ - -void ED_text_undo_step(bContext *C, int step) -{ - Text *text = CTX_data_edit_text(C); - - if (!text) - return; - - if (step == 1) - txt_do_undo(text); - else if (step == -1) - txt_do_redo(text); - - text_update_edited(text); - - text_update_cursor_moved(C); - text_drawcache_tag_update(CTX_wm_space_text(C), 1); - WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text); -} - diff --git a/source/blender/editors/space_text/text_undo.c b/source/blender/editors/space_text/text_undo.c new file mode 100644 index 00000000000..6bac00fc30c --- /dev/null +++ b/source/blender/editors/space_text/text_undo.c @@ -0,0 +1,170 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/space_text/text_undo.c + * \ingroup sptext + */ + +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_text_types.h" + +#include "BLI_listbase.h" +#include "BLI_array_utils.h" + +#include "BLT_translation.h" + +#include "PIL_time.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_library.h" +#include "BKE_main.h" +#include "BKE_report.h" +#include "BKE_text.h" +#include "BKE_undo_system.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_text.h" +#include "ED_curve.h" +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "text_intern.h" +#include "text_format.h" + +/* TODO(campbell): undo_system: move text undo out of text block. */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ +typedef struct TextUndoBuf { + char *buf; + int len; + int pos; +} TextUndoBuf; + +typedef struct TextUndoStep { + UndoStep step; + UndoRefID_Text text_ref; + TextUndoBuf data; +} TextUndoStep; + +static bool text_undosys_poll(bContext *C) +{ + Text *text = CTX_data_edit_text(C); + if (text == NULL) { + return false; + } + if (ID_IS_LINKED(text)) { + return false; + } + return true; +} + +static bool text_undosys_step_encode(struct bContext *C, UndoStep *us_p) +{ + TextUndoStep *us = (TextUndoStep *)us_p; + Text *text = CTX_data_edit_text(C); + us->text_ref.ptr = text; + + BLI_assert(BLI_array_is_zeroed(&us->data, 1)); + + us->data.buf = text->undo_buf; + us->data.pos = text->undo_pos; + us->data.len = text->undo_len; + + text->undo_buf = NULL; + text->undo_len = 0; + text->undo_pos = -1; + + us->step.data_size = text->undo_len; + + return true; +} + +static void text_undosys_step_decode(struct bContext *C, UndoStep *us_p, int dir) +{ + TextUndoStep *us = (TextUndoStep *)us_p; + Text *text = us->text_ref.ptr; + + /* TODO(campbell): undo_system: move undo out of Text data block. */ + text->undo_buf = us->data.buf; + text->undo_len = us->data.len; + if (dir < 0) { + text->undo_pos = us->data.pos; + txt_do_undo(text); + } + else { + text->undo_pos = -1; + txt_do_redo(text); + } + text->undo_buf = NULL; + text->undo_len = 0; + text->undo_pos = -1; + + text_update_edited(text); + text_update_cursor_moved(C); + text_drawcache_tag_update(CTX_wm_space_text(C), 1); + WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text); +} + +static void text_undosys_step_free(UndoStep *us_p) +{ + TextUndoStep *us = (TextUndoStep *)us_p; + MEM_SAFE_FREE(us->data.buf); +} + +static void text_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + TextUndoStep *us = (TextUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->text_ref)); +} + +/* Export for ED_undo_sys. */ + +void ED_text_undosys_type(UndoType *ut) +{ + ut->name = "Text"; + ut->poll = text_undosys_poll; + ut->step_encode = text_undosys_step_encode; + ut->step_decode = text_undosys_step_decode; + ut->step_free = text_undosys_step_free; + + ut->step_foreach_ID_ref = text_undosys_foreach_ID_ref; + + ut->mode = BKE_UNDOTYPE_MODE_ACCUMULATE; + ut->use_context = true; + + ut->step_size = sizeof(TextUndoStep); +} + +/** \} */ diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt index a8225bb64d1..b2c0f6ad309 100644 --- a/source/blender/editors/util/CMakeLists.txt +++ b/source/blender/editors/util/CMakeLists.txt @@ -41,9 +41,10 @@ set(INC_SYS set(SRC ed_transverts.c ed_util.c - editmode_undo.c + memfile_undo.c numinput.c undo.c + undo_system_types.c util_intern.h # general includes diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c index 57ff8b6db01..03d0b4a8d48 100644 --- a/source/blender/editors/util/ed_util.c +++ b/source/blender/editors/util/ed_util.c @@ -58,6 +58,7 @@ #include "BKE_packedFile.h" #include "BKE_paint.h" #include "BKE_screen.h" +#include "BKE_undo_system.h" #include "ED_armature.h" #include "ED_buttons.h" @@ -89,6 +90,10 @@ void ED_editors_init(bContext *C) Object *ob, *obact = (sce && sce->basact) ? sce->basact->object : NULL; ID *data; + if (wm->undo_stack == NULL) { + wm->undo_stack = BKE_undosys_stack_create(); + } + /* This is called during initialization, so we don't want to store any reports */ ReportList *reports = CTX_wm_reports(C); int reports_flag_prev = reports->flag & ~RPT_STORE; @@ -128,9 +133,15 @@ void ED_editors_exit(bContext *C) return; /* frees all editmode undos */ - undo_editmode_clear(); - ED_undo_paint_free(); - + if (G.main->wm.first) { + wmWindowManager *wm = G.main->wm.first; + /* normally we don't check for NULL undo stack, do here since it may run in different context. */ + if (wm->undo_stack) { + BKE_undosys_stack_destroy(wm->undo_stack); + wm->undo_stack = NULL; + } + } + for (sce = bmain->scene.first; sce; sce = sce->id.next) { if (sce->obedit) { Object *ob = sce->obedit; diff --git a/source/blender/editors/util/editmode_undo.c b/source/blender/editors/util/editmode_undo.c deleted file mode 100644 index fa697a0f006..00000000000 --- a/source/blender/editors/util/editmode_undo.c +++ /dev/null @@ -1,372 +0,0 @@ -/* - * ***** BEGIN GPL LICENSE BLOCK ***** - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2004 Blender Foundation - * All rights reserved. - * - * The Original Code is: all of this file. - * - * Contributor(s): none yet. - * - * ***** END GPL LICENSE BLOCK ***** - */ - -/** \file blender/editors/util/editmode_undo.c - * \ingroup edutil - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_object_types.h" - -#include "BLI_blenlib.h" -#include "BLI_utildefines.h" - -#include "BKE_blender_undo.h" -#include "BKE_context.h" -#include "BKE_depsgraph.h" -#include "BKE_global.h" - -#include "ED_util.h" -#include "ED_mesh.h" - -#include "util_intern.h" - -/* ****** XXX ***** */ -static void error(const char *UNUSED(arg)) {} -/* ****** XXX ***** */ - -typedef struct UndoElem { - struct UndoElem *next, *prev; - /** copy of edit-mode object ID */ - ID id; - /** pointer to edited object */ - Object *ob; - /** type of edited object */ - int type; - void *undodata; - uintptr_t undosize; - char name[BKE_UNDO_STR_MAX]; - - /** Use context to retrieve current edit-data. */ - void * (*getdata)(bContext * C); - /** Pointer to function freeing data. */ - void (*freedata)(void *); - /** Data to edit-mode conversion. */ - void (*to_editmode)(void *, void *, void *); - /** Edit-mode to data conversion. */ - void * (*from_editmode)(void *, void *); - /** Check if undo data is still valid. */ - int (*validate_undo)(void *, void *); -} UndoElem; - -static ListBase g_undobase = {NULL, NULL}; -static UndoElem *g_curundo = NULL; - -static void undo_restore(UndoElem *undo, void *editdata, void *obdata) -{ - if (undo) { - undo->to_editmode(undo->undodata, editdata, obdata); - } -} - -/** - * name can be a dynamic string - * See #UndoElem for callbacks docs. - * */ -void undo_editmode_push( - bContext *C, const char *name, - void * (*getdata)(bContext * C), - void (*freedata)(void *), - void (*to_editmode)(void *, void *, void *), - void *(*from_editmode)(void *, void *), - int (*validate_undo)(void *, void *)) -{ - UndoElem *uel; - Object *obedit = CTX_data_edit_object(C); - void *editdata; - int nr; - uintptr_t mem_used, mem_total, mem_max; - - /* at first here was code to prevent an "original" key to be inserted twice - * this was giving conflicts for example when mesh changed due to keys or apply */ - - /* remove all undos after (also when g_curundo == NULL) */ - while (g_undobase.last != g_curundo) { - uel = g_undobase.last; - uel->freedata(uel->undodata); - BLI_freelinkN(&g_undobase, uel); - } - - /* make new */ - g_curundo = uel = MEM_callocN(sizeof(UndoElem), "undo editmode"); - BLI_strncpy(uel->name, name, sizeof(uel->name)); - BLI_addtail(&g_undobase, uel); - - uel->getdata = getdata; - uel->freedata = freedata; - uel->to_editmode = to_editmode; - uel->from_editmode = from_editmode; - uel->validate_undo = validate_undo; - - /* limit amount to the maximum amount*/ - nr = 0; - uel = g_undobase.last; - while (uel) { - nr++; - if (nr == U.undosteps) { - break; - } - uel = uel->prev; - } - if (uel) { - while (g_undobase.first != uel) { - UndoElem *first = g_undobase.first; - first->freedata(first->undodata); - BLI_freelinkN(&g_undobase, first); - } - } - - /* copy */ - mem_used = MEM_get_memory_in_use(); - editdata = getdata(C); - g_curundo->undodata = g_curundo->from_editmode(editdata, obedit->data); - g_curundo->undosize = MEM_get_memory_in_use() - mem_used; - g_curundo->ob = obedit; - g_curundo->id = obedit->id; - g_curundo->type = obedit->type; - - if (U.undomemory != 0) { - /* limit to maximum memory (afterwards, we can't know in advance) */ - mem_total = 0; - mem_max = ((uintptr_t)U.undomemory) * 1024 * 1024; - - uel = g_undobase.last; - while (uel && uel->prev) { - mem_total += uel->undosize; - if (mem_total > mem_max) { - break; - } - uel = uel->prev; - } - - if (uel) { - if (uel->prev && uel->prev->prev) { - uel = uel->prev; - } - while (g_undobase.first != uel) { - UndoElem *first = g_undobase.first; - first->freedata(first->undodata); - BLI_freelinkN(&g_undobase, first); - } - } - } -} - -/* helper to remove clean other objects from undo stack */ -static void undo_clean_stack(bContext *C) -{ - UndoElem *uel; - Object *obedit = CTX_data_edit_object(C); - - /* global undo changes pointers, so we also allow identical names */ - /* side effect: when deleting/renaming object and start editing new one with same name */ - - uel = g_undobase.first; - while (uel) { - void *editdata = uel->getdata(C); - bool is_valid = false; - UndoElem *uel_next = uel->next; - - /* for when objects are converted, renamed, or global undo changes pointers... */ - if (uel->type == obedit->type) { - if (STREQ(uel->id.name, obedit->id.name)) { - if (uel->validate_undo == NULL) { - is_valid = true; - } - else if (uel->validate_undo(uel->undodata, editdata)) { - is_valid = true; - } - } - } - if (is_valid) { - uel->ob = obedit; - } - else { - if (uel == g_curundo) { - g_curundo = NULL; - } - - uel->freedata(uel->undodata); - BLI_freelinkN(&g_undobase, uel); - } - - uel = uel_next; - } - - if (g_curundo == NULL) { - g_curundo = g_undobase.last; - } -} - -/** - * 1 = an undo, -1 is a redo. - * we have to make sure 'g_curundo' remains at current situation - */ -void undo_editmode_step(bContext *C, int step) -{ - Object *obedit = CTX_data_edit_object(C); - - /* prevent undo to happen on wrong object, stack can be a mix */ - undo_clean_stack(C); - - if (step == 0) { - undo_restore(g_curundo, g_curundo->getdata(C), obedit->data); - } - else if (step == 1) { - if (g_curundo == NULL || g_curundo->prev == NULL) { - error("No more steps to undo"); - } - else { - if (G.debug & G_DEBUG) printf("undo %s\n", g_curundo->name); - g_curundo = g_curundo->prev; - undo_restore(g_curundo, g_curundo->getdata(C), obedit->data); - } - } - else { - /* g_curundo has to remain current situation! */ - if (g_curundo == NULL || g_curundo->next == NULL) { - error("No more steps to redo"); - } - else { - undo_restore(g_curundo->next, g_curundo->getdata(C), obedit->data); - g_curundo = g_curundo->next; - if (G.debug & G_DEBUG) printf("redo %s\n", g_curundo->name); - } - } - - /* special case for editmesh, mode must be copied back to the scene */ - if (obedit->type == OB_MESH) { - EDBM_selectmode_to_scene(C); - } - - DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); - - /* XXX notifiers */ -} - -void undo_editmode_clear(void) -{ - UndoElem *uel; - - uel = g_undobase.first; - while (uel) { - uel->freedata(uel->undodata); - uel = uel->next; - } - BLI_freelistN(&g_undobase); - g_curundo = NULL; -} - -/* based on index nr it does a restore */ -void undo_editmode_number(bContext *C, int nr) -{ - UndoElem *uel; - int a = 1; - - for (uel = g_undobase.first; uel; uel = uel->next, a++) { - if (a == nr) { - break; - } - } - g_curundo = uel; - undo_editmode_step(C, 0); -} - -void undo_editmode_name(bContext *C, const char *undoname) -{ - UndoElem *uel; - - for (uel = g_undobase.last; uel; uel = uel->prev) { - if (STREQ(undoname, uel->name)) { - break; - } - } - if (uel && uel->prev) { - g_curundo = uel->prev; - undo_editmode_step(C, 0); - } -} - -/** - * \a undoname is optional, when NULL it just checks for existing undo steps - */ -bool undo_editmode_is_valid(const char *undoname) -{ - if (undoname) { - UndoElem *uel; - - for (uel = g_undobase.last; uel; uel = uel->prev) { - if (STREQ(undoname, uel->name)) { - break; - } - } - return uel != NULL; - } - return g_undobase.last != g_undobase.first; -} - - -/** - * Get name of undo item, return null if no item with this index. - * - * if active pointer, set it to 1 if true - */ -const char *undo_editmode_get_name(bContext *C, int nr, bool *r_active) -{ - UndoElem *uel; - - /* prevent wrong numbers to be returned */ - undo_clean_stack(C); - - if (r_active) { - *r_active = false; - } - - uel = BLI_findlink(&g_undobase, nr); - if (uel) { - if (r_active && (uel == g_curundo)) { - *r_active = true; - } - return uel->name; - } - return NULL; -} - - -void *undo_editmode_get_prev(Object *ob) -{ - UndoElem *ue = g_undobase.last; - if (ue && ue->prev && ue->prev->ob == ob) { - return ue->prev->undodata; - } - return NULL; -} diff --git a/source/blender/editors/util/memfile_undo.c b/source/blender/editors/util/memfile_undo.c new file mode 100644 index 00000000000..95af0c48147 --- /dev/null +++ b/source/blender/editors/util/memfile_undo.c @@ -0,0 +1,149 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/util/memfile_undo.c + * \ingroup edutil + * + * Wrapper between 'BKE_undo.h' and 'BKE_undo_system.h' + */ + +#include "BLI_utildefines.h" +#include "BLI_sys_types.h" + +#include "DNA_object_enums.h" + +#include "BKE_blender_undo.h" +#include "BKE_context.h" +#include "BKE_undo_system.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_util.h" +#include "ED_render.h" + + +#include "../blenloader/BLO_undofile.h" + +#include "util_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct MemFileUndoStep { + UndoStep step; + MemFileUndoData *data; +} MemFileUndoStep; + +static bool memfile_undosys_poll(bContext *UNUSED(C)) +{ + /* other poll functions must run first, this is a catch-all. */ + + if ((U.uiflag & USER_GLOBALUNDO) == 0) { + return false; + } + return true; +} + +static bool memfile_undosys_step_encode(struct bContext *C, UndoStep *us_p) +{ + MemFileUndoStep *us = (MemFileUndoStep *)us_p; + + /* Important we only use 'main' from the context (see: BKE_undosys_stack_init_from_main). */ + struct Main *bmain = CTX_data_main(C); + + /* can be NULL, use when set. */ + MemFileUndoStep *us_prev = (MemFileUndoStep *)BKE_undosys_step_same_type_prev(us_p); + us->data = BKE_memfile_undo_encode(bmain, us_prev ? us_prev->data : NULL); + us->step.data_size = us->data->undo_size; + return true; +} + +static void memfile_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + /* Loading the content will correctly switch into compatible non-object modes. */ + ED_object_mode_set(C, OB_MODE_OBJECT); + + /* This is needed so undoing/redoing doesn't crash with threaded previews going */ + ED_viewport_render_kill_jobs(CTX_wm_manager(C), CTX_data_main(C), true); + MemFileUndoStep *us = (MemFileUndoStep *)us_p; + BKE_memfile_undo_decode(us->data, C); + + WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, CTX_data_scene(C)); +} + +static void memfile_undosys_step_free(UndoStep *us_p) +{ + /* To avoid unnecessary slow down, free backwards (so we don't need to merge when clearing all). */ + MemFileUndoStep *us = (MemFileUndoStep *)us_p; + if (us_p->next != NULL) { + UndoStep *us_next_p = BKE_undosys_step_same_type_next(us_p); + if (us_next_p != NULL) { + MemFileUndoStep *us_next = (MemFileUndoStep *)us_next_p; + BLO_memfile_merge(&us->data->memfile, &us_next->data->memfile); + } + } + + BKE_memfile_undo_free(us->data); +} + +/* Export for ED_undo_sys. */ +void ED_memfile_undosys_type(UndoType *ut) +{ + ut->name = "Global Undo"; + ut->poll = memfile_undosys_poll; + ut->step_encode = memfile_undosys_step_encode; + ut->step_decode = memfile_undosys_step_decode; + ut->step_free = memfile_undosys_step_free; + + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(MemFileUndoStep); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ + +/** + * Ideally we wouldn't need to export global undo internals, there are some cases where it's needed though. + */ +static struct MemFile *ed_undosys_step_get_memfile(UndoStep *us_p) +{ + MemFileUndoStep *us = (MemFileUndoStep *)us_p; + return &us->data->memfile; +} + +struct MemFile *ED_undosys_stack_memfile_get_active(UndoStack *ustack) +{ + UndoStep *us = BKE_undosys_stack_active_with_type(ustack, BKE_UNDOSYS_TYPE_MEMFILE); + if (us) { + return ed_undosys_step_get_memfile(us); + } + return NULL; +} + + +/** \} */ diff --git a/source/blender/editors/util/undo.c b/source/blender/editors/util/undo.c index d791818331e..fea3360a39c 100644 --- a/source/blender/editors/util/undo.c +++ b/source/blender/editors/util/undo.c @@ -47,6 +47,7 @@ #include "BKE_global.h" #include "BKE_main.h" #include "BKE_screen.h" +#include "BKE_undo_system.h" #include "ED_armature.h" #include "ED_particle.h" @@ -77,38 +78,27 @@ void ED_undo_push(bContext *C, const char *str) { - Object *obedit = CTX_data_edit_object(C); - Object *obact = CTX_data_active_object(C); - - if (G.debug & G_DEBUG) + if (G.debug & G_DEBUG) { printf("%s: %s\n", __func__, str); - - if (obedit) { - if (U.undosteps == 0) return; - - if (obedit->type == OB_MESH) - undo_push_mesh(C, str); - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) - undo_push_curve(C, str); - else if (obedit->type == OB_FONT) - undo_push_font(C, str); - else if (obedit->type == OB_MBALL) - undo_push_mball(C, str); - else if (obedit->type == OB_LATTICE) - undo_push_lattice(C, str); - else if (obedit->type == OB_ARMATURE) - undo_push_armature(C, str); } - else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) { - if (U.undosteps == 0) return; + const int steps = U.undosteps; - PE_undo_push(CTX_data_scene(C), str); + if (steps <= 0) { + return; } - else if (obact && obact->mode & OB_MODE_SCULPT) { - /* do nothing for now */ + + wmWindowManager *wm = CTX_wm_manager(C); + + /* Only apply limit if this is the last undo step. */ + if (wm->undo_stack->step_active && (wm->undo_stack->step_active->next == NULL)) { + BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, steps - 1, 0); } - else { - BKE_undo_write(C, str); + + BKE_undosys_step_push(wm->undo_stack, C, str); + + if (U.undomemory != 0) { + const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024; + BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, 0, memory_limit); } WM_file_tag_modified(); @@ -119,11 +109,8 @@ static int ed_undo_step(bContext *C, int step, const char *undoname) { wmWindowManager *wm = CTX_wm_manager(C); wmWindow *win = CTX_wm_window(C); - Main *bmain = CTX_data_main(C); + // Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); - Object *obedit = CTX_data_edit_object(C); - Object *obact = CTX_data_active_object(C); - ScrArea *sa = CTX_wm_area(C); /* undo during jobs are running can easily lead to freeing data using by jobs, * or they can just lead to freezing job in some other cases */ @@ -131,100 +118,45 @@ static int ed_undo_step(bContext *C, int step, const char *undoname) return OPERATOR_CANCELLED; } + /* TODO(campbell): undo_system: use undo system */ /* grease pencil can be can be used in plenty of spaces, so check it first */ if (ED_gpencil_session_active()) { return ED_undo_gpencil_step(C, step, undoname); } - if (sa && (sa->spacetype == SPACE_IMAGE)) { - SpaceImage *sima = (SpaceImage *)sa->spacedata.first; - - if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { - if (!ED_undo_paint_step(C, UNDO_PAINT_IMAGE, step, undoname) && undoname) { - if (U.uiflag & USER_GLOBALUNDO) { - ED_viewport_render_kill_jobs(wm, bmain, true); - BKE_undo_name(C, undoname); - } - } - - WM_event_add_notifier(C, NC_WINDOW, NULL); - return OPERATOR_FINISHED; + /* Undo System */ + { + if (undoname) { + UndoStep *step_data = BKE_undosys_step_find_by_name(wm->undo_stack, undoname); + BKE_undosys_step_undo_with_data(wm->undo_stack, C, step_data); } - } - - if (sa && (sa->spacetype == SPACE_TEXT)) { - ED_text_undo_step(C, step); - } - else if (obedit) { - if (OB_TYPE_SUPPORT_EDITMODE(obedit->type)) { - if (undoname) - undo_editmode_name(C, undoname); - else - undo_editmode_step(C, step); - - WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); + else { + BKE_undosys_step_undo_compat_only(wm->undo_stack, C, step); } } - else { - /* Note: we used to do a fall-through here where if the - * mode-specific undo system had no more steps to undo (or - * redo), the global undo would run. - * - * That was inconsistent with editmode, and also makes for - * unecessarily tricky interaction with the other undo - * systems. */ - if (obact && obact->mode & OB_MODE_TEXTURE_PAINT) { - ED_undo_paint_step(C, UNDO_PAINT_IMAGE, step, undoname); - } - else if (obact && obact->mode & OB_MODE_SCULPT) { - ED_undo_paint_step(C, UNDO_PAINT_MESH, step, undoname); - } - else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) { - if (step == 1) - PE_undo(scene); - else - PE_redo(scene); - } - else if (U.uiflag & USER_GLOBALUNDO) { - // note python defines not valid here anymore. - //#ifdef WITH_PYTHON - // XXX BPY_scripts_clear_pyobjects(); - //#endif - - /* for global undo/redo we should just clear the editmode stack */ - /* for example, texface stores image pointers */ - undo_editmode_clear(); - - ED_viewport_render_kill_jobs(wm, bmain, true); - - if (undoname) - BKE_undo_name(C, undoname); - else - BKE_undo_step(C, step); - scene = CTX_data_scene(C); - - WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); - } - } - WM_event_add_notifier(C, NC_WINDOW, NULL); WM_event_add_notifier(C, NC_WM | ND_UNDO, NULL); if (win) { win->addmousemove = true; } - + return OPERATOR_FINISHED; } void ED_undo_grouped_push(bContext *C, const char *str) { /* do nothing if previous undo task is the same as this one (or from the same undo group) */ - const char *last_undo = BKE_undo_get_name_last(); + { + wmWindowManager *wm = CTX_wm_manager(C); + if (wm->undo_stack->steps.last) { + const UndoStep *us = wm->undo_stack->steps.last; + if (STREQ(str, us->name)) { + return; + } + } - if (last_undo && STREQ(str, last_undo)) { - return; } /* push as usual */ @@ -265,47 +197,8 @@ void ED_undo_pop_op(bContext *C, wmOperator *op) /* name optionally, function used to check for operator redo panel */ bool ED_undo_is_valid(const bContext *C, const char *undoname) { - Object *obedit = CTX_data_edit_object(C); - Object *obact = CTX_data_active_object(C); - ScrArea *sa = CTX_wm_area(C); - - if (sa && sa->spacetype == SPACE_IMAGE) { - SpaceImage *sima = (SpaceImage *)sa->spacedata.first; - - if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { - return 1; - } - } - - if (sa && (sa->spacetype == SPACE_TEXT)) { - return 1; - } - else if (obedit) { - if (OB_TYPE_SUPPORT_EDITMODE(obedit->type)) { - return undo_editmode_is_valid(undoname); - } - } - else { - - /* if below tests fail, global undo gets executed */ - - if (obact && obact->mode & OB_MODE_TEXTURE_PAINT) { - if (ED_undo_paint_is_valid(UNDO_PAINT_IMAGE, undoname)) - return 1; - } - else if (obact && obact->mode & OB_MODE_SCULPT) { - if (ED_undo_paint_is_valid(UNDO_PAINT_MESH, undoname)) - return 1; - } - else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) { - return PE_undo_is_valid(CTX_data_scene(C)); - } - - if (U.uiflag & USER_GLOBALUNDO) { - return BKE_undo_is_valid(undoname); - } - } - return 0; + wmWindowManager *wm = CTX_wm_manager(C); + return BKE_undosys_stack_has_undo(wm->undo_stack, undoname); } static int ed_undo_exec(bContext *C, wmOperator *UNUSED(op)) @@ -483,111 +376,45 @@ void ED_undo_operator_repeat_cb_evt(bContext *C, void *arg_op, int UNUSED(arg_ev /* ************************** */ -enum { - UNDOSYSTEM_GLOBAL = 1, - UNDOSYSTEM_EDITMODE = 2, - UNDOSYSTEM_PARTICLE = 3, - UNDOSYSTEM_IMAPAINT = 4, - UNDOSYSTEM_SCULPT = 5, -}; - -static int get_undo_system(bContext *C) -{ - Object *obact = CTX_data_active_object(C); - Object *obedit = CTX_data_edit_object(C); - ScrArea *sa = CTX_wm_area(C); - - /* first check for editor undo */ - if (sa && (sa->spacetype == SPACE_IMAGE)) { - SpaceImage *sima = (SpaceImage *)sa->spacedata.first; - - if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { - if (!ED_undo_paint_empty(UNDO_PAINT_IMAGE)) - return UNDOSYSTEM_IMAPAINT; - } - } - /* find out which undo system */ - if (obedit) { - if (OB_TYPE_SUPPORT_EDITMODE(obedit->type)) { - return UNDOSYSTEM_EDITMODE; - } - } - else { - if (obact) { - if (obact->mode & OB_MODE_PARTICLE_EDIT) - return UNDOSYSTEM_PARTICLE; - else if (obact->mode & OB_MODE_TEXTURE_PAINT) { - if (!ED_undo_paint_empty(UNDO_PAINT_IMAGE)) - return UNDOSYSTEM_IMAPAINT; - } - else if (obact->mode & OB_MODE_SCULPT) { - if (!ED_undo_paint_empty(UNDO_PAINT_MESH)) - return UNDOSYSTEM_SCULPT; - } - } - if (U.uiflag & USER_GLOBALUNDO) - return UNDOSYSTEM_GLOBAL; - } - - return 0; -} - /* create enum based on undo items */ -static const EnumPropertyItem *rna_undo_itemf(bContext *C, int undosys, int *totitem) +static const EnumPropertyItem *rna_undo_itemf(bContext *C, int *totitem) { EnumPropertyItem item_tmp = {0}, *item = NULL; int i = 0; - bool active; - - while (true) { - const char *name = NULL; - - if (undosys == UNDOSYSTEM_PARTICLE) { - name = PE_undo_get_name(CTX_data_scene(C), i, &active); - } - else if (undosys == UNDOSYSTEM_EDITMODE) { - name = undo_editmode_get_name(C, i, &active); - } - else if (undosys == UNDOSYSTEM_IMAPAINT) { - name = ED_undo_paint_get_name(C, UNDO_PAINT_IMAGE, i, &active); - } - else if (undosys == UNDOSYSTEM_SCULPT) { - name = ED_undo_paint_get_name(C, UNDO_PAINT_MESH, i, &active); - } - else { - name = BKE_undo_get_name(i, &active); - } - - if (name) { - item_tmp.identifier = name; + + wmWindowManager *wm = CTX_wm_manager(C); + if (wm->undo_stack == NULL) { + return NULL; + } + + for (UndoStep *us = wm->undo_stack->steps.first; us; us = us->next, i++) { + if (us->skip == false) { + item_tmp.identifier = us->name; /* XXX This won't work with non-default contexts (e.g. operators) :/ */ - item_tmp.name = IFACE_(name); - if (active) + item_tmp.name = IFACE_(us->name); + if (us == wm->undo_stack->step_active) { item_tmp.icon = ICON_RESTRICT_VIEW_OFF; - else + } + else { item_tmp.icon = ICON_NONE; - item_tmp.value = i++; + } + item_tmp.value = i; RNA_enum_item_add(&item, totitem, &item_tmp); } - else - break; } - RNA_enum_item_end(&item, totitem); - + return item; } static int undo_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - int undosys, totitem = 0; - - undosys = get_undo_system(C); - - if (undosys) { - const EnumPropertyItem *item = rna_undo_itemf(C, undosys, &totitem); - + int totitem = 0; + + { + const EnumPropertyItem *item = rna_undo_itemf(C, &totitem); + if (totitem > 0) { uiPopupMenu *pup = UI_popup_menu_begin(C, RNA_struct_ui_name(op->type->srna), ICON_NONE); uiLayout *layout = UI_popup_menu_layout(pup); @@ -621,30 +448,12 @@ static int undo_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE /* note: also check ed_undo_step() in top if you change notifiers */ static int undo_history_exec(bContext *C, wmOperator *op) { - if (RNA_struct_property_is_set(op->ptr, "item")) { - int undosys = get_undo_system(C); - int item = RNA_int_get(op->ptr, "item"); - - if (undosys == UNDOSYSTEM_PARTICLE) { - PE_undo_number(CTX_data_scene(C), item); - } - else if (undosys == UNDOSYSTEM_EDITMODE) { - undo_editmode_number(C, item + 1); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); - } - else if (undosys == UNDOSYSTEM_IMAPAINT) { - ED_undo_paint_step_num(C, UNDO_PAINT_IMAGE, item); - } - else if (undosys == UNDOSYSTEM_SCULPT) { - ED_undo_paint_step_num(C, UNDO_PAINT_MESH, item); - } - else { - ED_viewport_render_kill_jobs(CTX_wm_manager(C), CTX_data_main(C), true); - BKE_undo_number(C, item); - WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, CTX_data_scene(C)); - } + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "item"); + if (RNA_property_is_set(op->ptr, prop)) { + int item = RNA_property_int_get(op->ptr, prop); + wmWindowManager *wm = CTX_wm_manager(C); + BKE_undosys_step_undo_from_index(wm->undo_stack, C, item); WM_event_add_notifier(C, NC_WINDOW, NULL); - return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; diff --git a/source/blender/editors/util/undo_system_types.c b/source/blender/editors/util/undo_system_types.c new file mode 100644 index 00000000000..a326d9eb859 --- /dev/null +++ b/source/blender/editors/util/undo_system_types.c @@ -0,0 +1,74 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/util/undo_system_types.c + * \ingroup edutil + */ + +#include + +#include "BLI_utildefines.h" + + +#include "ED_armature.h" +#include "ED_curve.h" +#include "ED_lattice.h" +#include "ED_mball.h" +#include "ED_mesh.h" +#include "ED_paint.h" +#include "ED_particle.h" +#include "ED_sculpt.h" +#include "ED_text.h" +#include "ED_util.h" +#include "util_intern.h" + +/* Keep last */ +#include "BKE_undo_system.h" + +void ED_undosys_type_init(void) +{ + /* Edit Modes */ + BKE_undosys_type_append(ED_armature_undosys_type); + BKE_undosys_type_append(ED_curve_undosys_type); + BKE_undosys_type_append(ED_font_undosys_type); + BKE_undosys_type_append(ED_lattice_undosys_type); + BKE_undosys_type_append(ED_mball_undosys_type); + BKE_undosys_type_append(ED_mesh_undosys_type); + + /* Paint Modes */ + BKE_UNDOSYS_TYPE_IMAGE = BKE_undosys_type_append(ED_image_undosys_type); + + BKE_UNDOSYS_TYPE_SCULPT = BKE_undosys_type_append(ED_sculpt_undosys_type); + + BKE_UNDOSYS_TYPE_PARTICLE = BKE_undosys_type_append(ED_particle_undosys_type); + + BKE_UNDOSYS_TYPE_PAINTCURVE = BKE_undosys_type_append(ED_paintcurve_undosys_type); + + /* Text editor */ + BKE_undosys_type_append(ED_text_undosys_type); + + /* Keep global undo last (as a fallback). */ + BKE_UNDOSYS_TYPE_MEMFILE = BKE_undosys_type_append(ED_memfile_undosys_type); +} + +void ED_undosys_type_free(void) +{ + BKE_undosys_type_free_all(); +} diff --git a/source/blender/editors/util/util_intern.h b/source/blender/editors/util/util_intern.h index 0f650330951..6eda3900e91 100644 --- a/source/blender/editors/util/util_intern.h +++ b/source/blender/editors/util/util_intern.h @@ -34,13 +34,11 @@ /* internal exports only */ -/* editmode_undo.c */ -void undo_editmode_name(struct bContext *C, const char *undoname); -bool undo_editmode_is_valid(const char *undoname); -const char *undo_editmode_get_name(struct bContext *C, int nr, bool *r_active); -void *undo_editmode_get_prev(struct Object *ob); -void undo_editmode_step(struct bContext *C, int step); -void undo_editmode_number(struct bContext *C, int nr); +struct UndoType; +struct Main; +struct Scene; -#endif /* __UTIL_INTERN_H__ */ +/* memfile_undo.c */ +void ED_memfile_undosys_type(struct UndoType *ut); +#endif /* __UTIL_INTERN_H__ */ diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index 687a883f792..8d8bc4d7d76 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -58,6 +58,7 @@ struct ReportList; struct Report; struct uiLayout; struct Stereo3dFormat; +struct UndoStep; #define OP_MAX_TYPENAME 64 #define KMAP_MAX_NAME 64 @@ -154,6 +155,8 @@ typedef struct wmWindowManager { ListBase timers; /* active timers */ struct wmTimer *autosavetimer; /* timer for auto save */ + struct UndoStack *undo_stack; /* all undo history (runtime only). */ + char is_interface_locked; /* indicates whether interface is locked for user interaction */ char par[7]; } wmWindowManager; @@ -313,7 +316,6 @@ typedef struct wmKeyMap { short kmi_id; /* last kmi id */ /* runtime */ - /** Verify if enabled in the current context, use #WM_keymap_poll instead of direct calls. */ int (*poll)(struct bContext *); /** For modal, #EnumPropertyItem for now. */ const void *modal_items; diff --git a/source/blender/windowmanager/WM_undo.h b/source/blender/windowmanager/WM_undo.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index 9e877a83b3e..8be7555b34a 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -61,6 +61,7 @@ #include "wm.h" #include "ED_screen.h" +#include "BKE_undo_system.h" #ifdef WITH_PYTHON #include "BPY_extern.h" @@ -485,7 +486,12 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm) WM_drag_free_list(&wm->drags); wm_reports_free(wm); - + + if (wm->undo_stack) { + BKE_undosys_stack_destroy(wm->undo_stack); + wm->undo_stack = NULL; + } + if (C && CTX_wm_manager(C) == wm) CTX_wm_manager_set(C, NULL); } diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index e0b57effbe9..1d96d291dc4 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -87,9 +87,11 @@ #include "BKE_sound.h" #include "BKE_scene.h" #include "BKE_screen.h" +#include "BKE_undo_system.h" #include "BLO_readfile.h" #include "BLO_writefile.h" +#include "BLO_undofile.h" /* to save from an undo memfile */ #include "RNA_access.h" #include "RNA_define.h" @@ -522,9 +524,13 @@ static void wm_file_read_post(bContext *C, const bool is_startup_file, const boo } if (!G.background) { -// undo_editmode_clear(); - BKE_undo_reset(); - BKE_undo_write(C, "original"); /* save current state */ + if (wm->undo_stack == NULL) { + wm->undo_stack = BKE_undosys_stack_create(); + } + else { + BKE_undosys_stack_clear(wm->undo_stack); + } + BKE_undosys_stack_init_from_main(wm->undo_stack, CTX_data_main(C)); } } @@ -596,10 +602,6 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) success = true; } -#if 0 - else if (retval == BKE_READ_EXOTIC_OK_OTHER) - BKE_undo_write(C, "Import file"); -#endif else if (retval == BKE_READ_EXOTIC_FAIL_OPEN) { BKE_reportf(reports, RPT_ERROR, "Cannot read file '%s': %s", filepath, errno ? strerror(errno) : TIP_("unable to open the file")); @@ -1271,7 +1273,10 @@ void wm_autosave_timer(const bContext *C, wmWindowManager *wm, wmTimer *UNUSED(w if (U.uiflag & USER_GLOBALUNDO) { /* fast save of last undobuffer, now with UI */ - BKE_undo_save_file(filepath); + struct MemFile *memfile = ED_undosys_stack_memfile_get_active(wm->undo_stack); + if (memfile) { + BLO_memfile_write_file(memfile, filepath); + } } else { /* save as regular blend file */ diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 2743216ee07..b9ac05e0c57 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -55,6 +55,7 @@ #include "BLI_utildefines.h" #include "BLO_writefile.h" +#include "BLO_undofile.h" #include "BKE_blender.h" #include "BKE_blender_undo.h" @@ -149,11 +150,6 @@ static void wm_free_reports(bContext *C) BKE_reports_clear(reports); } -static void wm_undo_kill_callback(bContext *C) -{ - WM_jobs_kill_all_except(CTX_wm_manager(C), CTX_wm_screen(C)); -} - bool wm_start_with_console = false; /* used in creator.c */ /* only called once, for startup */ @@ -172,7 +168,7 @@ void WM_init(bContext *C, int argc, const char **argv) WM_menutype_init(); WM_uilisttype_init(); - BKE_undo_callback_wm_kill_jobs_set(wm_undo_kill_callback); + ED_undosys_type_init(); BKE_library_callback_free_window_manager_set(wm_close_and_free); /* library.c */ BKE_library_callback_free_notifier_reference_set(WM_main_remove_notifier_reference); /* library.c */ @@ -478,7 +474,8 @@ void WM_exit_ext(bContext *C, const bool do_python) wmWindow *win; if (!G.background) { - if ((U.uiflag2 & USER_KEEP_SESSION) || BKE_undo_is_valid(NULL)) { + struct MemFile *undo_memfile = wm->undo_stack ? ED_undosys_stack_memfile_get_active(wm->undo_stack) : NULL; + if ((U.uiflag2 & USER_KEEP_SESSION) || (undo_memfile != NULL)) { /* save the undo state as quit.blend */ char filename[FILE_MAX]; bool has_edited; @@ -489,7 +486,7 @@ void WM_exit_ext(bContext *C, const bool do_python) has_edited = ED_editors_flush_edits(C, false); if ((has_edited && BLO_write_file(CTX_data_main(C), filename, fileflags, NULL, NULL)) || - BKE_undo_save_file(filename)) + (undo_memfile && BLO_memfile_write_file(undo_memfile, filename))) { printf("Saved session recovery to '%s'\n", filename); } @@ -512,11 +509,13 @@ void WM_exit_ext(bContext *C, const bool do_python) wm_dropbox_free(); WM_menutype_free(); WM_uilisttype_free(); - + /* all non-screen and non-space stuff editors did, like editmode */ if (C) ED_editors_exit(C); + ED_undosys_type_free(); + // XXX // BIF_GlobalReebFree(); // BIF_freeRetarget(); @@ -594,8 +593,6 @@ void WM_exit_ext(bContext *C, const bool do_python) GPU_exit(); } - BKE_undo_reset(); - ED_file_exit(); /* for fsmenu */ UI_exit(); diff --git a/source/creator/creator_signals.c b/source/creator/creator_signals.c index 81e6178c502..feb108da289 100644 --- a/source/creator/creator_signals.c +++ b/source/creator/creator_signals.c @@ -64,6 +64,7 @@ #include "BKE_main.h" #include "BKE_report.h" + /* for passing information between creator and gameengine */ #ifdef WITH_GAMEENGINE # include "BL_System.h" @@ -75,6 +76,12 @@ #include "creator_intern.h" /* own include */ +// #define USE_WRITE_CRASH_BLEND +#ifdef USE_WRITE_CRASH_BLEND +# include "BKE_undo_system.h" +# include "BLO_undofile.h" +#endif + /* set breakpoints here when running in debug mode, useful to catch floating point errors */ #if defined(__linux__) || defined(_WIN32) || defined(OSX_SSE_FPE) static void sig_handle_fpe(int UNUSED(sig)) @@ -110,29 +117,32 @@ static void sig_handle_crash_backtrace(FILE *fp) static void sig_handle_crash(int signum) { + wmWindowManager *wm = G.main->wm.first; -#if 0 - { - char fname[FILE_MAX]; +#ifdef USE_WRITE_CRASH_BLEND + if (wm->undo_stack) { + struct MemFile *memfile = BKE_undosys_stack_memfile_get_active(wm->undo_stack); + if (memfile) { + char fname[FILE_MAX]; - if (!G.main->name[0]) { - BLI_make_file_string("/", fname, BKE_tempdir_base(), "crash.blend"); - } - else { - BLI_strncpy(fname, G.main->name, sizeof(fname)); - BLI_replace_extension(fname, sizeof(fname), ".crash.blend"); - } + if (!G.main->name[0]) { + BLI_make_file_string("/", fname, BKE_tempdir_base(), "crash.blend"); + } + else { + BLI_strncpy(fname, G.main->name, sizeof(fname)); + BLI_replace_extension(fname, sizeof(fname), ".crash.blend"); + } - printf("Writing: %s\n", fname); - fflush(stdout); + printf("Writing: %s\n", fname); + fflush(stdout); - BKE_undo_save_file(fname); + BLO_memfile_write_file(memfile, fname); + } } #endif FILE *fp; char header[512]; - wmWindowManager *wm = G.main->wm.first; char fname[FILE_MAX]; @@ -338,4 +348,4 @@ void main_signal_setup_fpe(void) #endif } -#endif /* WITH_PYTHON_MODULE */ \ No newline at end of file +#endif /* WITH_PYTHON_MODULE */ -- cgit v1.2.3