Search code examples
javaswingkey-bindingskeylistener

Consume typed key by implements KeyBindings


Can you please help me how to use KeyBinding and with Consume for typed Chars together, same way as demostraded my SSCCE by using KeyListener

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;

public class Login {

    private static final long serialVersionUID = 1L;
    /* PassWord for unlock*/
    private PswChecker checker = new PswChecker("pass");

    public Login() {
        JTextField firstField = new JTextField(10);
        firstField.addKeyListener(passwordKeyListener);
        JLabel firstLabel = new JLabel("Password is 'pass' ", JLabel.RIGHT);
        firstLabel.setLabelFor(firstField);
        JPanel p = new JPanel();
        p.setLayout(new GridLayout(0, 2, 5, 5));
        p.add(firstLabel);
        p.add(firstField);
        JFrame f = new JFrame("login");
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setContentPane(p);
        f.setLocationByPlatform(true);
        f.pack();
        f.setVisible(true);
    }
    //
    private KeyListener passwordKeyListener = new KeyListener() {

        private boolean enabled = true;

        @Override
        public void keyTyped(KeyEvent e) {
            if (!enabled) {
                return;
            }
            if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
                boolean b = checker.accept(e.getKeyChar());
                e.consume();
                if (b) {
                    enabled = false;
                    if (e.getComponent() != null) {
                        e.getComponent().removeKeyListener(this);
                    }
                    unlock();
                }
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
        }
    };

    void unlock() {
        JOptionPane.showMessageDialog(null, "unlocked");
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                Login log = new Login();
            }
        });
    }

    class PswChecker {

        private String password = null;
        private boolean unlocked = false;
        private long lastInputTimestamp = 0L;
        private int index = 0;

        public PswChecker(String password) {
            if (password == null) {
                throw new IllegalArgumentException("Null password");
            }
            if (password.trim().length() == 0) {
                throw new IllegalArgumentException("Empty password");
            }
            this.password = password;
        }

        public boolean accept(char c) {
            if (unlocked) {
                return true;
            }
            long timestamp = System.currentTimeMillis();
            if (timestamp - lastInputTimestamp > 700) {
                index = 0;
            }
            lastInputTimestamp = timestamp;
            if (password.charAt(index) == c) {
                index++;
            } else {
                if (password.charAt(0) == c) {
                    index = 1;
                } else {
                    index = 0;
                }
            }
            unlocked = (index == password.length());
            return unlocked;
        }

        public boolean isUnlocked() {
            return unlocked;
        }

        public boolean isLocked() {
            return !unlocked;
        }

        @Override
        public String toString() {
            return unlocked ? "UNLOCKED" : "LOCKED";
        }

        /*private boolean check(String keystrokes, String password, boolean expectUnLocked) {
        PswChecker checker = new PswChecker(password);
        for (int i = 0; i < keystrokes.length(); i++) {
        checker.accept(keystrokes.charAt(i));
        }
        return checker.isUnlocked();
        }*/
    }
}

Solution

  • For security, consider JPasswordField, illustrated here. This would allow use of a DocumentFilter, discussed here.

    Addendum: Even for the more general case, I'd use a DocumentFilter, as shown below. I'd use key bindings for sharing an Action among components, as shown in this keypad example.

    Addendum: To illustrate @kleopatra's comment, I've updated the code to bind ESC to Reset. As a practical matter, I'd use only keys that aren't already bound to text field actions or required for normal use.

    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import javax.swing.*;
    import javax.swing.text.*;
    
    /** @see https://stackoverflow.com/q/9610386/230513 */
    public class Login {
    
        private static final String PWD = "pass";
        private static final String RESET = "Reset";
        private PlainDocument doc = new PlainDocument();
        private JTextField text = new JTextField(doc, "", 10);
    
        public Login() {
            doc.setDocumentFilter(new FieldFilter(PWD));
            JLabel label = new JLabel("Password is '" + PWD + "'", JLabel.RIGHT);
            label.setLabelFor(text);
            text.setToolTipText("Press ESC to reset.");
            text.getInputMap().put(
                KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), RESET);
            text.getActionMap().put(RESET, new Reset());
            JPanel p = new JPanel();
            p.setLayout(new GridLayout(0, 2, 5, 5));
            p.add(label);
            p.add(text);
            JFrame f = new JFrame("Login");
            f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            f.setContentPane(p);
            f.setLocationByPlatform(true);
            f.pack();
            f.setVisible(true);
        }
    
        private static class FieldFilter extends DocumentFilter {
    
            private String password;
            private boolean unlocked;
            private StringBuilder sb = new StringBuilder();
    
            public FieldFilter(String password) {
                this.password = password;
            }
    
            @Override
            public void replace(FilterBypass fb, int offset, int length,
                String text, AttributeSet attrs) throws BadLocationException {
                if (unlocked) {
                    super.replace(fb, offset, length, text, attrs);
                } else {
                    sb.append(text);
                    unlocked = password.equals(sb.toString());
                }
            }
    
            public void reset() {
                sb.delete(0, sb.length());
                unlocked = false;
            }
        }
    
        private static class Reset extends AbstractAction {
    
            @Override
            public void actionPerformed(ActionEvent e) {
                JTextField jtf = (JTextField) e.getSource();
                PlainDocument doc = (PlainDocument) jtf.getDocument();
                try {
                    doc.remove(0, doc.getLength());
                } catch (BadLocationException ex) {
                    ex.printStackTrace(System.err);
                }
                FieldFilter filter = (FieldFilter) doc.getDocumentFilter();
                filter.reset();
            }
        }
    
        public static void main(String[] args) {
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    Login log = new Login();
                }
            });
        }
    }