JBR-4687: Japanese IME input window hides what is being typed.

Uses CFS_EXCLUDE instead of CFS_CANDIDATEPOS in the ::ImmSetCandidateWindow() native API, which is more powerful and allows to take into account the issue's case.

(cherry picked from commit 0afe6c37bb)
(cherry picked from commit fa3d03b373)
This commit is contained in:
Nikita Provotorov
2023-12-13 22:04:04 +01:00
committed by jbrbot
parent 12fa92f956
commit 85ef4dd792
5 changed files with 92 additions and 46 deletions

View File

@@ -593,27 +593,35 @@ final class WInputMethod extends InputMethodAdapter
Runnable r = new Runnable() {
@Override
public void run() {
int x = 0;
int y = 0;
Component client = getClientComponent();
Rectangle caretRect = null;
if (client != null) {
if (!client.isShowing()) {
return;
}
if (haveActiveClient()) {
Rectangle rc = inputContext.getTextLocation(TextHitInfo.leading(0));
x = rc.x;
y = rc.y + rc.height;
} else {
Point pt = client.getLocationOnScreen();
Dimension size = client.getSize();
x = pt.x;
y = pt.y + size.height;
caretRect = inputContext.getTextLocation(TextHitInfo.leading(0));
}
if (caretRect == null) {
Point pt = client.getLocationOnScreen();
Dimension size = client.getSize();
caretRect = new Rectangle(pt, size);
}
}
openCandidateWindow(awtFocussedComponentPeer, x, y);
if (caretRect == null) {
openCandidateWindow(awtFocussedComponentPeer, 0, 0, 0, 0);
} else {
openCandidateWindow(
awtFocussedComponentPeer,
caretRect.x,
caretRect.y,
caretRect.x + caretRect.width - ( (caretRect.width > 0) ? 1 : 0),
caretRect.y + caretRect.height - ( (caretRect.height > 0) ? 1 : 0)
);
}
}
};
WToolkit.postEvent(WToolkit.targetToAppContext(source),
@@ -657,6 +665,6 @@ final class WInputMethod extends InputMethodAdapter
private native String getNativeIMMDescription();
static native Locale getNativeLocale();
static native boolean setNativeLocale(String localeName);
private native void openCandidateWindow(WComponentPeer peer, int x, int y);
private native void openCandidateWindow(WComponentPeer peer, int caretLeftX, int caretTopY, int caretRightX, int caretBottomY);
private native boolean isCompositionStringAvailable(int context);
}

View File

