001: package abbot.tester;
002:
003: import java.lang.reflect.Field;
004: import java.awt.*;
005: import java.awt.Robot;
006: import java.awt.event.*;
007: import java.util.*;
008: import java.io.*;
009:
010: import javax.swing.*;
011: import javax.swing.text.*;
012:
013: import abbot.Log;
014: import abbot.Platform;
015: import abbot.editor.OSXAdapter;
016:
017: /** Provides read/write of locale-specific mappings for virtual keycode-based
018: KeyStrokes to characters and vice versa.
019: <p>
020: If your locale's map is not present in src/abbot/tester/keymaps, please
021: run this class's {@link #main(String[])} method to generate
022: them and
023: <a href="http://sourceforge.net/tracker/?group_id=50939&atid=461492">submit
024: them to the project</a> for inclusion.
025: <p>
026: Variations among locales and OSes are expected; if a map for a locale+OS
027: is not found, the system falls back to the locale map.
028: */
029: public class MapGenerator extends KeyStrokeMap {
030: private static boolean setModifiers(Robot robot, int mask,
031: boolean press) {
032: try {
033: if ((mask & KeyEvent.SHIFT_MASK) != 0) {
034: if (press)
035: robot.keyPress(KeyEvent.VK_SHIFT);
036: else
037: robot.keyRelease(KeyEvent.VK_SHIFT);
038: }
039: if ((mask & KeyEvent.CTRL_MASK) != 0) {
040: if (press)
041: robot.keyPress(KeyEvent.VK_CONTROL);
042: else
043: robot.keyRelease(KeyEvent.VK_CONTROL);
044: }
045: if ((mask & KeyEvent.ALT_MASK) != 0) {
046: if (press)
047: robot.keyPress(KeyEvent.VK_ALT);
048: else
049: robot.keyRelease(KeyEvent.VK_ALT);
050: }
051: if ((mask & KeyEvent.META_MASK) != 0) {
052: if (press)
053: robot.keyPress(KeyEvent.VK_META);
054: else
055: robot.keyRelease(KeyEvent.VK_META);
056: }
057: if ((mask & KeyEvent.ALT_GRAPH_MASK) != 0) {
058: if (press)
059: robot.keyPress(KeyEvent.VK_ALT_GRAPH);
060: else
061: robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
062: }
063: return true;
064: } catch (IllegalArgumentException e) {
065: // ignore these
066: } catch (Exception e) {
067: Log.warn(e);
068: }
069: return false;
070: }
071:
072: private static class KeyWatcher extends KeyAdapter {
073: public char keyChar;
074: public boolean keyTyped;
075: public boolean keyPressed;
076: public String codeName;
077:
078: public void keyPressed(KeyEvent e) {
079: keyPressed = true;
080: // For debug only; activating this stuff tends to interfere with
081: // key capture
082: /*
083: Document d = ((JTextComponent)e.getComponent()).getDocument();
084: try {
085: String insert = codeName != null
086: ? insert = "\n" + codeName + "=" : "";
087: d.insertString(d.getLength(), insert, null);
088: }
089: catch(BadLocationException ble) {
090: }
091: */
092: }
093:
094: public void keyTyped(KeyEvent e) {
095: keyChar = e.getKeyChar();
096: keyTyped = true;
097: // For debug only; activating this stuff tends to interfere with
098: // key capture
099: /*
100: Document d = ((JTextComponent)e.getComponent()).getDocument();
101: char[] data = { keyChar };
102: try {
103: String insert = new String(data)
104: + " (" + String.valueOf((int)keyChar) + ")";
105: d.insertString(d.getLength(), insert, null);
106: }
107: catch(BadLocationException ble) {
108: }
109: */
110: codeName = null;
111: }
112: }
113:
114: private static KeyWatcher watcher = null;
115: private static final int UNTYPED = -1;
116: private static final int UNDEFINED = -2;
117: private static final int ILLEGAL = -3;
118: private static final int SYSTEM = -4;
119: private static final int ERROR = -5;
120:
121: private static int generateKey(final Window w, final Component c,
122: final Robot robot, Point p, String name, int code,
123: final boolean refocus) {
124: if (watcher == null) {
125: watcher = new KeyWatcher();
126: c.addKeyListener(watcher);
127: }
128: try {
129: robot.waitForIdle();
130: if (refocus) {
131: SwingUtilities.invokeAndWait(new Runnable() {
132: public void run() {
133: w.setVisible(true);
134: w.toFront();
135: c.requestFocus();
136: if (Platform.isWindows()
137: || Platform.isMacintosh()) {
138: robot.mouseMove(
139: w.getX() + w.getWidth() / 2, w
140: .getY()
141: + w.getHeight() / 2);
142: robot.mousePress(InputEvent.BUTTON1_MASK);
143: robot.mouseRelease(InputEvent.BUTTON1_MASK);
144: }
145: }
146: });
147: }
148: robot.mouseMove(p.x, p.y);
149: robot.waitForIdle();
150: try {
151: watcher.codeName = name;
152: watcher.keyTyped = watcher.keyPressed = false;
153: robot.keyPress(code);
154: robot.keyRelease(code);
155: long start = System.currentTimeMillis();
156: while (!watcher.keyPressed || !watcher.keyTyped) {
157: if (System.currentTimeMillis() - start > 500)
158: break;
159: robot.waitForIdle();
160: }
161: if (!watcher.keyPressed) {
162: // alt-tab, alt-f4 and the like which get eaten by the OS
163: return SYSTEM;
164: } else if (!watcher.keyTyped)
165: // keys which result in KEY_TYPED event
166: return UNTYPED;
167: else if (watcher.keyChar == KeyEvent.CHAR_UNDEFINED)
168: // usually the same as UNTYPED, but just in case
169: return UNDEFINED;
170: else
171: return watcher.keyChar;
172: } catch (IllegalArgumentException e) {
173: // not supported on this system
174: return ILLEGAL;
175: }
176: } catch (Exception e) {
177: // usually a core library bug
178: Log.warn(e);
179: return ERROR;
180: }
181: }
182:
183: private static boolean isFunctionKey(String name) {
184: if (name.startsWith("VK_F")) {
185: try {
186: Integer.parseInt(name.substring(4));
187: return true;
188: } catch (NumberFormatException e) {
189: }
190: }
191: return false;
192: }
193:
194: private static final Comparator FIELD_COMPARATOR = new Comparator() {
195: public int compare(Object o1, Object o2) {
196: try {
197: String n1 = ((Field) o1).getName();
198: String n2 = ((Field) o2).getName();
199: return n1.compareTo(n2);
200: } catch (Exception e) {
201: return 0;
202: }
203: }
204: };
205:
206: // From a VK_ code + modifiers, produce a simluated KEY_TYPED
207: // From a keychar, determine the necessary VK_ code + modifiers
208: private static void generateKeyStrokeMap(Window w, JTextComponent c) {
209: // TODO: invoke modifiers for multi-byte input sequences?
210: // Skip known modifiers and locking keys
211: Collection skip = Arrays.asList(new String[] {
212: "VK_UNDEFINED",
213: // modifiers
214: "VK_SHIFT",
215: "VK_CONTROL",
216: "VK_META",
217: "VK_ALT",
218: "VK_ALT_GRAPH",
219: // special-function keys
220: "VK_CAPS_LOCK",
221: "VK_NUM_LOCK",
222: "VK_SCROLL_LOCK",
223: // Misc other function keys
224: "VK_KANA", "VK_KANJI", "VK_ALPHANUMERIC",
225: "VK_KATAKANA", "VK_HIRAGANA", "VK_FULL_WIDTH",
226: "VK_HALF_WIDTH", "VK_ROMAN_CHARACTERS",
227: "VK_ALL_CANDIDATES", "VK_PREVIOUS_CANDIDATE",
228: "VK_CODE_INPUT", "VK_JAPANESE_KATAKANA",
229: "VK_JAPANESE_HIRAGANA", "VK_JAPANESE_ROMAN",
230: "VK_KANA_LOCK", "VK_INPUT_METHOD_ON_OFF", });
231: System.out.println("Generating keystroke map");
232: try {
233: Robot robot = new Robot();
234: // Make sure the window is ready for input
235: if (!RobotVerifier.verify(robot)) {
236: System.err
237: .println("Robot non-functional, can't generate map");
238: System.exit(1);
239: }
240: robot.delay(500);
241: Field[] fields = KeyEvent.class.getDeclaredFields();
242: Set codes = new TreeSet(FIELD_COMPARATOR);
243: for (int i = 0; i < fields.length; i++) {
244: String name = fields[i].getName();
245: if (name.startsWith("VK_") && !skip.contains(name)
246: && !name.startsWith("VK_DEAD_")
247: && !isFunctionKey(name)) {
248: codes.add(fields[i]);
249: }
250: }
251: System.out
252: .println("Total VK_ fields read: " + codes.size());
253: Point p = c.getLocationOnScreen();
254: p.x += c.getWidth() / 2;
255: p.y += c.getHeight() / 2;
256: // for now, only do reasonable modifiers; add more if the need
257: // arises
258: int[] modifierCombos = { 0, KeyEvent.SHIFT_MASK,
259: KeyEvent.CTRL_MASK, KeyEvent.META_MASK,
260: KeyEvent.ALT_MASK, KeyEvent.ALT_GRAPH_MASK, };
261: String[] MODIFIERS = { "none", "shift", "control", "meta",
262: "alt", "alt graph", };
263: // These modifiers might trigger window manager functions
264: int needRefocus = KeyEvent.META_MASK | KeyEvent.ALT_MASK;
265: Map[] maps = new Map[modifierCombos.length];
266: for (int m = 0; m < modifierCombos.length; m++) {
267: Map map = new TreeMap(FIELD_COMPARATOR);
268: int mask = modifierCombos[m];
269: if (!setModifiers(robot, mask, true)) {
270: System.out.println("Modifier " + MODIFIERS[m]
271: + " is not currently valid");
272: continue;
273: }
274: System.out.println("Generating keys with mask="
275: + MODIFIERS[m]);
276: Iterator iter = codes.iterator();
277: // Always try to fix the focus; who knows what keys have
278: // been mapped to the WM
279: boolean focus = true;
280: while (iter.hasNext()) {
281: Field f = (Field) iter.next();
282: int code = f.getInt(null);
283: //System.out.println(f.getName() + ".");
284: System.out.print(".");
285: int value = generateKey(w, c, robot, p,
286: f.getName(), code, focus
287: || (mask & needRefocus) != 0);
288: map.put(f, new Integer(value));
289: }
290: setModifiers(robot, modifierCombos[m], false);
291: System.out.println("");
292: maps[m] = map;
293: }
294:
295: Properties props = new Properties();
296: Iterator iter = maps[0].keySet().iterator();
297: while (iter.hasNext()) {
298: Field key = (Field) iter.next();
299: for (int m = 0; m < modifierCombos.length; m++) {
300: Map map = maps[m];
301: if (map == null)
302: continue;
303: String name = key.getName().substring(3);
304: name += "."
305: + Integer.toHexString(modifierCombos[m]);
306: Integer v = (Integer) map.get(key);
307: int value = v.intValue();
308: String hex;
309: switch (value) {
310: case UNTYPED:
311: hex = "untyped";
312: break;
313: case UNDEFINED:
314: hex = "undefined";
315: break;
316: case ILLEGAL:
317: hex = "illegal";
318: break;
319: case SYSTEM:
320: hex = "system";
321: break;
322: case ERROR:
323: hex = "error";
324: break;
325: default:
326: hex = Integer.toHexString(value);
327: break;
328: }
329: props.setProperty(name, hex);
330: }
331: }
332: String[] names = getMapNames();
333: String[] desc = getMapDescriptions();
334: for (int i = 0; i < names.length; i++) {
335: String fn = getFilename(names[i]);
336: System.out.println("Saving " + names[i] + " as " + fn);
337: FileOutputStream fos = new FileOutputStream(fn);
338: props.store(fos, "Key mappings for " + desc[i]);
339: }
340: } catch (AWTException e) {
341: System.err
342: .println("Robot not available, can't generate map");
343: } catch (Exception e) {
344: System.err.println("Error: " + e);
345: }
346: }
347:
348: /** Run this to generate the full set of mappings for a given locale. */
349: public static void main(String[] args) {
350: String language = System.getProperty("abbot.locale.language");
351: if (language != null) {
352: String country = System.getProperty("abbot.locale.country",
353: "");
354: String variant = System.getProperty("abbot.locale.variant",
355: "");
356: Locale.setDefault(new Locale(language, country, variant));
357: }
358:
359: final JFrame frame = new JFrame("KeyStroke mapping generator");
360: final JTextArea text = new JTextArea();
361: // Remove all action mappings; we want to receive *all* keystrokes
362: text.setInputMap(JTextArea.WHEN_FOCUSED, new InputMap());
363: frame.getContentPane().add(new JScrollPane(text));
364: frame.setLocation(100, 100);
365: frame.setSize(250, 90);
366: frame.addWindowListener(new WindowAdapter() {
367: public void windowClosing(final WindowEvent e) {
368: SwingUtilities.invokeLater(new Runnable() {
369: public void run() {
370: e.getWindow().setVisible(true);
371: }
372: });
373: }
374: });
375: frame.setVisible(true);
376: if (Platform.isOSX()) {
377: // avoid exit on cmd-Q
378: OSXAdapter.register(frame, new AbstractAction("quit") {
379: public void actionPerformed(ActionEvent e) {
380: }
381: }, null, null);
382: }
383: SwingUtilities.invokeLater(new Runnable() {
384: public void run() {
385: new Thread("keymap generator") {
386: public void run() {
387: generateKeyStrokeMap(frame, text);
388: System.exit(0);
389: }
390: }.start();
391: }
392: });
393: }
394: }
|