2

We are replacing a legacy C application originally written for MSDOS (yes, believe it or not!). This application uses a specially remapped keyboard which intercepts the DOS keyboard interrupt (remember that??!) to sometimes alter the scan codes of the keys pressed by the user so that different processing would occur. Special labels were then placed on the keys telling the users the "new" meaning of these keys.

The new Java version is required to preserve this keyboard layout which the targeted group of users is very familiar with.

An example of what we are trying to do is as follows:

You may never have thought about this, but the numeric keypad of a modern telephone is reversed from the numeric keypad of a computer keyboard. On the former 1-2-3 is on the top row and on the latter it is on the bottom row. We are required to make the keyboard's numeric keypad look like the telephone. Let's say, when the user types "7" on the numeric keypad, we want it look as though he typed a "1", when he types an "8", we want a "2", when he types a "3" we want a "9".

There is much more that we have to do to emulate the DOS application, but we can't even solve this simple case now. I have been all over Key Binding, KeyAdapters, KeyListeners, and even KeyEventDispatchers, and I cannot make this work. I am pretty sure we have to work on the lowest level we are allowed to work by Java to come as close as possible to what the legacy app does. And needless to say, we want the cleanest implementation possible, so that the application level code is not littered with inputMaps and actionMaps etc. As much as possible this needs to be handled globally. Can anyone help?

Steve Cohen
  • 4,679
  • 9
  • 51
  • 89
  • It should not be very complicated. `InputMap` should do the job, but you need to remember to remove the previous key bindings. (If you are on linux you could use xmodmap at the lowest level, but you are probably better off doing at the Java level since your application is then portable to any system with standard keyboards.) – toto2 Jun 15 '11 at 17:30
  • All right, to get a little more specific, let's assume we're going to use KeyEventDispatchers as the solution. Java documentation is very unclear on this point: http://download.oracle.com/javase/7/docs/api/java/awt/KeyboardFocusManager.html#addKeyEventDispatcher%28java.awt.KeyEventDispatcher%29 talks about adding a KeyEventDispatcher, but goes on to say that the default cannot be removed. From the behavior observed when I try this, it seems the the default KeyEventDispatcher is getting called first. When you add a KeyEventDispatcher, which is called first, the most or least recently added? – Steve Cohen Jun 15 '11 at 17:31
  • OK, toto, to pursue your suggestion further, "you need to remember to remove the previous key bindings" How would I do that? That is, remove the default key bindings? – Steve Cohen Jun 15 '11 at 17:38

4 Answers4

3

If I were doing this, I would write the Java App without worrying about the key bindings. Assume that when a component gets a keyevent for #7 its #7, don't worry about whether the 7 or 1 was really typed. The app shouldn't care about how keys are mapped on the keyboard. This should let you start developing the app immediately.

As far as overriding key bindings, this seems like where you want to look: http://download.oracle.com/javase/7/docs/api/java/awt/KeyEventDispatcher.html

It sounds like you can write your own KeyEventDispatcher to handle all the key mapping logic and it prevents mapping logic from messing up the rest of the logic in your application.

