diff options
author | Hans Goudey <h.goudey@me.com> | 2020-05-12 03:55:46 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2020-05-12 04:32:14 +0300 |
commit | 1e12468b84d23e690eab0f5434d894e945694305 (patch) | |
tree | 2c78f9d378293d1fb61c71fc6b58caa243e1929f /source/blender | |
parent | 542ff416e274aa1a7663e1bc2a7c29dabe072dff (diff) |
UI: undo/redo support for text fields
Support undo/redo when editing text buttons.
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/editors/interface/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_handlers.c | 44 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 12 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_undo.c | 139 |
4 files changed, 195 insertions, 1 deletions
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 8e93fc2e379..c2c27af9770 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -71,6 +71,7 @@ set(SRC interface_templates.c interface_template_search_menu.c interface_template_search_operator.c + interface_undo.c interface_utils.c interface_widgets.c resources.c diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 8acfc9e915c..d229419a958 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -381,6 +381,9 @@ typedef struct uiHandleButtonData { uiSelectContextStore select_others; #endif + /* Text field undo. */ + struct uiUndoStack_Text *undo_stack_text; + /* post activate */ uiButtonActivateType posttype; uiBut *postbut; @@ -3308,6 +3311,10 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) but->selsta = 0; but->selend = len; + /* Initialize undo history tracking. */ + data->undo_stack_text = ui_textedit_undo_stack_create(); + ui_textedit_undo_push(data->undo_stack_text, but->editstr, but->pos); + /* optional searchbox */ if (but->type == UI_BTYPE_SEARCH_MENU) { data->searchbox = but->search->create_fn(C, data->region, but); @@ -3363,6 +3370,10 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data) WM_cursor_modal_restore(win); + /* Free text undo history text blocks. */ + ui_textedit_undo_stack_destroy(data->undo_stack_text); + data->undo_stack_text = NULL; + #ifdef WITH_INPUT_IME if (win->ime_data) { ui_textedit_ime_end(win, but); @@ -3442,7 +3453,7 @@ static void ui_do_but_textedit( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { int retval = WM_UI_HANDLER_CONTINUE; - bool changed = false, inbox = false, update = false; + bool changed = false, inbox = false, update = false, skip_undo_push = false; #ifdef WITH_INPUT_IME wmWindow *win = CTX_wm_window(C); @@ -3674,6 +3685,32 @@ static void ui_do_but_textedit( } retval = WM_UI_HANDLER_BREAK; break; + case EVT_ZKEY: { + /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */ + + const bool is_redo = (event->shift != 0); + if ( +#if defined(__APPLE__) + (event->oskey && !IS_EVENT_MOD(event, alt, ctrl)) || +#endif + (event->ctrl && !IS_EVENT_MOD(event, alt, oskey))) { + int undo_pos; + const char *undo_str = ui_textedit_undo( + data->undo_stack_text, is_redo ? 1 : -1, &undo_pos); + if (undo_str != NULL) { + ui_textedit_string_set(but, data, undo_str); + + /* Set the cursor & clear selection. */ + but->pos = undo_pos; + but->selsta = but->pos; + but->selend = but->pos; + changed = true; + } + } + skip_undo_push = true; + retval = WM_UI_HANDLER_BREAK; + break; + } } if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE) @@ -3727,6 +3764,11 @@ static void ui_do_but_textedit( #endif if (changed) { + /* The undo stack may be NULL if an event exits editing. */ + if ((skip_undo_push == false) && (data->undo_stack_text != NULL)) { + ui_textedit_undo_push(data->undo_stack_text, data->str, but->pos); + } + /* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */ if (update && data->interactive) { ui_apply_but(C, block, but, data, true); diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 26e8447a0b6..1c6b9632ddb 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -26,6 +26,7 @@ #include "BLI_compiler_attrs.h" #include "DNA_listBase.h" +#include "DNA_screen_types.h" #include "RNA_types.h" #include "UI_interface.h" #include "UI_resources.h" @@ -39,6 +40,7 @@ struct bContextStore; struct uiHandleButtonData; struct uiLayout; struct uiStyle; +struct uiUndoStack_Text; struct uiWidgetColors; struct wmEvent; struct wmKeyConfig; @@ -770,6 +772,16 @@ void ui_draw_but_TRACKPREVIEW(ARegion *region, const struct uiWidgetColors *wcol, const rcti *rect); +/* interface_undo.c */ +struct uiUndoStack_Text *ui_textedit_undo_stack_create(void); +void ui_textedit_undo_stack_destroy(struct uiUndoStack_Text *undo_stack); +void ui_textedit_undo_push(struct uiUndoStack_Text *undo_stack, + const char *text, + int cursor_index); +const char *ui_textedit_undo(struct uiUndoStack_Text *undo_stack, + int direction, + int *r_cursor_index); + /* interface_handlers.c */ PointerRNA *ui_handle_afterfunc_add_operator(struct wmOperatorType *ot, int opcontext, diff --git a/source/blender/editors/interface/interface_undo.c b/source/blender/editors/interface/interface_undo.c new file mode 100644 index 00000000000..016bc4159db --- /dev/null +++ b/source/blender/editors/interface/interface_undo.c @@ -0,0 +1,139 @@ +/* + * 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. + * + * Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + * + * Undo stack to use for UI widgets that manage their own editing state. + */ + +#include <string.h> + +#include "BLI_listbase.h" + +#include "DNA_listBase.h" + +#include "MEM_guardedalloc.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Text Field Undo Stack + * \{ */ + +typedef struct uiUndoStack_Text_State { + struct uiUndoStack_Text_State *next, *prev; + int cursor_index; + char text[0]; +} uiUndoStack_Text_State; + +typedef struct uiUndoStack_Text { + ListBase states; + uiUndoStack_Text_State *current; +} uiUndoStack_Text; + +static const char *ui_textedit_undo_impl(uiUndoStack_Text *stack, int *r_cursor_index) +{ + /* Don't undo if no data has been pushed yet. */ + if (stack->current == NULL) { + return NULL; + } + + /* Travel backwards in the stack and copy information to the caller. */ + if (stack->current->prev != NULL) { + stack->current = stack->current->prev; + + *r_cursor_index = stack->current->cursor_index; + return stack->current->text; + } + return NULL; +} + +static const char *ui_textedit_redo_impl(uiUndoStack_Text *stack, int *r_cursor_index) +{ + /* Don't redo if no data has been pushed yet. */ + if (stack->current == NULL) { + return NULL; + } + + /* Only redo if new data has not been entered since the last undo. */ + if (stack->current->next) { + stack->current = stack->current->next; + + *r_cursor_index = stack->current->cursor_index; + return stack->current->text; + } + return NULL; +} + +const char *ui_textedit_undo(uiUndoStack_Text *stack, int direction, int *r_cursor_index) +{ + BLI_assert(ELEM(direction, -1, 1)); + if (direction < 0) { + return ui_textedit_undo_impl(stack, r_cursor_index); + } + else { + return ui_textedit_redo_impl(stack, r_cursor_index); + } +} + +/** + * Push the information in the arguments to a new state in the undo stack. + * + * \note Currently the total length of the undo stack is not limited. + */ +void ui_textedit_undo_push(uiUndoStack_Text *stack, const char *text, int cursor_index) +{ + /* Clear all redo actions from the current state. */ + if (stack->current != NULL) { + while (stack->current->next) { + uiUndoStack_Text_State *state = stack->current->next; + BLI_remlink(&stack->states, state); + MEM_freeN(state); + } + } + + /* Create the new state */ + const int text_size = strlen(text) + 1; + stack->current = MEM_mallocN(sizeof(uiUndoStack_Text_State) + text_size, __func__); + stack->current->cursor_index = cursor_index; + memcpy(stack->current->text, text, text_size); + BLI_addtail(&stack->states, stack->current); +} +/** + * Start the undo stack. + * + * \note The current state should be pushed immediately after calling this. + */ +uiUndoStack_Text *ui_textedit_undo_stack_create(void) +{ + uiUndoStack_Text *stack = MEM_mallocN(sizeof(uiUndoStack_Text), __func__); + stack->current = NULL; + BLI_listbase_clear(&stack->states); + + return stack; +} + +void ui_textedit_undo_stack_destroy(uiUndoStack_Text *stack) +{ + BLI_freelistN(&stack->states); + MEM_freeN(stack); +} + +/** \} */ |