purring pigeon
purring pigeon

Reputation: 4209

How to consume a TAB/Enter KeyPressed on the TextArea, and replace with focustraversal or enter key without using internal API?

I need to have a control which will wordwrap, add scrollbars, etc - but ignore the enter key and jump to the next control using tab/shift tab. I can't seem to get this right.

This is the control I have done, and it seems to just simply stay in the text area. (This was used from an old example online and it seems to work only if the textArea is in the same node as the rest).

public class TabAndEnterIgnoringTextArea extends TextArea {

final TextArea myTextArea = this;

public TabAndEnterIgnoringTextArea() {
    this.setWrapText(true);
    addEventFilter(KeyEvent.KEY_PRESSED, new TabAndEnterHandler());
}

private class TabAndEnterHandler implements EventHandler<KeyEvent> {
    private KeyEvent recodedEvent;

    @Override
    public void handle(KeyEvent event) {
        if (recodedEvent != null) {
            recodedEvent = null;
            return;
        }


        Parent parent = getParent();
        if (parent != null) {
            switch (event.getCode()) {
            case ENTER:
                if (event.isControlDown()) {
                    recodedEvent = recodeWithoutControlDown(event);
                    myTextArea.fireEvent(recodedEvent);
                } else {
                    Event parentEvent = event.copyFor(parent, parent);
                    myTextArea.getParent().fireEvent(parentEvent);
                }
                event.consume();
                break;

            case TAB:
                if (event.isControlDown()) {
                    recodedEvent = recodeWithoutControlDown(event);
                    myTextArea.fireEvent(recodedEvent);
                } else if (event.isShiftDown()) {
                    ObservableList<Node> children = FXCollections.observableArrayList();
                    addAllDescendents(parent, children);
                    int idx = children.indexOf(myTextArea);
                    if (idx > 0) {
                        for (int i = idx - 1; i > 0; i--) {
                            if (children.get(i).isFocusTraversable()) {
                                children.get(i).requestFocus();
                                break;
                            }
                        }
                    }
                } else {
                    ObservableList<Node> children = FXCollections.observableArrayList();
                    addAllDescendents(parent, children);
                    int idx = children.indexOf(myTextArea);
                    if (idx >= 0) {
                        for (int i = idx + 1; i < children.size(); i++) {
                            if (children.get(i).isFocusTraversable()) {
                                children.get(i).requestFocus();
                                break;
                            }
                        }
                        if (idx + 1 >= children.size()) {
                            for (int i = 0; i < idx; i++) {
                                if (children.get(i).isFocusTraversable()) {
                                    children.get(i).requestFocus();
                                    break;
                                }
                            }
                        }
                    }
                }
                event.consume();
                break;
            default:
                break;
            }
        }
    }

    private void addAllDescendents(Parent parent, ObservableList<Node> nodes) {
        for (Node node : parent.getChildrenUnmodifiable()) {
            nodes.add(node);
            if (node instanceof Parent)
                addAllDescendents((Parent) node, nodes);
        }
    }

    private KeyEvent recodeWithoutControlDown(KeyEvent event) {
        return new KeyEvent(event.getEventType(), event.getCharacter(), event.getText(), event.getCode(),
                event.isShiftDown(), false, event.isAltDown(), event.isMetaDown());
    }
}

Once I land in my field, it won't leave with the keyboard. Any ideas? Also - I shouldn't assume that the next control is actually in the nodes within my parent, as the control may be part of another control where its the last control and the next one might be on the parent above.

Basically I want the next landable item in the scenegraph.

I am able to do it with internal API - but I know that is very discouraged.

public class TabAndEnterIgnoringTextArea extends TextArea {

final TextArea myTextArea = this;

public TabAndEnterIgnoringTextArea() {
    addEventFilter(KeyEvent.KEY_PRESSED, new TabAndEnterHandler());
}

class TabAndEnterHandler implements EventHandler<KeyEvent> {
    private KeyEvent recodedEvent;

    @Override
    public void handle(KeyEvent event) {
        if (recodedEvent != null) {
            recodedEvent = null;
            return;
        }

        Parent parent = myTextArea.getParent();
        Scene scene = parent.getScene();
        while (scene == null){
            parent = parent.getParent();
            scene = parent.getScene();
        }
        SceneTraversalEngine engine = new SceneTraversalEngine(getScene());

        if (parent != null) {
            switch (event.getCode()) {
            case ENTER:
                if (event.isControlDown()) {
                    recodedEvent = recodeWithoutControlDown(event);
                    myTextArea.fireEvent(recodedEvent);
                } else {
                    Event parentEvent = event.copyFor(parent, parent);
                    myTextArea.getParent().fireEvent(parentEvent);
                }
                event.consume();
                break;

            case TAB:
                if(event.isShiftDown()){
                    engine.trav(myTextArea, Direction.PREVIOUS);
                }else {
                    engine.trav(myTextArea, Direction.NEXT);
                }
            }
        }
    }

    private KeyEvent recodeWithoutControlDown(KeyEvent event) {
        return new KeyEvent(event.getEventType(), event.getCharacter(), event.getText(), event.getCode(),
                event.isShiftDown(), false, event.isAltDown(), event.isMetaDown());
    }
}

}

Thanks

Upvotes: 1

Views: 995

Answers (1)

purring pigeon
purring pigeon

Reputation: 4209

I think I found a solution which will allow me to have this work as designed.

public class TabAndEnterIgnoringTextArea extends TextArea {

final TextArea myTextArea = this;

public TabAndEnterIgnoringTextArea() {
    this.setWrapText(true);
    addEventFilter(KeyEvent.KEY_PRESSED, new TabAndEnterHandler());
}

private class TabAndEnterHandler implements EventHandler<KeyEvent> {
    @Override
    public void handle(KeyEvent event) {
        if(event.getCode() == KeyCode.TAB || event.getCode() == KeyCode.ENTER) {
            event.consume();
            if(event.getCode() == KeyCode.TAB){
                selectNextNode(!event.isShiftDown());
            }
        }
    }

    private void selectNextNode(boolean forward){
        List<Node> nodes = getAllNodes(myTextArea.getScene().getRoot());
        int index = nodes.indexOf(myTextArea);
        if(forward){
            if(index < nodes.size() - 1) {
                nodes.get(index + 1).requestFocus();
            }else {
                nodes.get(0).requestFocus();
            }
        }else {
            if(index == 0) {
                nodes.get(nodes.size() - 1).requestFocus();
            }else {
                nodes.get(index - 1).requestFocus();
            }
        }
    }

    private ArrayList<Node> getAllNodes(Parent root) {
        ArrayList<Node> nodes = new ArrayList<Node>();
        addAllDescendents(root, nodes);
        return nodes;
    }

    private void addAllDescendents(Parent parent, ArrayList<Node> nodes) {
        for (Node node : parent.getChildrenUnmodifiable()) {
            if(node.isFocusTraversable()){
                nodes.add(node);
            }
            if (node instanceof Parent)
                addAllDescendents((Parent)node, nodes);
        }
    }
}
}

If you see anything wrong with this approach I would appreciate it, but it seems to work for my purposes.

Upvotes: 1

Related Questions