Richtextfx: Correct InputMethodTextChanged Event Handling

Created on 18 Nov 2020  路  4Comments  路  Source: FXMisc/RichTextFX

The workaround in the https://github.com/FXMisc/RichTextFX/issues/146 works but it does not handle the InputMethodTextChanged event perfectly.

I searched around javafx's code and found how it is done for TextInputControl.

https://github.com/openjdk/jfx/blob/dd22cd2d97d4bfc305e44f2e77b4c104c62ff31f/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TextInputControlSkin.java#L693

Most helpful comment

I have extracted the code needed to do this. As for InputMethodAttributes, this is too complicated and not necessary, I just removed it.

import org.fxmisc.richtext.CodeArea;

import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.InputMethodTextRun;

public class Setup {
    private int imlength;
    private int imstart;

    public void setOnInputMethodTextChanged(CodeArea area){
        area.setOnInputMethodTextChanged(event -> {
            handleInputMethodEvent(event,area);
        });
    }

    public void handleInputMethodEvent(InputMethodEvent event,CodeArea area) {
            if (area.isEditable()  && !area.isDisabled()) {

                // remove previous input method text (if any) or selected text
                if (imlength != 0) {
                    //removeHighlight(imattrs);
                    //imattrs.clear();
                    area.selectRange(imstart, imstart + imlength);
                }

                // Insert committed text
                if (event.getCommitted().length() != 0) {
                    String committed = event.getCommitted();
                    area.replaceText(area.getSelection(), committed);
                }

                // Replace composed text
                imstart = area.getSelection().getStart();
                StringBuilder composed = new StringBuilder();
                for (InputMethodTextRun run : event.getComposed()) {
                    composed.append(run.getText());
                }
                area.replaceText(area.getSelection(), composed.toString());
                imlength = composed.length();
                if (imlength != 0) {
                    int pos = imstart;
                    for (InputMethodTextRun run : event.getComposed()) {
                        int endPos = pos + run.getText().length();
                        //createInputMethodAttributes(run.getHighlight(), pos, endPos);
                        pos = endPos;
                    }
                    //addHighlight(imattrs, imstart);

                    // Set caret position in composed text
                    int caretPos = event.getCaretPosition();
                    if (caretPos >= 0 && caretPos < imlength) {
                        area.selectRange(imstart + caretPos, imstart + caretPos);
                    }
                }
            }
        }
}

All 4 comments

I have extracted the code needed to do this. As for InputMethodAttributes, this is too complicated and not necessary, I just removed it.

import org.fxmisc.richtext.CodeArea;

import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.InputMethodTextRun;

public class Setup {
    private int imlength;
    private int imstart;

    public void setOnInputMethodTextChanged(CodeArea area){
        area.setOnInputMethodTextChanged(event -> {
            handleInputMethodEvent(event,area);
        });
    }

    public void handleInputMethodEvent(InputMethodEvent event,CodeArea area) {
            if (area.isEditable()  && !area.isDisabled()) {

                // remove previous input method text (if any) or selected text
                if (imlength != 0) {
                    //removeHighlight(imattrs);
                    //imattrs.clear();
                    area.selectRange(imstart, imstart + imlength);
                }

                // Insert committed text
                if (event.getCommitted().length() != 0) {
                    String committed = event.getCommitted();
                    area.replaceText(area.getSelection(), committed);
                }

                // Replace composed text
                imstart = area.getSelection().getStart();
                StringBuilder composed = new StringBuilder();
                for (InputMethodTextRun run : event.getComposed()) {
                    composed.append(run.getText());
                }
                area.replaceText(area.getSelection(), composed.toString());
                imlength = composed.length();
                if (imlength != 0) {
                    int pos = imstart;
                    for (InputMethodTextRun run : event.getComposed()) {
                        int endPos = pos + run.getText().length();
                        //createInputMethodAttributes(run.getHighlight(), pos, endPos);
                        pos = endPos;
                    }
                    //addHighlight(imattrs, imstart);

                    // Set caret position in composed text
                    int caretPos = event.getCaretPosition();
                    if (caretPos >= 0 && caretPos < imlength) {
                        area.selectRange(imstart + caretPos, imstart + caretPos);
                    }
                }
            }
        }
}