meverett
  • 921
  • 5
  • 6
  • meverett: No, that won't work. For example, there are many dialog tabs. When the user types "7" while focused on a text box, "1", not "7" will appear. Using Key Bindings, I see "71" when I do that. Tried KeyEventDispatcher, but can't make that work either so far. See earlier post on that. – Steve Cohen Jun 15 '11 at 17:45
  • Are you consuming the first KeyEvent before broadcasting the desired mapped key? It sounds like the KeyEventDispatcher is what you want, just need to spend sometime figuring out exactly what its doing. – meverett Jun 15 '11 at 18:01
  • I agree that KeyEventDispatcher sounds like what I want, but it doesn't seem to work. The simple technique outlined here: http://www.exampledepot.com/egs/java.awt/DispatchKey.html#comment-51807 doesn't seem to get us there (the untranslated keystroke appears in the text box, not the translated one) and it's not really well documented. Looking for a solid example of this. – Steve Cohen Jun 15 '11 at 18:20
  • @Steve Cohen, The code from that example worked fine for me. I use JDK6_07 on XP. Post the actual SSCCE (http://sscce.org) that you used to text the code so other can test it as well to see if this is a version/platform issue. – camickr Jun 15 '11 at 19:56
  • I guess I should try to run that code myself and see what the difference is to what I was doing. I THOUGHT I was copying his method, and it was pretty simple, but somehow it didn't work. – Steve Cohen Jun 15 '11 at 20:24
  • @camickr, OK, I have posted an SSCCE on PasteBin: http://pastebin.com/dLZyweK9 . The code in exampledepot.com/egs/java.awt/DispatchKey.html#comment-51807 does indeed work - BUT - when extending the concept to numeric keypad remapping, it does not seem to work. Try it. – Steve Cohen Jun 15 '11 at 21:27
2

This is hacky, and I'll admit I haven't used it myself, but you could subclass KeyEvent and override the fields as needed. So yourSubclass.VK_NUMPAD1 is the integer value of KeyEvent.VK_NUMPAD7, yourSubclass.VK_NUMPAD2 is the integer value of KeyEvent.VK_NUMPAD8, etcetera. Then use your subclass everywhere KeyEvent is normally used.

tsm
  • 3,598
  • 2
  • 21
  • 35
2

I also agree that the KeyEventDispatcher should work. But if this a version/platform issue, then maybe you can use a custom Event Queue. See Global Event Dispatching

camickr
  • 321,443
  • 19
  • 166
  • 288
1

My stack overfloweth! The answer was here: http://download.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html which says:

For key pressed and key released events, the getKeyCode method returns the event's keyCode. For key typed events, the getKeyCode method always returns VK_UNDEFINED.

My original attempt thought it could get a keyCode on KEY_TYPED. It could not, and it was the KEY_TYPED event that was clobbering the mapping done in KEY_PRESSED.

Here is a working implementation:

import static java.awt.event.KeyEvent.*;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;


public class KeyboardDispatcherDemo extends JFrame {

    /**
     * This class shows how to map numeric keypad keys.
     * It performs two conversions:
     * 1. lower-to-uppercase
     * 2. if numeric keypad 7-9 is typed, 1-3 appears and vice versa.
     * 
     * This is modified from the code at 
     * http://www.exampledepot.com/egs/java.awt/DispatchKey.html#comment-51807
     * which demoes the lower-to-upper conversion.
     * 
     * It doesn't yet handle modified numeric keypad keys.
     * 
     */
    public KeyboardDispatcherDemo() {

        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(
            new KeyEventDispatcher() {
                private char lastMappedKey;
                private final Map<Integer, Character> keyMap = 
                    new HashMap<Integer, Character>() { 
                {
                    put(VK_NUMPAD1, '7'); 
                    put(VK_NUMPAD2, '8'); 
                    put(VK_NUMPAD3, '9'); 
                    put(VK_NUMPAD7, '1'); 
                    put(VK_NUMPAD8, '2'); 
                    put(VK_NUMPAD9, '3'); 

                }};

                public boolean dispatchKeyEvent(KeyEvent e) {
                    System.out.println(String.format("INPUT: %s", e.toString()));
                    boolean dispatch = false;
                    switch (e.getID()) {
                    case KeyEvent.KEY_PRESSED:
                        dispatch = dispatchKeyPressed(e);
                        break;
                    case KeyEvent.KEY_TYPED:
                        dispatch = dispatchKeyTyped(e);
                        break;
                    case KeyEvent.KEY_RELEASED: 
                        dispatch = dispatchKeyReleased(e);
                        break;
                    default:
                        throw new IllegalArgumentException();
                    }
                    System.out.println(String.format("OUTPUT: %s", e.toString()));
                    System.out.println();
                    return dispatch;
                }
                private boolean dispatchKeyPressed(KeyEvent e) {
                    char k = e.getKeyChar();
                    if (k != CHAR_UNDEFINED) {
                        if (Character.isLetter(k)) {
                            e.setKeyChar(Character.toUpperCase(e.getKeyChar()));
                        } else if (e.getModifiers() == 0){
                            Character mapping = keyMap.get(e.getKeyCode());
                            if (mapping != null) {
                                e.setKeyChar(mapping);
                            }
                        }
                        // save the last mapping so that KEY_TYPED can use it.
                        // note we don't do this for modifier keys.
                        this.lastMappedKey = e.getKeyChar();
                    }
                    return false;
                }

                // KEY_TYPED events don't have keyCodes so we rely on the
                // lastMappedKey that was saved on KeyPressed.
                private boolean dispatchKeyTyped(KeyEvent e) {
                    char k = e.getKeyChar();
                    if (k != CHAR_UNDEFINED) {
                        e.setKeyChar(lastMappedKey);
                    }
                    return false;
                }
                private boolean dispatchKeyReleased(KeyEvent e) {
                    char k = e.getKeyChar();
                    if (k != CHAR_UNDEFINED) {
                        e.setKeyChar(lastMappedKey);
                        this.lastMappedKey=CHAR_UNDEFINED;
                    }
                    return false;

                }
            });




        setTitle("KeyboardDispatcherDemo");
        JPanel panel = new JPanel();
        panel.setBackground(new Color(204, 153, 255));
        panel.setLayout(new BorderLayout());
        getContentPane().add(panel, BorderLayout.CENTER);

        JTextArea staticText = new JTextArea();
        staticText.setText("This demonstrates how to map numeric keypad keys.  It uppercases all letters and converts Numeric Keypad 1-3 to 7-9 and vice versa.  Try it.");
        staticText.setLineWrap(true);
        staticText.setWrapStyleWord(true);
        panel.add(staticText, BorderLayout.NORTH);
        staticText.setFocusable(false);

        JTextField textField = new JTextField();
        textField.setText("");
        textField.setHorizontalAlignment(SwingConstants.LEFT);
        panel.add(textField, BorderLayout.SOUTH);
        textField.setColumns(10);
        textField.setFocusable(true);


        setSize(getPreferredSize());

        setVisible(true);


    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        new KeyboardDispatcherDemo();

    }

    @Override
    public Dimension getPreferredSize() {
        // TODO Auto-generated method stub
        return new Dimension(400,300);
    }

}

Thanks to all who nudged me toward the answer.

which brings me to my next question ... stay tuned.

Steve Cohen
  • 4,679
  • 9
  • 51
  • 89