JDK-8089738 : [FXCanvas] SWT IME is not implemented
  • Type: Bug
  • Component: javafx
  • Sub-Component: other
  • Affected Version: 8u20,8u25
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2014-11-02
  • Updated: 2024-06-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
Using FXCanvas causes Windows 8 IME to show input field outside of the application window, when typing inside TextField.


Test code:

import javafx.embed.swt.FXCanvas;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class FXCanvasBug {
    public static void main(String[] args) {
        Display display = new Display ();
        Shell shell = new Shell(display);
        FXCanvas canvas = new FXCanvas(shell, SWT.EMBEDDED);
        shell.setLayout(new FillLayout());
        canvas.setScene(new Scene(new TextField("Type here"), 200, 100));
        shell.open();
        while (!shell.isDisposed ()) {
            if (!display.readAndDispatch ()) display.sleep ();
        }
        display.dispose ();
    }
}



Bugged input: http://imageshack.com/a/img674/9017/hKsFZf.png 
Normal input: http://imageshack.com/a/img661/4255/hwdCiO.png  (text should appear inside textField)

The version with normal input is made inside Eclipse RCP 4.4, with SWT_AWT + JFXPanel to replace FXCanvas. But it only works inside Eclipse RCP, not as standalone application. It's not really usable due to serious flickr that happens to SWT_AWT bridge.

Comments
the IME candidate window is also NOT positioned by FXCanvas, unlike the implementation of native windowing system. Here are the fixes (update candidate position after receiving non-committing IME events): private void sendInputMethodEventToFX() { String text = this.getTextForEvent(this.ime.getText()); EmbeddedSceneInterface scenePeer = this.getScenePeer(); if (scenePeer != null) { String committed = text.substring(0, this.ime.getCommitCount()); if (this.ime.getText().length() == this.ime.getCommitCount()) { // Send empty text when committed, because the actual chars will then be immediately sent via keys. scenePeer.inputMethodEvent( javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, FXCollections.emptyObservableList(), "", this.ime.getCompositionOffset()); } else { ObservableList<InputMethodTextRun> composed = this.inputMethodEventComposed(text, this.ime.getCommitCount()); scenePeer.inputMethodEvent( javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, composed, committed, this.ime.getCaretOffset()); this.updateImeCandicatePos(); } } } @SuppressWarnings("deprecation") private void updateImeCandicatePos() { EmbeddedSceneInterface emScene = (EmbeddedSceneInterface) this.getScene().impl_getPeer(); InputMethodRequests imeRequests = emScene.getInputMethodRequests(); Point2D absolutePos = imeRequests.getTextLocation(0); COMPOSITIONFORM lpCompForm = new COMPOSITIONFORM(); lpCompForm.dwStyle = OS.CFS_CANDIDATEPOS; Point relativePos = this.toControl((int) absolutePos.getX(), (int) absolutePos.getY()); lpCompForm.x = relativePos.x; lpCompForm.y = relativePos.y; long hIMC = OS.ImmGetContext(this.handle); try { OS.ImmSetCompositionWindow(hIMC, lpCompForm); } finally { OS.ImmReleaseContext(this.handle, hIMC); } } The position returned from InputMethodRequests.getTextLocation is somehow absolute screen point, for some reason I haven't figured out yet. The X is right but Y seems a dozen pixels from the bottom of text (still acceptable though).
18-04-2015

I solved the WM_IME_ENDCOMPOSITION problem by directly modifying SWT source, obviously not an ideal way, but that's all I can do now before SWT team fix it (reported at https://bugs.eclipse.org/bugs/show_bug.cgi?id=450182 ) Inside org.eclipse.swt.widgets.IME: LRESULT WM_IME_ENDCOMPOSITION (long /*int*/ wParam, long /*int*/ lParam) { if (isInlineEnabled()) { if (text.length() > 0) { Event event = new Event(); // cause receiver to replace [start:end] with text "", effectively clear uncommitted chars event.detail = SWT.COMPOSITION_CHANGED; event.start = startOffset; event.end = startOffset + text.length(); event.text = ""; caretOffset = commitCount = 0; text = ""; ranges = null; styles = null; sendEvent(SWT.ImeComposition, event); startOffset = -1; } return LRESULT.ONE; } return null; } PS: that part of the bug is directly caused by SWT and their custom widgets (StyledText) also share the same issue.
05-11-2014

/* * Following is the code to delegate IME events. It works at most cases but * fails to delegate the IME end event (WM_IME_ENDCOMPOSITION), which happens * when the user aborts typed text by switching input method in the middle. * There doesn't seem to be a direct fix, as SWT disallows catching of * WM_IME_ENDCOMPOSITION from Canvas/FXCanvas and the jar is also signed, * which makes classpath overriding impossible. */ import java.lang.reflect.Field; import java.text.CharacterIterator; import java.util.ArrayList; import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.embed.swt.FXCanvas; import javafx.scene.input.InputMethodHighlight; import javafx.scene.input.InputMethodTextRun; import org.eclipse.swt.SWT; import com.sun.javafx.collections.ObservableListWrapper; import com.sun.javafx.embed.EmbeddedSceneInterface; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.IME; public class FXCanvas2 extends FXCanvas { private static Field scenePeerField; static { try { scenePeerField = FXCanvas.class.getDeclaredField("scenePeer"); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } scenePeerField.setAccessible(true); } private final IME ime; private EmbeddedSceneInterface scenePeer; public FXCanvas2(Composite parent, int style) { super(parent, style); this.ime = new IME(this, SWT.NONE); this.ime.addListener(SWT.ImeComposition, event -> { switch (event.detail) { case SWT.COMPOSITION_CHANGED: FXCanvas2.this.sendInputMethodEventToFX(); break; } }); } public EmbeddedSceneInterface getScenePeer() { if (this.scenePeer == null) { try { this.scenePeer = (EmbeddedSceneInterface) scenePeerField.get(this); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } return this.scenePeer; } private void sendInputMethodEventToFX() { String text = this.getTextForEvent(this.ime.getText()); EmbeddedSceneInterface scenePeer = this.getScenePeer(); if (scenePeer != null) { String committed = text.substring(0, this.ime.getCommitCount()); if (this.ime.getText().length() == this.ime.getCommitCount()) { // Send empty text when committed, because the actual chars will then be immediately sent via keys. scenePeer.inputMethodEvent( javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, FXCollections.emptyObservableList(), "", this.ime.getCompositionOffset()); } else { ObservableList<InputMethodTextRun> composed = this.inputMethodEventComposed(text, this.ime.getCommitCount()); scenePeer.inputMethodEvent( javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, composed, committed, this.ime.getCaretOffset()); } } } private String getTextForEvent(String text) { if (text == null) return ""; StringBuilder builder = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (c == CharacterIterator.DONE) break; builder.append(c); } return builder.toString(); } private ObservableList<InputMethodTextRun> inputMethodEventComposed(String text, int commitCount) { List<InputMethodTextRun> composed = new ArrayList<>(); if (commitCount < text.length()) { composed.add(new InputMethodTextRun( text.substring(commitCount), InputMethodHighlight.UNSELECTED_RAW)); } return new ObservableListWrapper<>(composed); } }
05-11-2014

I found the cause of problem by comparing JFXPanel and FXCanvas: the latter completely lacks any support for IME (!), while JFXPanel handles InputMethodEvent from AWT and delegate it to JavaFX's InputMethodEvent. The code looks simple enough so maybe all that's needed is to port a few lines for SWT. I shall update this soon once I make a fix.
04-11-2014