Skip to content

Commit 32f1140

Browse files
committed
InputText: use TextSrc more consistently to facilitate accessing user buffer in text processing code. (#8242)
Followup to e900571 Removed SetClipboardText() trick used in abd07f6 (#7925)
1 parent e900571 commit 32f1140

File tree

2 files changed

+36
-44
lines changed

2 files changed

+36
-44
lines changed

imgui_internal.h

+1
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,7 @@ struct IMGUI_API ImGuiInputTextState
11241124
ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set.
11251125
ImGuiID ID; // widget id owning the text state
11261126
int TextLen; // UTF-8 length of the string in TextA (in bytes)
1127+
const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). Field only set and valid _inside_ the call InputText() call.
11271128
ImVector<char> TextA; // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1).
11281129
ImVector<char> TextToRevertTo; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered)
11291130
ImVector<char> CallbackTextBackup; // temporary storage for callback to support automatic reconcile of undo-stack

imgui_widgets.cpp

+35-44
Original file line numberDiff line numberDiff line change
@@ -3938,12 +3938,12 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c
39383938
namespace ImStb
39393939
{
39403940
static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; }
3941-
static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextA[idx]; }
3942-
static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
3941+
static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; }
3942+
static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
39433943
static char STB_TEXTEDIT_NEWLINE = '\n';
39443944
static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
39453945
{
3946-
const char* text = obj->TextA.Data;
3946+
const char* text = obj->TextSrc;
39473947
const char* text_remaining = NULL;
39483948
const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true);
39493949
r->x0 = 0.0f;
@@ -3962,15 +3962,15 @@ static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int id
39623962
if (idx >= obj->TextLen)
39633963
return obj->TextLen + 1;
39643964
unsigned int c;
3965-
return idx + ImTextCharFromUtf8(&c, obj->TextA.Data + idx, obj->TextA.Data + obj->TextLen);
3965+
return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen);
39663966
}
39673967

39683968
static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
39693969
{
39703970
if (idx <= 0)
39713971
return -1;
3972-
const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, obj->TextA.Data + idx);
3973-
return (int)(p - obj->TextA.Data);
3972+
const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx);
3973+
return (int)(p - obj->TextSrc);
39743974
}
39753975

39763976
static bool ImCharIsSeparatorW(unsigned int c)
@@ -3993,10 +3993,10 @@ static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
39933993
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
39943994
return 0;
39953995

3996-
const char* curr_p = obj->TextA.Data + idx;
3997-
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
3998-
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextA.Data + obj->TextLen);
3999-
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextA.Data + obj->TextLen);
3996+
const char* curr_p = obj->TextSrc + idx;
3997+
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
3998+
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen);
3999+
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen);
40004000

40014001
bool prev_white = ImCharIsBlankW(prev_c);
40024002
bool prev_separ = ImCharIsSeparatorW(prev_c);
@@ -4009,10 +4009,10 @@ static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
40094009
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
40104010
return 0;
40114011

4012-
const char* curr_p = obj->TextA.Data + idx;
4013-
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
4014-
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextA.Data + obj->TextLen);
4015-
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextA.Data + obj->TextLen);
4012+
const char* curr_p = obj->TextSrc + idx;
4013+
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
4014+
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen);
4015+
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen);
40164016

40174017
bool prev_white = ImCharIsBlankW(prev_c);
40184018
bool prev_separ = ImCharIsSeparatorW(prev_c);
@@ -4050,6 +4050,7 @@ static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)
40504050
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
40514051
{
40524052
// Offset remaining text (+ copy zero terminator)
4053+
IM_ASSERT(obj->TextSrc == obj->TextA.Data);
40534054
char* dst = obj->TextA.Data + pos;
40544055
char* src = obj->TextA.Data + pos + n;
40554056
memmove(dst, src, obj->TextLen - n - pos + 1);
@@ -4067,11 +4068,13 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch
40674068
return false;
40684069

40694070
// Grow internal buffer if needed
4071+
IM_ASSERT(obj->TextSrc == obj->TextA.Data);
40704072
if (new_text_len + text_len + 1 > obj->TextA.Size)
40714073
{
40724074
if (!is_resizable)
40734075
return false;
40744076
obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);
4077+
obj->TextSrc = obj->TextA.Data;
40754078
}
40764079

