From 0d5f845bb5732392b5892ff6d24ceffcf38cf22f Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 10 Nov 2015 08:35:12 +1100 Subject: [PATCH] UI: support editing >400 length text buttons RNA could define strings as dynamically sized, but the interface ignored this and clamped them to UI_MAX_DRAW_STR. Fixes T46734, also fixes possible pasting non-utf8 text into utf8 buttons. --- source/blender/editors/interface/interface.c | 60 +++++++- .../editors/interface/interface_handlers.c | 140 +++++++++++------- .../editors/interface/interface_intern.h | 1 + 3 files changed, 142 insertions(+), 59 deletions(-) diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 1f0d10588ca..69e5f407fa2 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -2196,6 +2196,63 @@ void ui_but_string_get(uiBut *but, char *str, const size_t maxlen) ui_but_string_get_ex(but, str, maxlen, -1); } +/** + * A version of #ui_but_string_get_ex for dynamic buffer sizes + * (where #ui_but_string_get_max_length returns 0). + * + * \param r_str_size: size of the returned string (including terminator). + */ +char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size) +{ + char *str = NULL; + *r_str_size = 1; + + if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + PropertyType type; + + type = RNA_property_type(but->rnaprop); + + if (type == PROP_STRING) { + /* RNA string */ + str = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, NULL, 0, r_str_size); + (*r_str_size) += 1; + } + else if (type == PROP_ENUM) { + /* RNA enum */ + int value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); + const char *value_id; + if (!RNA_property_enum_name(but->block->evil_C, &but->rnapoin, but->rnaprop, value, &value_id)) { + value_id = ""; + } + + *r_str_size = strlen(value_id) + 1; + str = BLI_strdupn(value_id, *r_str_size); + } + else if (type == PROP_POINTER) { + /* RNA pointer */ + PointerRNA ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); + str = RNA_struct_name_get_alloc(&ptr, NULL, 0, r_str_size); + (*r_str_size) += 1; + } + else { + BLI_assert(0); + } + } + else { + BLI_assert(0); + } + + if (UNLIKELY(str == NULL)) { + /* should never happen, paranoid check */ + *r_str_size = 1; + str = BLI_strdup(""); + BLI_assert(0); + + } + + return str; +} + #ifdef WITH_PYTHON static bool ui_set_but_string_eval_num_unit(bContext *C, uiBut *but, const char *str, double *value) @@ -3366,8 +3423,7 @@ static uiBut *ui_def_but_rna( else if (proptype == PROP_STRING) { min = 0; max = RNA_property_string_maxlength(prop); - if (max == 0) /* interface code should ideally support unlimited length */ - max = UI_MAX_DRAW_STR; + /* note, 'max' may be zero (code for dynamically resized array) */ } } diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index f15e9c05027..d15a46113e3 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -119,6 +119,7 @@ static void ui_but_smart_controller_add(bContext *C, uiBut *from, uiBut *to); static void ui_but_link_add(bContext *C, uiBut *from, uiBut *to); static int ui_do_but_EXIT(bContext *C, uiBut *but, struct uiHandleButtonData *data, const wmEvent *event); static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b); +static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str); #ifdef USE_KEYNAV_LIMIT static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event); @@ -282,7 +283,9 @@ typedef struct uiHandleButtonData { wmTimer *flashtimer; /* edited value */ - char *str, *origstr; + /* use 'ui_textedit_string_set' to assign new strings */ + char *str; + char *origstr; double value, origvalue, startvalue; float vec[3], origvec[3]; #if 0 /* UNUSED */ @@ -299,8 +302,12 @@ typedef struct uiHandleButtonData { wmTimer *autoopentimer; /* text selection/editing */ - int maxlen, selextend; + /* size of 'str' (including terminator) */ + int maxlen; + int selextend; float selstartx; + /* allow to realloc str/editstr and use 'maxlen' to track alloc size (maxlen + 1) */ + bool is_str_dynamic; /* number editing / dragging */ /* coords are Window/uiBlock relative (depends on the button) */ @@ -2156,7 +2163,8 @@ static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleB ID *id = (ID *)wmd->poin; button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - BLI_strncpy(data->str, id->name + 2, data->maxlen); + + ui_textedit_string_set(but, data, id->name + 2); if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) { but->changed = true; @@ -2314,10 +2322,7 @@ static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, else { button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - if (ui_but_is_utf8(but)) - BLI_strncpy_utf8(active_data->str, buf_paste, active_data->maxlen); - else - BLI_strncpy(active_data->str, buf_paste, active_data->maxlen); + ui_textedit_string_set(but, active_data, buf_paste); if (but->type == UI_BTYPE_SEARCH_MENU) { /* else uiSearchboxData.active member is not updated [#26856] */ @@ -2468,6 +2473,31 @@ void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], uiBut *bu /* ************* in-button text selection/editing ************* */ +static void ui_textedit_string_ensure_max_length(uiBut *but, uiHandleButtonData *data, int maxlen) +{ + BLI_assert(data->is_str_dynamic); + BLI_assert(data->str == but->editstr); + + if (maxlen > data->maxlen) { + data->str = but->editstr = MEM_reallocN(data->str, sizeof(char) * maxlen); + data->maxlen = maxlen; + } +} + +static void ui_textedit_string_set(uiBut *but, uiHandleButtonData *data, const char *str) +{ + if (data->is_str_dynamic) { + ui_textedit_string_ensure_max_length(but, data, strlen(str) + 1); + } + + if (ui_but_is_utf8(but)) { + BLI_strncpy_utf8(data->str, str, data->maxlen); + } + else { + BLI_strncpy(data->str, str, data->maxlen); + } +} + static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data) { @@ -2603,22 +2633,25 @@ static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, } /** - * This is used for both utf8 and ascii, its meant to be used for single keys, - * notice the buffer is either copied or not, so its not suitable for pasting in - * - campbell */ -static bool ui_textedit_type_buf( + * This is used for both utf8 and ascii + * + * For unicode buttons, \a buf is treated as unicde. + */ +static bool ui_textedit_insert_buf( uiBut *but, uiHandleButtonData *data, - const char *utf8_buf, int utf8_buf_len) + const char *buf, int buf_len) { - char *str; - int len; + int len = strlen(data->str); + int len_new = len - (but->selend - but->selsta) + 1; bool changed = false; - str = data->str; - len = strlen(str); + if (data->is_str_dynamic) { + ui_textedit_string_ensure_max_length(but, data, len_new + buf_len); + } - if (len - (but->selend - but->selsta) + 1 <= data->maxlen) { - int step = utf8_buf_len; + if (len_new <= data->maxlen) { + char *str = data->str; + size_t step = buf_len; /* type over the current selection */ if ((but->selend - but->selsta) > 0) { @@ -2626,9 +2659,19 @@ static bool ui_textedit_type_buf( len = strlen(str); } - if (len + step < data->maxlen) { + if ((len + step >= data->maxlen) && (data->maxlen - (len + 1) > 0)) { + if (ui_but_is_utf8(but)) { + /* shorten 'step' to a utf8 algined size that fits */ + BLI_strnlen_utf8_ex(buf, data->maxlen - (len + 1), &step); + } + else { + step = data->maxlen - (len + 1); + } + } + + if (step && (len + step < data->maxlen)) { memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos); - memcpy(&str[but->pos], utf8_buf, step * sizeof(char)); + memcpy(&str[but->pos], buf, step * sizeof(char)); but->pos += step; changed = true; } @@ -2637,7 +2680,7 @@ static bool ui_textedit_type_buf( return changed; } -static bool ui_textedit_type_ascii(uiBut *but, uiHandleButtonData *data, char ascii) +static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii) { char buf[2] = {ascii, '\0'}; @@ -2649,7 +2692,7 @@ static bool ui_textedit_type_ascii(uiBut *but, uiHandleButtonData *data, char as } /* in some cases we want to allow invalid utf8 chars */ - return ui_textedit_type_buf(but, data, buf, 1); + return ui_textedit_insert_buf(but, data, buf, 1); } static void ui_textedit_move( @@ -2803,48 +2846,24 @@ enum { static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode) { - char *str, *pbuf; - int x; + char *pbuf; bool changed = false; - int str_len, buf_len; - - str = data->str; - str_len = strlen(str); + int buf_len; /* paste */ if (mode == UI_TEXTEDIT_PASTE) { - /* TODO, ensure UTF8 ui_but_is_utf8() - campbell */ /* extract the first line from the clipboard */ pbuf = WM_clipboard_text_get_firstline(false, &buf_len); if (pbuf) { - char buf[UI_MAX_DRAW_STR] = {0}; - unsigned int y; - - buf_len = BLI_strncpy_rlen(buf, pbuf, sizeof(buf)); - - /* paste over the current selection */ - if ((but->selend - but->selsta) > 0) { - ui_textedit_delete_selection(but, data); - str_len = strlen(str); - } - - for (y = 0; y < buf_len; y++) { - /* add contents of buffer */ - if (str_len + 1 < data->maxlen) { - for (x = data->maxlen; x > but->pos; x--) - str[x] = str[x - 1]; - str[but->pos] = buf[y]; - but->pos++; - str_len++; - str[str_len] = '\0'; - } + if (ui_but_is_utf8(but)) { + buf_len -= BLI_utf8_invalid_strip(pbuf, buf_len); } + ui_textedit_insert_buf(but, data, pbuf, buf_len); + changed = true; - } - if (pbuf) { MEM_freeN(pbuf); } } @@ -2854,7 +2873,7 @@ static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const in int sellen = but->selend - but->selsta; char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste"); - BLI_strncpy(buf, str + but->selsta, sellen + 1); + BLI_strncpy(buf, data->str + but->selsta, sellen + 1); WM_clipboard_text_set(buf, 0); MEM_freeN(buf); @@ -2958,14 +2977,21 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) /* retrieve string */ data->maxlen = ui_but_string_get_max_length(but); - data->str = MEM_callocN(sizeof(char) * data->maxlen + 1, "textedit str"); - ui_but_string_get(but, data->str, data->maxlen); + if (data->maxlen != 0) { + data->str = MEM_callocN(sizeof(char) * data->maxlen, "textedit str"); + ui_but_string_get(but, data->str, data->maxlen); + } + else { + data->is_str_dynamic = true; + data->str = ui_but_string_get_dynamic(but, &data->maxlen); + } if (ui_but_is_float(but) && !ui_but_is_unit(but)) { BLI_str_rstrip_float_zero(data->str, '\0'); } if (is_num_but) { + BLI_assert(data->is_str_dynamic == false); ui_but_convert_to_unit_alt_name(but, data->str, data->maxlen); } @@ -3347,10 +3373,10 @@ static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandle } // strcpy(utf8_buf, "12345"); - changed = ui_textedit_type_buf(but, data, event->utf8_buf, utf8_buf_len); + changed = ui_textedit_insert_buf(but, data, event->utf8_buf, utf8_buf_len); } else { - changed = ui_textedit_type_ascii(but, data, ascii); + changed = ui_textedit_insert_ascii(but, data, ascii); } retval = WM_UI_HANDLER_BREAK; diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 9271bbfc31c..0a68f84fd23 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -470,6 +470,7 @@ bool ui_but_is_colorpicker_display_space(struct uiBut *but); extern void ui_but_string_get_ex(uiBut *but, char *str, const size_t maxlen, const int float_precision) ATTR_NONNULL(); extern void ui_but_string_get(uiBut *but, char *str, const size_t maxlen) ATTR_NONNULL(); +extern char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size); extern void ui_but_convert_to_unit_alt_name(uiBut *but, char *str, size_t maxlen) ATTR_NONNULL(); extern bool ui_but_string_set(struct bContext *C, uiBut *but, const char *str) ATTR_NONNULL(); extern bool ui_but_string_set_eval_num(struct bContext *C, uiBut *but, const char *str, double *value) ATTR_NONNULL();