UI: undo/redo support for text fields
Support undo/redo when editing text buttons.
This commit is contained in:
parent
542ff416e2
commit
1e12468b84
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
Loading…
Reference in New Issue