Thanks for doing this and sharing. I cribbed the same code but had no way of testing it, or rather didn't know how to.
Anyway there's another piece of code (also copied from FX) that goes with this which I would really appreciate if you could evaluate/test. Put it in your setOnInputMethodTextChanged method above:

area.setInputMethodRequests(new InputMethodRequests()
{
    @Override public Point2D getTextLocation(int offset) {
        int pos = getSelection().getStart() + offset;
        Bounds charBounds = getCharacterBoundsOnScreen( pos, pos ).get();
        Point2D location = new Point2D( charBounds.getMinX(), charBounds.getMaxY() );
        return location;
    }

    @Override public int getLocationOffset(int x, int y) {
        return 0;
    }

    @Override public void cancelLatestCommittedText() {}

    @Override public String getSelectedText() {
        IndexRange selection = getSelection();
        return getText(selection.getStart(), selection.getEnd());
    }
});

Depending on your feedback, I can maybe include this all into the next release of RichTextFX.

According to https://github.com/FXMisc/RichTextFX/issues/146, a new class should be created which implements InputMethodRequests.

Here is the code I have tested okay:

import java.util.Optional;
import org.fxmisc.richtext.CodeArea;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.InputMethodRequests;
import javafx.scene.input.InputMethodTextRun;

public class Setup {
    private int imlength;
    private int imstart;

    public void setInputMethodRequests(CodeArea area){
        InputMethodRequestsObject imRequests = new InputMethodRequestsObject(area);
        area.setInputMethodRequests(imRequests);
    }

    public void setOnInputMethodTextChanged(CodeArea area){
        area.setOnInputMethodTextChanged(event -> {
            handleInputMethodEvent(event,area);
        });
    }

    public int getimlength(){
        return imlength;
    }

    public void handleInputMethodEvent(InputMethodEvent event,CodeArea area) {
            if (area.isEditable()  && !area.isDisabled()) {

                // remove previous input method text (if any) or selected text
                if (imlength != 0) {
                    //removeHighlight(imattrs);
                    //imattrs.clear();
                    area.selectRange(imstart, imstart + imlength);
                }

                // Insert committed text
                if (event.getCommitted().length() != 0) {
                    String committed = event.getCommitted();
                    area.replaceText(area.getSelection(), committed);
                }

                // Replace composed text
                imstart = area.getSelection().getStart();
                StringBuilder composed = new StringBuilder();
                for (InputMethodTextRun run : event.getComposed()) {
                    composed.append(run.getText());
                }
                area.replaceText(area.getSelection(), composed.toString());
                imlength = composed.length();
                if (imlength != 0) {
                    int pos = imstart;
                    for (InputMethodTextRun run : event.getComposed()) {
                        int endPos = pos + run.getText().length();
                        //createInputMethodAttributes(run.getHighlight(), pos, endPos);
                        pos = endPos;
                    }
                    //addHighlight(imattrs, imstart);

                    // Set caret position in composed text
                    int caretPos = event.getCaretPosition();
                    if (caretPos >= 0 && caretPos < imlength) {
                        area.selectRange(imstart + caretPos, imstart + caretPos);
                    }
                }
            }
        }

    public class InputMethodRequestsObject implements InputMethodRequests {

        InputMethodRequestsObject(CodeArea codeArea){
            area=codeArea;
        }

        public CodeArea area;

        @Override
        public
        String getSelectedText() {
            return area.getSelectedText();
        }
        @Override
        public
        int getLocationOffset(int x, int y) {
            return 0;
        }
        @Override
        public
        void cancelLatestCommittedText() {

        }
        @Override
        public
        Point2D getTextLocation(int offset) {
            // a very rough example, only tested under macOS
            Optional<Bounds> caretPositionBounds = area.getCaretBounds();
            if (caretPositionBounds.isPresent()) {
                Bounds bounds = caretPositionBounds.get();
                return new Point2D(bounds.getMaxX() - 5, bounds.getMaxY());
            }

            throw new NullPointerException();
        }
    }
}

Thanks a lot @xulihang, I'll submit a PR in the next couple of days or so ....

Was this page helpful?
0 / 5 - 0 ratings

Related issues

RobertBColton picture RobertBColton  路  8Comments

hendriklenz picture hendriklenz  路  9Comments

miho picture miho  路  10Comments

DaveJarvis picture DaveJarvis  路  6Comments

RobertBColton picture RobertBColton  路  5Comments