Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct InputMethodTextChanged Event Handling #980

Closed
xulihang opened this issue Nov 18, 2020 · 4 comments · Fixed by #985
Closed

Correct InputMethodTextChanged Event Handling #980

xulihang opened this issue Nov 18, 2020 · 4 comments · Fixed by #985

Comments

@xulihang
Copy link
Contributor

xulihang commented Nov 18, 2020

The workaround in the #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

@xulihang xulihang changed the title correct InputMethodTextChanged event Correct InputMethodTextChanged Event Handling Nov 18, 2020
@xulihang
Copy link
Contributor Author

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);
	                }
	            }
	        }
	    }
}

@Jugen
Copy link
Collaborator

Jugen commented Nov 19, 2020

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.

@xulihang
Copy link
Contributor Author

xulihang commented Nov 19, 2020

According to #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();
	    }
	}
}

@Jugen
Copy link
Collaborator

Jugen commented Nov 19, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants