2

I'm creating a notepad program in java and I would like to implement auto-close braces, which include (), [], and {}. Right now I'm having trouble detecting when the left parenthesis is pressed using keylisteners.

// called in the GUI create method
// passes in a JTextPane in the parameter
autoCloseBraces(txt);

private static void AutoCloseBraces(JTextPane txt) {

        txt.addKeyListener(new KeyListener() {

            @Override
            public void keyPressed(KeyEvent e) {

                ArrayList<Integer> keyCode = new ArrayList<Integer>();
                keyCode.add(e.getKeyCode());
                if (keyCode.contains(KeyEvent.VK_SHIFT) && keyCode.contains(KeyEvent.VK_9)) {

                    System.out.println("OK");

                }

            }

            @Override
            public void keyReleased(KeyEvent e) {

                if (e.getKeyCode() == KeyEvent.VK_9) {

                    System.out.println("OKKKKKK");

                }

            }

            @Override
            public void keyTyped(KeyEvent e) {

                if (e.getKeyCode() == KeyEvent.VK_9) {

                    System.out.println("OKK");

                }

            }

        });

    }

I've also tried using KeyEvent.VK_LEFT_PARENTHESIS in replace of KeyEvent.VK_9 but nothing seems to be working. When either '(' or '9' is pressed it acts as if only '9' was pressed. Not sure what's going on here. I've also tried searching google for examples of it but couldn't find anything. Anybody know what the problem is or where I could go to find an explanation or example of a working code?

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
Vince
  • 2,596
  • 11
  • 43
  • 76
  • 4
    Here is an SO post titled: [How can a KeyListener detect key combinations](http://stackoverflow.com/questions/7851505/how-can-a-keylistener-detect-key-combinations-e-g-alt-1-1) and [here](http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) is the documentation referenced in the accepted answer. – CubeJockey Jul 27 '15 at 19:46
  • Use a DocumentFilter to detect the incoming changes and make changes to the document as needed – MadProgrammer Jul 27 '15 at 21:30
  • As a [conceptual example](http://stackoverflow.com/questions/14727548/java-change-the-document-in-documentlistener/14727657#14727657) – MadProgrammer Jul 27 '15 at 21:44

2 Answers2

3

This is both simple and complicated based on what you want to achieve, first however, some "do nots":

  • Don't use KeyListener or key bindings with text components if you intend to modify the text of the field, neither of these are notified when the user pastes test
  • Don't use a DocumentListener as this can generate an infinite loop and cause mutation exceptions in the Document

Okay, so what should you use? A DocumentFilter, which allows you to modify the text been passed into the underlying Document of the text component. See Implementing a Document Filter for more details.

The next problem is, generally speaking as a user, I'd prefer if you didn't modify the position of my cursor (unless I want you to ;)). Now, in this case, it would be rather rude to insert a } and have the cursor move to the right hand side of it, because now I have to move the cursor back and that just breaks the work flow.

This is a conundrum, as the DocumentFilter has no idea about the text component (and nor should it), so we need some way for the filter to tell us that it's about to apply a change and has applied a change.

To this end, we can create a custom DocumentFilter which has a listener call back functionality, which can notify us that it's about to apply a "auto complete" change and that is has completed a "auto complete" change. This gives us two points in time, before and after the change, so we can grab the information we need in order to control the cursor...

public interface AutoCompleteListener {

    public void willAutoComplete(SyntaxFilter filter);

    public void didAutoComplete(SyntaxFilter filter);

}

public class SyntaxFilter extends DocumentFilter {

    private List<AutoCompleteListener> listeners;

    public SyntaxFilter() {
        listeners = new ArrayList<>(25);
    }

    public void addAutoCompleteListener(AutoCompleteListener listener) {
        listeners.add(listener);
    }

    public void removeAutoCompleteListener(AutoCompleteListener listener) {
        listeners.add(listener);
    }

    protected void fireWillAutoComplete() {
        for (AutoCompleteListener listener : listeners) {
            listener.willAutoComplete(this);
        }
    }

    protected void fireDidAutoComplete() {
        for (AutoCompleteListener listener : listeners) {
            listener.didAutoComplete(this);
        }
    }

    @Override
    public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
        super.replace(fb, offset, length, text, attrs); //To change body of generated methods, choose Tools | Templates.
        if (text.endsWith("{")) {
            fireWillAutoComplete();
            fb.insertString(fb.getDocument().getLength(), "}", attrs);
            fireDidAutoComplete();
        }
    }

}

Now, there are a couple of ways I can think of to control the cursor position, you could simply grab the current position before the change and reset it after the change, this will work, but you might find it generates a small rendering flick as the cursor is flung about the place. A alternative is simply to stop the cursor from been moved when an update occurs, for example...

Helloworld

JTextArea editor = new JTextArea(10, 20);
SyntaxFilter filter = new SyntaxFilter();
filter.addAutoCompleteListener(new AutoCompleteListener() {
    private int updatePolicy = DefaultCaret.UPDATE_WHEN_ON_EDT;

    @Override
    public void willAutoComplete(SyntaxFilter filter) {
        updatePolicy = ((DefaultCaret) editor.getCaret()).getUpdatePolicy();
        ((DefaultCaret) editor.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
    }

    @Override
    public void didAutoComplete(SyntaxFilter filter) {
        ((DefaultCaret) editor.getCaret()).setUpdatePolicy(updatePolicy);
    }
});
((AbstractDocument) editor.getDocument()).setDocumentFilter(filter);
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

Not entirely certain why you are using an ArrayList of keyCodes. As you initialize the ArrayList in the method, it will only contain one value anyways (that of e.getKeyCode()). If you are trying to use combinations of keys, you theoretically can with KeyListeners, but it is far better to use KeyBindings.

As for the title question:

Use the actual ASCII values with KeyListeners.

For the left parenthesis, the keyCode is 40 (you can find many ASCII tables on the internet, including this one).

if(e.getKeyCode() == 40){
    //do something
}
Alexander Guyer
  • 2,063
  • 1
  • 14
  • 20
  • When it comes to wanting to modify a text component in real time, neither a KeyListener or key binding or DocumentListener is appropriate. The key input Apis won't be notified of changes to the field if the user pastes txt into the field and the DocumentListener isn't meant for modifying the state of the field during its execution, instead a DocumentFilter would be suitable – MadProgrammer Jul 27 '15 at 21:47
  • @MadProgrammer This is correct. Sorry, I did not realize that the program being created involved editable text fields. – Alexander Guyer Jul 27 '15 at 21:56