JBR-1693 difficult to input Japanese text with "Fast" Key Repeat

(cherry picked from commit 12de3e287e)
This commit is contained in:
Anton Tarasov
2019-08-02 20:17:31 +03:00
committed by Vitaly Provodin
parent fc89040251
commit 0c94851700

View File

@@ -26,6 +26,7 @@
package sun.lwawt.macosx;
import java.awt.im.spi.*;
import java.lang.reflect.Method;
import java.util.*;
import java.awt.*;
import java.awt.peer.*;
@@ -36,17 +37,21 @@ import java.lang.Character.Subset;
import java.lang.reflect.InvocationTargetException;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.text.JTextComponent;
import sun.awt.AWTAccessor;
import sun.awt.AppContext;
import sun.awt.SunToolkit;
import sun.awt.im.InputMethodAdapter;
import sun.lwawt.*;
import static sun.awt.AWTAccessor.ComponentAccessor;
public class CInputMethod extends InputMethodAdapter {
private InputMethodContext fIMContext;
private Component fAwtFocussedComponent;
private volatile InputMethodContext fIMContext;
private volatile Component fAwtFocussedComponent;
private LWComponentPeer<?, ?> fAwtFocussedComponentPeer;
private boolean isActive;
@@ -728,37 +733,38 @@ public class CInputMethod extends InputMethodAdapter {
final int[] rect = new int[4];
try {
if (fIMContext != null)
LWCToolkit.invokeAndWait(new Runnable() {
public void run() { synchronized(rect) {
int insertOffset = fIMContext.getInsertPositionOffset();
int composedTextOffset = absoluteTextOffset - insertOffset;
if (composedTextOffset < 0) composedTextOffset = 0;
Rectangle r = fIMContext.getTextLocation(TextHitInfo.beforeOffset(composedTextOffset));
rect[0] = r.x;
rect[1] = r.y;
rect[2] = r.width;
rect[3] = r.height;
if (fIMContext != null) {
FxInvoker.invoke(() -> {
synchronized (rect) {
int insertOffset = fIMContext.getInsertPositionOffset();
int composedTextOffset = absoluteTextOffset - insertOffset;
if (composedTextOffset < 0) composedTextOffset = 0;
Rectangle r = fIMContext.getTextLocation(TextHitInfo.beforeOffset(composedTextOffset));
rect[0] = r.x;
rect[1] = r.y;
rect[2] = r.width;
rect[3] = r.height;
// This next if-block is a hack to work around a bug in JTextComponent. getTextLocation ignores
// the TextHitInfo passed to it and always returns the location of the insertion point, which is
// at the start of the composed text. We'll do some calculation so the candidate window for Kotoeri
// follows the requested offset into the composed text.
if (composedTextOffset > 0 && (fAwtFocussedComponent instanceof JTextComponent)) {
Rectangle r2 = fIMContext.getTextLocation(TextHitInfo.beforeOffset(0));
// This next if-block is a hack to work around a bug in JTextComponent. getTextLocation ignores
// the TextHitInfo passed to it and always returns the location of the insertion point, which is
// at the start of the composed text. We'll do some calculation so the candidate window for Kotoeri
// follows the requested offset into the composed text.
if (composedTextOffset > 0 && (fAwtFocussedComponent instanceof JTextComponent)) {
Rectangle r2 = fIMContext.getTextLocation(TextHitInfo.beforeOffset(0));
if (r.equals(r2) && fCurrentTextAsString != null) {
// FIXME: (SAK) If the candidate text wraps over two lines, this calculation pushes the candidate
// window off the right edge of the component.
String inProgressSubstring = fCurrentTextAsString.substring(0, composedTextOffset);
Graphics g = fAwtFocussedComponent.getGraphics();
int xOffset = g.getFontMetrics().stringWidth(inProgressSubstring);
rect[0] += xOffset;
g.dispose();
if (r.equals(r2) && fCurrentTextAsString != null) {
// FIXME: (SAK) If the candidate text wraps over two lines, this calculation pushes the candidate
// window off the right edge of the component.
String inProgressSubstring = fCurrentTextAsString.substring(0, composedTextOffset);
Graphics g = fAwtFocussedComponent.getGraphics();
int xOffset = g.getFontMetrics().stringWidth(inProgressSubstring);
rect[0] += xOffset;
g.dispose();
}
}
}
}}
}, fAwtFocussedComponent, true); // [tav] avoid deadlock with javafx
}, (sun.awt.im.InputContext)fIMContext, fAwtFocussedComponent);
}
} catch (InvocationTargetException ite) { ite.printStackTrace(); }
synchronized(rect) { return rect; }
@@ -773,13 +779,14 @@ public class CInputMethod extends InputMethodAdapter {
final int[] insertPositionOffset = new int[1];
try {
if (fIMContext != null)
LWCToolkit.invokeAndWait(new Runnable() {
public void run() { synchronized(offsetInfo) {
offsetInfo[0] = fIMContext.getLocationOffset(screenX, screenY);
insertPositionOffset[0] = fIMContext.getInsertPositionOffset();
}}
}, fAwtFocussedComponent, true); // [tav] avoid deadlock with javafx
if (fIMContext != null) {
FxInvoker.invoke(() -> {
synchronized (offsetInfo) {
offsetInfo[0] = fIMContext.getLocationOffset(screenX, screenY);
insertPositionOffset[0] = fIMContext.getInsertPositionOffset();
}
}, (sun.awt.im.InputContext)fIMContext, fAwtFocussedComponent);
}
} catch (InvocationTargetException ite) { ite.printStackTrace(); }
// This bit of gymnastics ensures that the returned location is within the composed text.
@@ -829,4 +836,88 @@ public class CInputMethod extends InputMethodAdapter {
// Initialize toolbox routines
static native void nativeInit();
private static class FxInvoker {
final static Method GET_CLIENT_COMPONENT_METHOD;
static Class<?> JFX_PANEL_CLASS;
static {
Method m = null;
try {
m = sun.awt.im.InputContext.class.getDeclaredMethod("getClientComponent");
if (m != null) m.setAccessible(true);
} catch (NoSuchMethodException ignore) {
}
GET_CLIENT_COMPONENT_METHOD = m;
}
static Component getClientComponent(sun.awt.im.InputContext ctx) {
if (GET_CLIENT_COMPONENT_METHOD != null) {
try {
return (Component)GET_CLIENT_COMPONENT_METHOD.invoke(ctx);
} catch (IllegalAccessException | InvocationTargetException ignore) {
}
}
return null;
}
static boolean instanceofJFXPanel(Component clientComponent) {
if (clientComponent != null) {
if (JFX_PANEL_CLASS == null) {
try {
// the class is not available in the current class loader context, use the client class loader
JFX_PANEL_CLASS = Class.forName("javafx.embed.swing.JFXPanel", false, clientComponent.getClass().getClassLoader());
} catch (ClassNotFoundException ignore) {
}
}
if (JFX_PANEL_CLASS != null) {
return JFX_PANEL_CLASS.isInstance(clientComponent);
}
}
return false;
}
static void await(CountDownLatch latch) {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Executed on AppKit
static void invoke(Runnable runnable, sun.awt.im.InputContext inputContext, Component targetToAppContext) throws InvocationTargetException {
CountDownLatch edtLatch = new CountDownLatch(1);
CountDownLatch tkLatch = new CountDownLatch(1);
AtomicBoolean runOnAppKit = new AtomicBoolean(false);
// Emulate EventQueue.invokeAndWait(runnable) for the runnable that delegates execution
// back to AppKit (JavaFX Event thread), without running secondary loop on AppKit.
InvocationEvent event = new InvocationEvent(targetToAppContext, () -> {
try {
runOnAppKit.set(instanceofJFXPanel(getClientComponent(inputContext)));
if (!runOnAppKit.get()) {
runnable.run();
}
} finally {
edtLatch.countDown();
await(tkLatch);
}
});
assert targetToAppContext != null;
AppContext appContext = SunToolkit.targetToAppContext(targetToAppContext);
SunToolkit.postEvent(appContext, event);
SunToolkit.flushPendingEvents(appContext);
await(edtLatch);
try {
if (runOnAppKit.get()) {
runnable.run();
}
} finally {
tkLatch.countDown();
}
}
}
}