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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
170
source/blender/editors/space_text/text_undo.c
Normal file
170
source/blender/editors/space_text/text_undo.c
Normal file
@@ -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 <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
Reference in New Issue
Block a user