[Bf-blender-cvs] [1e12468b84d] master: UI: undo/redo support for text fields

Hans Goudey noreply at git.blender.org
Tue May 12 03:32:55 CEST 2020


Commit: 1e12468b84d23e690eab0f5434d894e945694305
Author: Hans Goudey
Date:   Tue May 12 10:55:46 2020 +1000
Branches: master
https://developer.blender.org/rB1e12468b84d23e690eab0f5434d894e945694305

UI: undo/redo support for text fields

Support undo/redo when editing text buttons.

===================================================================

M	source/blender/editors/interface/CMakeLists.txt
M	source/blender/editors/interface/interface_handlers.c
M	source/blender/editors/interface/interface_intern.h
A	source/blender/editors/interface/interface_undo.c

===================================================================

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);
+}
+
+/** \} */



More information about the Bf-blender-cvs mailing list