From b6d0daa9cb261b3ce2ba3053ebee86f79caa5f06 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 16 Oct 2011 12:25:42 +0000 Subject: utf8 editing for UI text input, this means backspace, delete, arrow keys properly move the cursor with multi-byte chars. Note that this is only for the interface, text editor and python console still miss this feature. --- source/blender/blenlib/BLI_string.h | 4 + source/blender/blenlib/intern/string_utf8.c | 80 ++++++++++ .../blender/editors/interface/interface_handlers.c | 175 ++++++++++++--------- 3 files changed, 185 insertions(+), 74 deletions(-) (limited to 'source') diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h index c53ce9dced5..3ac8dba106a 100644 --- a/source/blender/blenlib/BLI_string.h +++ b/source/blender/blenlib/BLI_string.h @@ -147,6 +147,10 @@ void BLI_ascii_strtoupper(char *str, int len); char *BLI_strncpy_utf8(char *dst, const char *src, size_t maxncpy); int BLI_utf8_invalid_byte(const char *str, int length); int BLI_utf8_invalid_strip(char *str, int length); + /* copied from glib */ +char *BLI_str_find_prev_char_utf8(const char *str, const char *p); +char *BLI_str_find_next_char_utf8(const char *p, const char *end); +char *BLI_str_prev_char_utf8(const char *p); #ifdef __cplusplus } diff --git a/source/blender/blenlib/intern/string_utf8.c b/source/blender/blenlib/intern/string_utf8.c index 961a41690f7..dc6cb0ef228 100644 --- a/source/blender/blenlib/intern/string_utf8.c +++ b/source/blender/blenlib/intern/string_utf8.c @@ -183,3 +183,83 @@ char *BLI_strncpy_utf8(char *dst, const char *src, size_t maxncpy) return dst_r; } +/* copied from glib */ +/** + * g_utf8_find_prev_char: + * @str: pointer to the beginning of a UTF-8 encoded string + * @p: pointer to some position within @str + * + * Given a position @p with a UTF-8 encoded string @str, find the start + * of the previous UTF-8 character starting before @p. Returns %NULL if no + * UTF-8 characters are present in @str before @p. + * + * @p does not have to be at the beginning of a UTF-8 character. No check + * is made to see if the character found is actually valid other than + * it starts with an appropriate byte. + * + * Return value: a pointer to the found character or %NULL. + **/ +char * BLI_str_find_prev_char_utf8(const char *str, const char *p) +{ + for (--p; p >= str; --p) { + if ((*p & 0xc0) != 0x80) { + return (char *)p; + } + } + return NULL; +} + +/** + * g_utf8_find_next_char: + * @p: a pointer to a position within a UTF-8 encoded string + * @end: a pointer to the byte following the end of the string, + * or %NULL to indicate that the string is nul-terminated. + * + * Finds the start of the next UTF-8 character in the string after @p. + * + * @p does not have to be at the beginning of a UTF-8 character. No check + * is made to see if the character found is actually valid other than + * it starts with an appropriate byte. + * + * Return value: a pointer to the found character or %NULL + **/ +char *BLI_str_find_next_char_utf8(const char *p, const char *end) +{ + if (*p) { + if (end) { + for (++p; p < end && (*p & 0xc0) == 0x80; ++p) { + /* do nothing */ + } + } + else { + for (++p; (*p & 0xc0) == 0x80; ++p) { + /* do nothing */ + } + } + } + return (p == end) ? NULL : (char *)p; +} + +/** + * g_utf8_prev_char: + * @p: a pointer to a position within a UTF-8 encoded string + * + * Finds the previous UTF-8 character in the string before @p. + * + * @p does not have to be at the beginning of a UTF-8 character. No check + * is made to see if the character found is actually valid other than + * it starts with an appropriate byte. If @p might be the first + * character of the string, you must use g_utf8_find_prev_char() instead. + * + * Return value: a pointer to the found character. + **/ +char *BLI_str_prev_char_utf8(const char *p) +{ + while (1) { + p--; + if ((*p & 0xc0) != 0x80) { + return (char *)p; + } + } +} +/* end glib copy */ diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 6699e8f8701..f4400d2d7db 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -1252,6 +1252,86 @@ static short test_special_char(char ch) return 0; } +static int ui_textedit_step_next_utf8(const char *str, size_t maxlen, short *pos) +{ + const char *str_end= str + (maxlen + 1); + const char *str_pos= str + (*pos); + const char *str_next= BLI_str_find_next_char_utf8(str_pos, str_end); + if (str_next) { + (*pos) += (str_next - str_pos); + if((*pos) > maxlen) (*pos)= maxlen; + return TRUE; + } + + return FALSE; +} + +static int ui_textedit_step_prev_utf8(const char *str, size_t UNUSED(maxlen), short *pos) +{ + if((*pos) > 0) { + const char *str_pos= str + (*pos); + const char *str_prev= BLI_str_find_prev_char_utf8(str, str_pos); + if (str_prev) { + (*pos) -= (str_pos - str_prev); + return TRUE; + } + } + + return FALSE; +} + +static void ui_textedit_step_utf8(const char *str, size_t maxlen, + short *pos, const char direction, + const short do_jump, const short do_all) +{ + const short pos_prev= *pos; + + if(direction) { /* right*/ + if(do_jump) { + /* jump between special characters (/,\,_,-, etc.), + * look at function test_special_char() for complete + * list of special character, ctr -> */ + while((*pos) < maxlen) { + if (ui_textedit_step_next_utf8(str, maxlen, pos)) { + if(!do_all && test_special_char(str[(*pos)])) break; + } + else { + break; /* unlikely but just incase */ + } + } + } + else { + ui_textedit_step_next_utf8(str, maxlen, pos); + } + } + else { /* left */ + if(do_jump) { + /* left only: compensate for index/change in direction */ + ui_textedit_step_prev_utf8(str, maxlen, pos); + + /* jump between special characters (/,\,_,-, etc.), + * look at function test_special_char() for complete + * list of special character, ctr -> */ + while ((*pos) > 0) { + if (ui_textedit_step_prev_utf8(str, maxlen, pos)) { + if(!do_all && test_special_char(str[(*pos)])) break; + } + else { + break; + } + } + + /* left only: compensate for index/change in direction */ + if(((*pos) != 0) && ABS(pos_prev - (*pos)) > 1) { + ui_textedit_step_next_utf8(str, maxlen, pos); + } + } + else { + ui_textedit_step_prev_utf8(str, maxlen, pos); + } + } +} + static int ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data) { char *str= data->str; @@ -1294,13 +1374,17 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, sho /* mouse dragged outside the widget to the left */ if (x < startx && but->ofs > 0) { - int i= but->ofs; + short i= but->ofs; origstr[but->ofs] = 0; while (i > 0) { - i--; - if (BLF_width(fstyle->uifont_id, origstr+i) > (startx - x)*0.25f) break; // 0.25 == scale factor for less sensitivity + if (ui_textedit_step_prev_utf8(origstr, but->ofs, &i)) { + if (BLF_width(fstyle->uifont_id, origstr+i) > (startx - x)*0.25f) break; // 0.25 == scale factor for less sensitivity + } + else { + break; /* unlikely but possible */ + } } but->ofs = i; but->pos = but->ofs; @@ -1314,9 +1398,13 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, sho /* XXX does not take zoom level into account */ while (startx + aspect_sqrt * BLF_width(fstyle->uifont_id, origstr+but->ofs) > x) { if (but->pos <= 0) break; - but->pos--; - origstr[but->pos+but->ofs] = 0; - } + if (ui_textedit_step_prev_utf8(origstr, but->ofs, &but->pos)) { + origstr[but->pos+but->ofs] = 0; + } + else { + break; /* unlikely but possible */ + } + } but->pos += but->ofs; if(but->pos<0) but->pos= 0; } @@ -1391,48 +1479,7 @@ static void ui_textedit_move(uiBut *but, uiHandleButtonData *data, int direction data->selextend = 0; } else { - if(direction) { /* right*/ - if(jump) { - /* jump between special characters (/,\,_,-, etc.), - * look at function test_special_char() for complete - * list of special character, ctr -> */ - while(but->pos < len) { - but->pos++; - if(!jump_all && test_special_char(str[but->pos])) break; - } - } - else { - but->pos++; - if(but->pos > len) but->pos= len; - } - } - else { /* left */ - if(jump) { - - /* left only: compensate for index/change in direction */ - if(but->pos > 0) { - but->pos--; - } - - /* jump between special characters (/,\,_,-, etc.), - * look at function test_special_char() for complete - * list of special character, ctr -> */ - while(but->pos > 0){ - but->pos--; - if(!jump_all && test_special_char(str[but->pos])) break; - } - - /* left only: compensate for index/change in direction */ - if((but->pos != 0) && ABS(pos_prev - but->pos) > 1) { - but->pos++; - } - - } - else { - if(but->pos>0) but->pos--; - } - } - + ui_textedit_step_utf8(str, len, &but->pos, direction, jump, jump_all); if(select) { /* existing selection */ @@ -1498,21 +1545,10 @@ static int ui_textedit_delete(uiBut *but, uiHandleButtonData *data, int directio changed= ui_textedit_delete_selection(but, data); } else if(but->pos>=0 && but->pospos; int step; - - if (jump) { - x = but->pos; - step= 0; - while(x < len) { - x++; - step++; - if(test_special_char(str[x])) break; - } - } - else { - step= 1; - } - + ui_textedit_step_utf8(str, len, &pos, direction, jump, all); + step= pos - but->pos; for(x=but->pos; xpos>0) { + short pos= but->pos; int step; - if (jump) { - x = but->pos; - step= 0; - while(x > 0) { - x--; - step++; - if((step > 1) && test_special_char(str[x])) break; - } - } - else { - step= 1; - } + ui_textedit_step_utf8(str, len, &pos, direction, jump, all); + step= but->pos - pos; for(x=but->pos; x