/** Provides shared UI- and action-related constants. */ public interface AWTConstants { int MULTI_CLICK_INTERVAL = 250; // a guess /** Number of pixels traversed before a drag starts. */ // OSX 10(1.3.1), 5(1.4.1) // Linux/X11: delay+16 // NOTE: could maybe install a drag gesture recognizer, but that's kinda // complex for what you get out of it. int DRAG_THRESHOLD = Platform.isWindows() || Platform.isMacintosh() ? 10 : 16; int BUTTON_MASK = (InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK); int POPUP_MASK = AWT.getPopupMask(); String POPUP_MODIFIER = AWT.getMouseModifiers(POPUP_MASK); boolean POPUP_ON_PRESS = AWT.getPopupOnPress(); int TERTIARY_MASK = AWT.getTertiaryMask(); String TERTIARY_MODIFIER = AWT.getMouseModifiers(TERTIARY_MASK); int MENU_SHORTCUT_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); String MENU_SHORTCUT_MODIFIER = AWT.getKeyModifiers(MENU_SHORTCUT_MASK); String MENU_SHORTCUT_STRING = MENU_SHORTCUT_MASK == InputEvent.ALT_MASK ? "alt " : MENU_SHORTCUT_MASK == InputEvent.META_MASK ? "meta " : MENU_SHORTCUT_MASK == InputEvent.SHIFT_MASK ? "shift " : "control "; String MENU_SHORTCUT_KEYCODE = AWT.getKeyCode(AWT.maskToKeyCode(MENU_SHORTCUT_MASK)); }
/** * Handle an event. This can either be ignored or contribute to the recording. For a given event, * if no current semantic recorder is active, select one based on the event's component. If the * semantic recorder accepts the event, then it is used to consume each subsequent event, until * its recordEvent method returns true, indicating that the semantic event has completed. */ protected void recordEvent(java.awt.AWTEvent event) throws RecordingFailedException { // Discard any key/button release events at the start of the recording. if (steps.size() == 0 && event.getID() == KeyEvent.KEY_RELEASED) { Log.log("Ignoring initial release event"); return; } SemanticRecorder newRecorder = null; // Process extraneous key modifiers used to simulate mouse buttons // Only check events while we have no semantic recorder, though, // because we wish to ignore everything between the modifiers if (Platform.isMacintosh() && semanticRecorder == null) { if (pruneClickModifiers(event)) return; } if (semanticRecorder == null) { SemanticRecorder sr = (event.getSource() instanceof Component) ? getSemanticRecorder((Component) event.getSource()) // Use ComponentRecorder for MenuComponents : getSemanticRecorder(Component.class); if (sr.accept(event)) { semanticRecorder = newRecorder = sr; setStatus("Recording semantic event with " + sr); if (event.getSource() instanceof JInternalFrame) { // Ideally, adding an extra listener would be done by the // JInternalFrameRecorder, but the object needs more state // than is available to the recorder (notably to be able // to send events to the primary recorder). If something // else turns up similar to this, then the EventRecorder // should be made available to the semantic recorders. // // Must add a listener, since COMPONENT_HIDDEN is not sent // on JInternalFrame close (1.4.1). JInternalFrame f = (JInternalFrame) event.getSource(); new InternalFrameWatcher(f); } } } // If we're currently recording a semantic event, continue to do so if (semanticRecorder != null) { boolean consumed = semanticRecorder.record(event); boolean finished = semanticRecorder.isFinished(); if (finished) { Log.debug("Semantic recorder is finished"); saveSemanticEvent(); } // If not consumed, need to check for semantic recorder (again) // (but avoid recursing indefinitely) if (!consumed && newRecorder == null) { Log.debug("Event was not consumed, parse it again"); recordEvent(event); } } else { captureRawEvent(event); } }
/** Eliminate redundant key press/release events surrounding a keytyped event. */ private void coalesceKeyEvents() { setStatus("Coalescing key events"); for (int i = 0; i < steps.size(); i++) { Step step = (Step) steps.get(i); if (isKey(step, ANY_KEY, PRESS)) { // In the case of modifiers, remove only if the presence of // the key down/up is redundant. Event se = (Event) step; String cs = se.getAttribute(XMLConstants.TAG_KEYCODE); int code = AWT.getKeyCode(cs); // OSX option modifier should be ignored, since it is used to // generate input method events. boolean isOSXOption = Platform.isOSX() && code == KeyEvent.VK_ALT; if (AWT.isModifier(code) && !isOSXOption) continue; // In the case of non-modifier keys, walk the steps until we // find the key release, then optionally replace the key press // with a keystroke, or remove it if the keystroke was already // recorded. This sorts out jumbled key press/release events. boolean foundKeyStroke = false; boolean foundRelease = false; for (int j = i + 1; j < steps.size(); j++) { Step next = (Step) steps.get(j); // If we find the release, remove it and this if (isKey(next, cs, RELEASE)) { foundRelease = true; String target = ((Event) next).getComponentID(); steps.remove(j); steps.remove(i); // Add a keystroke only if we didn't find any key // input between press and release (except on OSX, // where the option key generates input method events // which aren't recorded). if (!foundKeyStroke && !isOSXOption) { String mods = se.getAttribute(XMLConstants.TAG_MODIFIERS); String[] args = (mods == null || "0".equals(mods) ? new String[] {target, cs} : new String[] {target, cs, mods}); Step typed = new Action(getResolver(), null, "actionKeyStroke", args); steps.add(i, typed); setStatus("Insert artifical " + typed); } else { setStatus("Removed redundant key events (" + cs + ")"); --i; } break; } else if (isKeyStroke(next, ANY_KEY) || isKeyString(next)) { foundKeyStroke = true; // If it's a numpad keycode, use the numpad // keycode instead of the resulting numeric character // keystroke. if (cs.startsWith("VK_NUMPAD")) { foundKeyStroke = false; steps.remove(j--); } } } // We don't like standalone key presses if (!foundRelease) { setStatus("Removed extraneous key press (" + cs + ")"); steps.remove(i--); } } } }