@@ -4019,49 +4019,63 @@ void AwtComponent::SetCompositionWindow(RECT& r)
ImmReleaseContext(hwnd, hIMC);
}
void AwtComponent::OpenCandidateWindow(int x, int y)
{
void AwtComponent::OpenCandidateWindow(
const int caretLeftX,
const int caretTopY,
const int caretRightX,
const int caretBottomY
) {
UINT bits = 1;
POINT p = {0, 0}; // upper left corner of the client area
HWND hWnd = ImmGetHWnd();
if (!::IsWindowVisible(hWnd)) {
return;
}
HWND hTop = GetTopLevelParentForWindow(hWnd);
::ClientToScreen(hTop, &p);
int sx = ScaleUpAbsX(x) - p.x;
int sy = ScaleUpAbsY(y) - p.y;
const int sCaretLeftX = ScaleUpAbsX(caretLeftX) - p.x;
const int sCaretTopY = ScaleUpAbsY(caretTopY) - p.y;
const int sCaretRightX = ScaleUpAbsX(caretRightX) - p.x;
const int sCaretBottomY = ScaleUpAbsY(caretBottomY) - p.y;
if (!m_bitsCandType) {
SetCandidateWindow(m_bitsCandType, sx, sy);
SetCandidateWindow(m_bitsCandType, sCaretLeftX, sCaretTopY, sCaretRightX, sCaretBottomY);
return;
}
for (int iCandType=0; iCandType<32; iCandType++, bits<<=1) {
if ( m_bitsCandType & bits )
SetCandidateWindow(iCandType, sx, sy);
SetCandidateWindow(iCandType, sCaretLeftX, sCaretTopY, sCaretRightX, sCaretBottomY);
}
}
void AwtComponent::SetCandidateWindow(int iCandType, int x, int y)
{
void AwtComponent::SetCandidateWindow(
const int iCandType,
const int caretLeftX,
const int caretTopY,
const int caretRightX,
const int caretBottomY
) {
HWND hwnd = ImmGetHWnd();
HIMC hIMC = ImmGetContext(hwnd);
if (hIMC) {
CANDIDATEFORM cf;
cf.dwStyle = CFS_CANDIDATEPOS;
ImmGetCandidateWindow(hIMC, 0, &cf);
if (x != cf.ptCurrentPos.x || y != cf.ptCurrentPos.y) {
if (caretLeftX != cf.ptCurrentPos.x || caretBottomY != cf.ptCurrentPos.y) {
cf.dwIndex = iCandType;
cf.dwStyle = CFS_CANDIDATEPOS;
cf.ptCurrentPos = {x, y};
cf.rcArea = {0, 0, 0, 0};
cf.dwStyle = CFS_EXCLUDE;
cf.ptCurrentPos = {caretLeftX, caretBottomY};
cf.rcArea = {caretLeftX, caretTopY, caretRightX, caretBottomY};
ImmSetCandidateWindow(hIMC, &cf);
}
COMPOSITIONFORM cfr;
cfr.dwStyle = CFS_POINT;
ImmGetCompositionWindow(hIMC, &cfr);
if (x != cfr.ptCurrentPos.x || y != cfr.ptCurrentPos.y) {
if (caretLeftX != cfr.ptCurrentPos.x || caretBottomY != cfr.ptCurrentPos.y) {
cfr.dwStyle = CFS_POINT;
cfr.ptCurrentPos = {x, y};
cfr.ptCurrentPos = {caretLeftX, caretBottomY};
cfr.rcArea = {0, 0, 0, 0};
ImmSetCompositionWindow(hIMC, &cfr);
}

View File

@@ -545,8 +545,8 @@ public:
virtual MsgRouting WmPaste();
virtual void SetCompositionWindow(RECT &r);
virtual void OpenCandidateWindow(int x, int y);
virtual void SetCandidateWindow(int iCandType, int x, int y);
virtual void OpenCandidateWindow(int caretLeftX, int caretTopY, int caretRightX, int caretBottomY);
virtual void SetCandidateWindow(int iCandType, int caretLeftX, int caretTopY, int caretRightX, int caretBottomY);
virtual MsgRouting WmImeSetContext(BOOL fSet, LPARAM *lplParam);
virtual MsgRouting WmImeNotify(WPARAM subMsg, LPARAM bitsCandType);
virtual MsgRouting WmImeStartComposition();

View File

@@ -407,7 +407,7 @@ JNIEXPORT void JNICALL Java_sun_awt_windows_WInputMethod_setStatusWindowVisible
* Signature: (Lsun/awt/windows/WComponentPeer;II)V
*/
JNIEXPORT void JNICALL Java_sun_awt_windows_WInputMethod_openCandidateWindow
(JNIEnv *env, jobject self, jobject peer, jint x, jint y)
(JNIEnv *env, jobject self, jobject peer, jint caretLeftX, jint caretTopY, jint caretRightX, jint caretBottomY)
{
TRY;
@@ -415,19 +415,26 @@ JNIEXPORT void JNICALL Java_sun_awt_windows_WInputMethod_openCandidateWindow
JNI_CHECK_PEER_RETURN(peer);
jobject peerGlobalRef = env->NewGlobalRef(peer);
if (peerGlobalRef == nullptr) {
return;
}
// WARNING! MAKELONG macro treats the given values as unsigned.
// This may lead to some bugs in multiscreen configurations, as
// coordinates can be negative numbers. So, while handling
// WM_AWT_OPENCANDIDATEWINDOW message in AwtToolkit, we should
// carefully extract right x and y values using GET_X_LPARAM and
// GET_Y_LPARAM, not LOWORD and HIWORD
// See CR 4805862, AwtToolkit::WndProc
// it'd be better to replace the static_cast with a placement new, but it's broken
// in debug builds because the "new" expression is redefined as a macro
::RECT* const caretRect = static_cast<::RECT*>( safe_Malloc(sizeof(::RECT)) );
// safe_Malloc throws an std::bad_alloc if fails, so we don't need to add a nullptr check here
*caretRect = ::RECT{ caretLeftX, caretTopY, caretRightX, caretBottomY };
// use special message to open candidate window in main thread.
AwtToolkit::GetInstance().InvokeInputMethodFunction(WM_AWT_OPENCANDIDATEWINDOW,
(WPARAM)peerGlobalRef, MAKELONG(x, y));
// global ref is deleted in message handler
// use a special message to open a candidate window in main thread.
static_assert( sizeof(peerGlobalRef) <= sizeof(WPARAM), "peerGlobalRef may not fit into WPARAM type" );
static_assert( sizeof(caretRect) <= sizeof(LPARAM), "caretRect may not fit into LPARAM type" );
AwtToolkit::GetInstance().InvokeInputMethodFunction(
WM_AWT_OPENCANDIDATEWINDOW,
reinterpret_cast<WPARAM>(peerGlobalRef),
reinterpret_cast<LPARAM>(caretRect)
);
// peerGlobalRef and caretRect are deleted in the message handler (AwtToolkit::WndProc)
CATCH_BAD_ALLOC;
}

View File

@@ -1219,16 +1219,33 @@ LRESULT CALLBACK AwtToolkit::WndProc(HWND hWnd, UINT message,
return (LRESULT)activateKeyboardLayout((HKL)lParam);
}
case WM_AWT_OPENCANDIDATEWINDOW: {
jobject peerObject = (jobject)wParam;
AwtComponent* p = (AwtComponent*)JNI_GET_PDATA(peerObject);
jobject peerObject = reinterpret_cast<jobject>(wParam);
AwtComponent* p = reinterpret_cast<AwtComponent*>( JNI_GET_PDATA(peerObject) );
DASSERT( !IsBadReadPtr(p, sizeof(AwtObject)));
// fix for 4805862: use GET_X_LPARAM and GET_Y_LPARAM macros
// instead of LOWORD and HIWORD
p->OpenCandidateWindow(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
env->DeleteGlobalRef(peerObject);
::RECT* caretRect = reinterpret_cast<::RECT*>(lParam);
DASSERT( !IsBadReadPtr(caretRect, sizeof(*caretRect)) );
if ( (p != nullptr) && (caretRect != nullptr) ) {
p->OpenCandidateWindow(caretRect->left, caretRect->top, caretRect->right, caretRect->bottom);
}
// Cleaning up
if (caretRect != nullptr) {
free(caretRect);
caretRect = nullptr;
}
if (peerObject != nullptr) {
env->DeleteGlobalRef(peerObject);
peerObject = nullptr;
}
p = nullptr;
// Returning to AwtToolkit::InvokeInputMethodFunction
AwtToolkit& tk = AwtToolkit::GetInstance();
tk.m_inputMethodData = 0;
::SetEvent(tk.m_inputMethodWaitEvent);
return 0;
}