This fixes the behavior in notepad, with Japanese ibus-mozc IME, when typing "NIHONGO-SPACE-NO", or with Korean ibus-hangul when typing "GA-NA-DA". The latter still has an issue with the last character not appearing in the a composition but I believe it is unrelated to comctl32 and will be fixed by some future imm32 changes.
-- v2: user32: Keep and display composition separate from the selection. comctl32/edit: Keep and display composition separate from the selection.
From: Rémi Bernon rbernon@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=53860 --- dlls/comctl32/edit.c | 181 +++++++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 74 deletions(-)
diff --git a/dlls/comctl32/edit.c b/dlls/comctl32/edit.c index 883dd5d9145..cfb6b20069d 100644 --- a/dlls/comctl32/edit.c +++ b/dlls/comctl32/edit.c @@ -140,8 +140,8 @@ typedef struct /* * IME Data */ - UINT composition_len; /* length of composition, 0 == no composition */ - int composition_start; /* the character position for the composition */ + LPWSTR composition_text; /* the contents of the composition string */ + SCRIPT_STRING_ANALYSIS composition_ssa; UINT ime_status; /* IME status flag */ /* * Uniscribe Data @@ -318,6 +318,30 @@ static inline void EDIT_InvalidateUniscribeData(EDITSTATE *es) } }
+static SCRIPT_STRING_ANALYSIS EDIT_UpdateUniscribeData_composition(EDITSTATE *es, HDC dc, INT length) +{ + if (!es->composition_ssa) + { + HFONT old_font = NULL; + HDC udc = dc; + + if (!udc) + udc = GetDC(es->hwndSelf); + if (es->font) + old_font = SelectObject(udc, es->font); + + ScriptStringAnalyse(udc, es->composition_text, length, (1.5*length+16), -1, SSA_LINK|SSA_FALLBACK|SSA_GLYPHS, + -1, NULL, NULL, NULL, NULL, NULL, &es->composition_ssa); + + if (es->font) + SelectObject(udc, old_font); + if (udc != dc) + ReleaseDC(es->hwndSelf, udc); + } + + return es->composition_ssa; +} + static SCRIPT_STRING_ANALYSIS EDIT_UpdateUniscribeData_linedef(EDITSTATE *es, HDC dc, LINEDEF *line_def) { if (!line_def) @@ -2043,6 +2067,50 @@ static void EDIT_MoveWordForward(EDITSTATE *es, BOOL extend) }
+static INT EDIT_PaintComposition(EDITSTATE *es, HDC dc, INT x, INT y) +{ + LOGFONTW underline_font; + HFONT underline, old_font; + SCRIPT_ANALYSIS *ssa; + INT ret, count; + HFONT current; + SIZE size; + + if (!es->composition_text) + return 0; + count = wcslen(es->composition_text); + + ssa = EDIT_UpdateUniscribeData_composition(es, dc, count); + if (ssa) + { + ScriptStringOut(ssa, x, y, 0, &es->format_rect, 0, count, FALSE); + ScriptStringCPtoX(ssa, count - 1, TRUE, &ret); + return ret; + } + + current = GetCurrentObject(dc,OBJ_FONT); + GetObjectW(current,sizeof(LOGFONTW),&underline_font); + underline_font.lfUnderline = TRUE; + underline = CreateFontIndirectW(&underline_font); + old_font = SelectObject(dc,underline); + + if (es->style & ES_MULTILINE) { + ret = (INT)LOWORD(TabbedTextOutW(dc, x, y, es->composition_text, count, + es->tabs_count, es->tabs, es->format_rect.left - es->x_offset)); + } else { + TextOutW(dc, x, y, es->composition_text, count); + GetTextExtentPoint32W(dc, es->composition_text, count, &size); + ret = size.cx; + } + + if (old_font) + SelectObject(dc,old_font); + if (underline) + DeleteObject(underline); + + return ret; +} + /********************************************************************* * * EDIT_PaintText @@ -2052,9 +2120,6 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col { COLORREF BkColor; COLORREF TextColor; - LOGFONTW underline_font; - HFONT hUnderline = 0; - HFONT old_font = 0; INT ret; INT li; INT BkMode; @@ -2066,20 +2131,9 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col BkColor = GetBkColor(dc); TextColor = GetTextColor(dc); if (rev) { - if (es->composition_len == 0) - { - SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); - SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); - SetBkMode( dc, OPAQUE); - } - else - { - HFONT current = GetCurrentObject(dc,OBJ_FONT); - GetObjectW(current,sizeof(LOGFONTW),&underline_font); - underline_font.lfUnderline = TRUE; - hUnderline = CreateFontIndirectW(&underline_font); - old_font = SelectObject(dc,hUnderline); - } + SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); + SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + SetBkMode( dc, OPAQUE); } li = EDIT_EM_LineIndex(es, line); if (es->style & ES_MULTILINE) { @@ -2091,19 +2145,9 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col ret = size.cx; } if (rev) { - if (es->composition_len == 0) - { - SetBkColor(dc, BkColor); - SetTextColor(dc, TextColor); - SetBkMode( dc, BkMode); - } - else - { - if (old_font) - SelectObject(dc,old_font); - if (hUnderline) - DeleteObject(hUnderline); - } + SetBkColor(dc, BkColor); + SetTextColor(dc, TextColor); + SetBkMode( dc, BkMode); } return ret; } @@ -2176,14 +2220,24 @@ static void EDIT_PaintLine(EDITSTATE *es, HDC dc, INT line, BOOL rev) }
if (ssa) + { ScriptStringOut(ssa, x, y, 0, &es->format_rect, s - li, e - li, FALSE); + ScriptStringCPtoX(ssa, s - li - 1, TRUE, &x); + EDIT_PaintComposition(es, dc, x, y); + } else if (rev && (s != e) && ((es->flags & EF_FOCUSED) || (es->style & ES_NOHIDESEL))) { x += EDIT_PaintText(es, dc, x, y, line, 0, s - li, FALSE); + x += EDIT_PaintComposition(es, dc, x, y); x += EDIT_PaintText(es, dc, x, y, line, s - li, e - s, TRUE); x += EDIT_PaintText(es, dc, x, y, line, e - li, li + ll - e, FALSE); - } else - x += EDIT_PaintText(es, dc, x, y, line, 0, ll, FALSE); + } + else + { + x += EDIT_PaintText(es, dc, x, y, line, 0, s - li, FALSE); + x += EDIT_PaintComposition(es, dc, x, y); + x += EDIT_PaintText(es, dc, x, y, line, s - li, li + ll - s, FALSE); + }
if (es->cue_banner_text && es->text_length == 0 && (!(es->flags & EF_FOCUSED) || es->cue_banner_draw_focused)) { @@ -4258,14 +4312,21 @@ static void EDIT_GetCompositionStr(HIMC hIMC, LPARAM CompFlag, EDITSTATE *es) LPSTR lpCompStrAttr = NULL; DWORD dwBufLenAttr;
- buflen = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, NULL, 0); + Free(es->composition_text); + es->composition_text = NULL; + if (es->composition_ssa) + { + ScriptStringFree(&es->composition_ssa); + es->composition_ssa = NULL; + }
+ buflen = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, NULL, 0); if (buflen < 0) { return; }
- lpCompStr = Alloc(buflen); + lpCompStr = Alloc(buflen + sizeof(WCHAR)); if (!lpCompStr) { ERR("Unable to allocate IME CompositionString\n"); @@ -4298,26 +4359,11 @@ static void EDIT_GetCompositionStr(HIMC hIMC, LPARAM CompFlag, EDITSTATE *es) } }
- /* check for change in composition start */ - if (es->selection_end < es->composition_start) - es->composition_start = es->selection_end; - - /* replace existing selection string */ - es->selection_start = es->composition_start; - - if (es->composition_len > 0) - es->selection_end = es->composition_start + es->composition_len; - else - es->selection_end = es->selection_start; - - EDIT_EM_ReplaceSel(es, FALSE, lpCompStr, buflen / sizeof(WCHAR), TRUE, TRUE); - es->composition_len = abs(es->composition_start - es->selection_end); - - es->selection_start = es->composition_start; - es->selection_end = es->selection_start + es->composition_len; - Free(lpCompStrAttr); - Free(lpCompStr); + es->composition_text = lpCompStr; + + es->flags |= EF_UPDATE; + EDIT_UpdateText(es, NULL, TRUE); }
static void EDIT_GetResultStr(HIMC hIMC, EDITSTATE *es) @@ -4340,16 +4386,7 @@ static void EDIT_GetResultStr(HIMC hIMC, EDITSTATE *es)
ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, lpResultStr, buflen);
- /* check for change in composition start */ - if (es->selection_end < es->composition_start) - es->composition_start = es->selection_end; - - es->selection_start = es->composition_start; - es->selection_end = es->composition_start + es->composition_len; EDIT_EM_ReplaceSel(es, TRUE, lpResultStr, buflen / sizeof(WCHAR), TRUE, TRUE); - es->composition_start = es->selection_end; - es->composition_len = 0; - Free(lpResultStr); }
@@ -4358,11 +4395,8 @@ static void EDIT_ImeComposition(HWND hwnd, LPARAM CompFlag, EDITSTATE *es) HIMC hIMC; int cursor;
- if (es->composition_len == 0 && es->selection_start != es->selection_end) - { + if (!es->composition_text && es->selection_start != es->selection_end) EDIT_EM_ReplaceSel(es, TRUE, NULL, 0, TRUE, TRUE); - es->composition_start = es->selection_end; - }
hIMC = ImmGetContext(hwnd); if (!hIMC) @@ -5051,8 +5085,6 @@ static LRESULT CALLBACK EDIT_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPAR break;
case WM_IME_STARTCOMPOSITION: - es->composition_start = es->selection_end; - es->composition_len = 0; break;
case WM_IME_COMPOSITION: @@ -5066,11 +5098,12 @@ static LRESULT CALLBACK EDIT_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPAR break;
case WM_IME_ENDCOMPOSITION: - if (es->composition_len > 0) + Free(es->composition_text); + es->composition_text = NULL; + if (es->composition_ssa) { - EDIT_EM_ReplaceSel(es, TRUE, NULL, 0, TRUE, TRUE); - es->selection_end = es->selection_start; - es->composition_len= 0; + ScriptStringFree(&es->composition_ssa); + es->composition_ssa = NULL; } break;
From: Rémi Bernon rbernon@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=53860 --- dlls/user32/edit.c | 186 +++++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 76 deletions(-)
diff --git a/dlls/user32/edit.c b/dlls/user32/edit.c index 47c61f59c24..94148b58393 100644 --- a/dlls/user32/edit.c +++ b/dlls/user32/edit.c @@ -128,8 +128,8 @@ typedef struct /* * IME Data */ - UINT composition_len; /* length of composition, 0 == no composition */ - int composition_start; /* the character position for the composition */ + LPWSTR composition_text; /* the contents of the composition string */ + SCRIPT_STRING_ANALYSIS composition_ssa; UINT ime_status; /* IME status flag */ /* * Uniscribe Data @@ -367,6 +367,30 @@ static inline void EDIT_InvalidateUniscribeData(EDITSTATE *es) } }
+static SCRIPT_STRING_ANALYSIS EDIT_UpdateUniscribeData_composition(EDITSTATE *es, HDC dc, INT length) +{ + if (!es->composition_ssa) + { + HFONT old_font = NULL; + HDC udc = dc; + + if (!udc) + udc = NtUserGetDC(es->hwndSelf); + if (es->font) + old_font = SelectObject(udc, es->font); + + ScriptStringAnalyse(udc, es->composition_text, length, (1.5*length+16), -1, SSA_LINK|SSA_FALLBACK|SSA_GLYPHS, + -1, NULL, NULL, NULL, NULL, NULL, &es->composition_ssa); + + if (es->font) + SelectObject(udc, old_font); + if (udc != dc) + NtUserReleaseDC(es->hwndSelf, udc); + } + + return es->composition_ssa; +} + static SCRIPT_STRING_ANALYSIS EDIT_UpdateUniscribeData_linedef(EDITSTATE *es, HDC dc, LINEDEF *line_def) { if (!line_def) @@ -2125,6 +2149,50 @@ static void EDIT_MoveWordForward(EDITSTATE *es, BOOL extend) }
+static INT EDIT_PaintComposition(EDITSTATE *es, HDC dc, INT x, INT y) +{ + LOGFONTW underline_font; + HFONT underline, old_font; + SCRIPT_ANALYSIS *ssa; + INT ret, count; + HFONT current; + SIZE size; + + if (!es->composition_text) + return 0; + count = wcslen(es->composition_text); + + ssa = EDIT_UpdateUniscribeData_composition(es, dc, count); + if (ssa) + { + ScriptStringOut(ssa, x, y, 0, &es->format_rect, 0, count, FALSE); + ScriptStringCPtoX(ssa, count - 1, TRUE, &ret); + return ret; + } + + current = GetCurrentObject(dc,OBJ_FONT); + GetObjectW(current,sizeof(LOGFONTW),&underline_font); + underline_font.lfUnderline = TRUE; + underline = CreateFontIndirectW(&underline_font); + old_font = SelectObject(dc,underline); + + if (es->style & ES_MULTILINE) { + ret = (INT)LOWORD(TabbedTextOutW(dc, x, y, es->composition_text, count, + es->tabs_count, es->tabs, es->format_rect.left - es->x_offset)); + } else { + TextOutW(dc, x, y, es->composition_text, count); + GetTextExtentPoint32W(dc, es->composition_text, count, &size); + ret = size.cx; + } + + if (old_font) + SelectObject(dc,old_font); + if (underline) + DeleteObject(underline); + + return ret; +} + /********************************************************************* * * EDIT_PaintText @@ -2134,9 +2202,6 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col { COLORREF BkColor; COLORREF TextColor; - LOGFONTW underline_font; - HFONT hUnderline = 0; - HFONT old_font = 0; INT ret; INT li; INT BkMode; @@ -2148,20 +2213,9 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col BkColor = GetBkColor(dc); TextColor = GetTextColor(dc); if (rev) { - if (es->composition_len == 0) - { - SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); - SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); - SetBkMode( dc, OPAQUE); - } - else - { - HFONT current = GetCurrentObject(dc,OBJ_FONT); - GetObjectW(current,sizeof(LOGFONTW),&underline_font); - underline_font.lfUnderline = TRUE; - hUnderline = CreateFontIndirectW(&underline_font); - old_font = SelectObject(dc,hUnderline); - } + SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); + SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + SetBkMode( dc, OPAQUE); } li = EDIT_EM_LineIndex(es, line); if (es->style & ES_MULTILINE) { @@ -2173,19 +2227,9 @@ static INT EDIT_PaintText(EDITSTATE *es, HDC dc, INT x, INT y, INT line, INT col ret = size.cx; } if (rev) { - if (es->composition_len == 0) - { - SetBkColor(dc, BkColor); - SetTextColor(dc, TextColor); - SetBkMode( dc, BkMode); - } - else - { - if (old_font) - SelectObject(dc,old_font); - if (hUnderline) - DeleteObject(hUnderline); - } + SetBkColor(dc, BkColor); + SetTextColor(dc, TextColor); + SetBkMode( dc, BkMode); } return ret; } @@ -2258,14 +2302,24 @@ static void EDIT_PaintLine(EDITSTATE *es, HDC dc, INT line, BOOL rev) }
if (ssa) + { ScriptStringOut(ssa, x, y, 0, &es->format_rect, s - li, e - li, FALSE); + ScriptStringCPtoX(ssa, s - li - 1, TRUE, &x); + EDIT_PaintComposition(es, dc, x, y); + } else if (rev && (s != e) && ((es->flags & EF_FOCUSED) || (es->style & ES_NOHIDESEL))) { x += EDIT_PaintText(es, dc, x, y, line, 0, s - li, FALSE); + x += EDIT_PaintComposition(es, dc, x, y); x += EDIT_PaintText(es, dc, x, y, line, s - li, e - s, TRUE); x += EDIT_PaintText(es, dc, x, y, line, e - li, li + ll - e, FALSE); - } else - x += EDIT_PaintText(es, dc, x, y, line, 0, ll, FALSE); + } + else + { + x += EDIT_PaintText(es, dc, x, y, line, 0, s - li, FALSE); + x += EDIT_PaintComposition(es, dc, x, y); + x += EDIT_PaintText(es, dc, x, y, line, s - li, li + ll - s, FALSE); + } }
@@ -4343,6 +4397,14 @@ static void EDIT_GetCompositionStr(HIMC hIMC, LPARAM CompFlag, EDITSTATE *es) LPSTR lpCompStrAttr = NULL; DWORD dwBufLenAttr;
+ HeapFree(GetProcessHeap(), 0, es->composition_text); + es->composition_text = NULL; + if (es->composition_ssa) + { + ScriptStringFree(&es->composition_ssa); + es->composition_ssa = NULL; + } + buflen = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, NULL, 0);
if (buflen < 0) @@ -4350,7 +4412,7 @@ static void EDIT_GetCompositionStr(HIMC hIMC, LPARAM CompFlag, EDITSTATE *es) return; }
- lpCompStr = HeapAlloc(GetProcessHeap(),0,buflen); + lpCompStr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buflen + sizeof(WCHAR)); if (!lpCompStr) { ERR("Unable to allocate IME CompositionString\n"); @@ -4383,26 +4445,11 @@ static void EDIT_GetCompositionStr(HIMC hIMC, LPARAM CompFlag, EDITSTATE *es) } }
- /* check for change in composition start */ - if (es->selection_end < es->composition_start) - es->composition_start = es->selection_end; - - /* replace existing selection string */ - es->selection_start = es->composition_start; - - if (es->composition_len > 0) - es->selection_end = es->composition_start + es->composition_len; - else - es->selection_end = es->selection_start; - - EDIT_EM_ReplaceSel(es, FALSE, lpCompStr, buflen / sizeof(WCHAR), TRUE, TRUE); - es->composition_len = abs(es->composition_start - es->selection_end); - - es->selection_start = es->composition_start; - es->selection_end = es->selection_start + es->composition_len; - HeapFree(GetProcessHeap(),0,lpCompStrAttr); - HeapFree(GetProcessHeap(),0,lpCompStr); + es->composition_text = lpCompStr; + + es->flags |= EF_UPDATE; + EDIT_UpdateText(es, NULL, TRUE); }
static void EDIT_GetResultStr(HIMC hIMC, EDITSTATE *es) @@ -4425,16 +4472,7 @@ static void EDIT_GetResultStr(HIMC hIMC, EDITSTATE *es)
ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, lpResultStr, buflen);
- /* check for change in composition start */ - if (es->selection_end < es->composition_start) - es->composition_start = es->selection_end; - - es->selection_start = es->composition_start; - es->selection_end = es->composition_start + es->composition_len; EDIT_EM_ReplaceSel(es, TRUE, lpResultStr, buflen / sizeof(WCHAR), TRUE, TRUE); - es->composition_start = es->selection_end; - es->composition_len = 0; - HeapFree(GetProcessHeap(),0,lpResultStr); }
@@ -4443,11 +4481,8 @@ static void EDIT_ImeComposition(HWND hwnd, LPARAM CompFlag, EDITSTATE *es) HIMC hIMC; int cursor;
- if (es->composition_len == 0 && es->selection_start != es->selection_end) - { + if (!es->composition_text && es->selection_start != es->selection_end) EDIT_EM_ReplaceSel(es, TRUE, NULL, 0, TRUE, TRUE); - es->composition_start = es->selection_end; - }
hIMC = ImmGetContext(hwnd); if (!hIMC) @@ -5201,8 +5236,6 @@ LRESULT EditWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, B break;
case WM_IME_STARTCOMPOSITION: - es->composition_start = es->selection_end; - es->composition_len = 0; break;
case WM_IME_COMPOSITION: @@ -5216,13 +5249,14 @@ LRESULT EditWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, B break;
case WM_IME_ENDCOMPOSITION: - if (es->composition_len > 0) - { - EDIT_EM_ReplaceSel(es, TRUE, NULL, 0, TRUE, TRUE); - es->selection_end = es->selection_start; - es->composition_len= 0; - } - break; + HeapFree(GetProcessHeap(), 0, es->composition_text); + es->composition_text = NULL; + if (es->composition_ssa) + { + ScriptStringFree(&es->composition_ssa); + es->composition_ssa = NULL; + } + break;
case WM_IME_COMPOSITIONFULL: break;
On Thu May 4 13:39:13 2023 +0000, Byeongsik Jeon wrote:
From additional tests it looks that like the composition string is
simply not updating the edit control selection or text until it is committed. This is probably because you're testing Japanese or Chinese IMEs. In this case, the composition string is handled in the ime_ui_window_proc. Because the MS native ime_ui_window_proc is currently implemented more complexly than Wine's, it looks as if the edit control is drawing. However, upon further investigation, this is not the case.
Hmm, I don't know, I've just been testing by updating the input context and sending WM_IME messages directly to the edit control, then checking with `EM_GETSEL` and `WM_GETTEXT` what effect they had.
I didn't see any change to the selection range or text until it a result string is committed when doing this. I didn't investigate very long anyway, and I think that keeping and displaying the composition string separately is an easier fix.
On Thu May 4 13:48:22 2023 +0000, Rémi Bernon wrote:
Hmm, I don't know, I've just been testing by updating the input context and sending WM_IME messages directly to the edit control, then checking with `EM_GETSEL` and `WM_GETTEXT` what effect they had. I didn't see any change to the selection range or text until it a result string is committed when doing this. I didn't investigate very long anyway, and I think that keeping and displaying the composition string separately is an easier fix.
The test is correct. I've explained why. Because the WM_IME_COMPOSITION(GCS_COMPSTR) message is passed to the IME UI window Proc, it doesn't appear in the edit control until the commit message comes.
However, the current Wine ime_ui_window_proc needs a lot of improvement.
I think the v2 implementation looks good. I just have a slight problem. The test sequence is NIHONGO-ENTER-ENTER-NIHONGO in ibus-mozc.
On Thu May 4 14:20:18 2023 +0000, Byeongsik Jeon wrote:
The test is correct. I've explained why. Because the WM_IME_COMPOSITION(GCS_COMPSTR) message is passed to the IME UI window Proc, it doesn't appear in the edit control until the commit message comes. However, the current Wine ime_ui_window_proc needs a lot of improvement. I think the v2 implementation looks good. I just have a slight problem. The test sequence is NIHONGO-ENTER-ENTER-NIHONGO in ibus-mozc.
Right, it's duplicating the composition string on every line now, I'll fix this. I see that the cursor is also incorrectly moved too.