40774080
char* text = obj->TextA.Data;
@@ -4218,6 +4221,7 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
42184221
IM_ASSERT(Buf == edit_state->TextA.Data);
42194222
int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
42204223
edit_state->TextA.resize(new_buf_size + 1);
4224+
edit_state->TextSrc = edit_state->TextA.Data;
42214225
Buf = edit_state->TextA.Data;
42224226
BufSize = edit_state->BufCapacity = new_buf_size;
42234227
}
@@ -4507,7 +4511,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
45074511
const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.
45084512
const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
45094513
const bool init_state = (init_make_active || user_scroll_active);
4510-
bool readonly_swapped_text_data = false;
45114514
if (init_reload_from_user_buf)
45124515
{
45134516
int new_len = (int)strlen(buf);
@@ -4612,22 +4615,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
46124615
// Expose scroll in a manner that is agnostic to us using a child window
46134616
if (is_multiline && state != NULL)
46144617
state->Scroll.y = draw_window->Scroll.y;
4615-
}
46164618

4617-
if (g.ActiveId == id && is_readonly)
4618-
{
4619-
// FIXME: Refresh buffer because cursor/selection code uses that data (see repro #8242)
4620-
// The "simple" way would be to copy buf into state->TextA, like in the block above.
4621-
// But because we like to live dangerously, we do a little swap....
4622-
// Removing the swap and only doing a TextA.clear() is a way to identify who's using TextA.Data.
4623-
state->TextLen = (int)strlen(buf);
4624-
state->TextA.clear();
4625-
state->TextA.Data = buf; // Ouch
4626-
state->TextA.Size = state->TextLen + 1;
4627-
readonly_swapped_text_data = true; // Need to always ensure that every code path below lead to this being handled
4628-
//state->TextA.resize(buf_size + 1);
4629-
//memcpy(state->TextA.Data, buf, state->TextLen + 1);
4619+
// Read-only mode always ever read from source buffer. Refresh TextLen when active.
4620+
if (is_readonly && state != NULL)
4621+
state->TextLen = (int)strlen(buf);
4622+
//if (is_readonly && state != NULL)
4623+
// state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation.
46304624
}
4625+
if (state != NULL)
4626+
state->TextSrc = is_readonly ? buf : state->TextA.Data;
46314627

46324628
// We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
46334629
if (g.ActiveId == id && state == NULL)
@@ -4892,13 +4888,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
48924888
// Cut, Copy
48934889
if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
48944890
{
4891+
// SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy.
48954892
const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0;
48964893
const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen;
4897-
4898-
char backup = state->TextA.Data[ie];
4899-
state->TextA.Data[ie] = 0; // A bit of a hack since SetClipboardText only takes null terminated strings
4900-
SetClipboardText(state->TextA.Data + ib);
4901-
state->TextA.Data[ie] = backup;
4894+
g.TempBuffer.reserve(ie - ib + 1);
4895+
memcpy(g.TempBuffer.Data, state->TextSrc, ie - ib);
4896+
g.TempBuffer.Data[ie] = 0;
4897+
SetClipboardText(g.TempBuffer.Data);
49024898
}
49034899
if (is_cut)
49044900
{
@@ -5028,7 +5024,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
50285024

50295025
// FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
50305026
char* callback_buf = is_readonly ? buf : state->TextA.Data;
5031-
5027+
IM_ASSERT(callback_buf == state->TextSrc);
50325028
state->CallbackTextBackup.resize(state->TextLen + 1);
50335029
memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1);
50345030

@@ -5066,9 +5062,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
50665062
}
50675063

50685064
// Will copy result string if modified
5069-
if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
5065+
if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
50705066
{
5071-
apply_new_text = state->TextA.Data;
5067+
apply_new_text = state->TextSrc;
50725068
apply_new_text_length = state->TextLen;
50735069
value_changed = true;
50745070
}
@@ -5324,13 +5320,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
53245320
if (is_password && !is_displaying_hint)
53255321
PopFont();
53265322

5327-
if (readonly_swapped_text_data)
5328-
{
5329-
IM_ASSERT(state->TextA.Data == buf);
5330-
state->TextA.Size = 0;
5331-
state->TextA.Data = NULL;
5332-
}
5333-
53345323
if (is_multiline)
53355324
{
53365325
// For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)...
@@ -5349,6 +5338,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
53495338
g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
53505339
}
53515340
}
5341+
if (state)
5342+
state->TextSrc = NULL;
53525343

53535344
// Log as text
53545345
if (g.LogEnabled && (!is_password || is_displaying_hint))

0 commit comments

Comments
 (0)