/* * Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package jace.core; import jace.apple2e.SoftSwitches; import jace.config.InvokableAction; import jace.config.Reconfigurable; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javafx.event.EventHandler; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.stage.WindowEvent; /** * Keyboard manages all keyboard-related activities. For now, all hotkeys are * hard-coded. The eventual direction for this class is to only manage key * handlers for all keys and provide remapping -- but it's not there yet. * Created on March 29, 2007, 11:32 PM * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class Keyboard implements Reconfigurable { public void resetState() { clearStrobe(); openApple(false); solidApple(false); } private Computer computer; public Keyboard(Computer computer) { this.computer = computer; } @Override public String getShortName() { return "kbd"; } static byte currentKey = 0; public static void clearStrobe() { currentKey = (byte) (currentKey & 0x07f); } public static void pressKey(byte key) { currentKey = (byte) (0x0ff & (0x080 | key)); } public static byte readState() { // If strobe was cleared... if ((currentKey & 0x080) == 0) { // Call clipboard buffer paste routine int newKey = Keyboard.getClipboardKeystroke(); if (newKey >= 0) { pressKey((byte) newKey); } } return currentKey; } /** * Creates a new instance of Keyboard */ public Keyboard() { } private static Map<KeyCode, Set<KeyHandler>> keyHandlersByKey = new HashMap<>(); private static Map<Object, Set<KeyHandler>> keyHandlersByOwner = new HashMap<>(); public static void registerInvokableAction(InvokableAction action, Object owner, Method method, String code) { boolean isStatic = Modifier.isStatic(method.getModifiers()); registerKeyHandler(new KeyHandler(code) { @Override public boolean handleKeyUp(KeyEvent e) { if (action == null || !action.notifyOnRelease()) { return false; } // System.out.println("Key up: "+method.toString()); Object returnValue = null; try { if (method.getParameterCount() > 0) { returnValue = method.invoke(isStatic ? null : owner, false); } else { returnValue = method.invoke(isStatic ? null : owner); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex); } if (returnValue != null) { return (Boolean) returnValue; } return action.consumeKeyEvent(); } @Override public boolean handleKeyDown(KeyEvent e) { // System.out.println("Key down: "+method.toString()); Object returnValue = null; try { if (method.getParameterCount() > 0) { returnValue = method.invoke(isStatic ? null : owner, true); } else { returnValue = method.invoke(isStatic ? null : owner); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex); } if (returnValue != null) { return (Boolean) returnValue; } return action != null ? action.consumeKeyEvent() : null; } }, owner); } public static void registerKeyHandler(KeyHandler l, Object owner) { if (!keyHandlersByKey.containsKey(l.key)) { keyHandlersByKey.put(l.key, new HashSet<>()); } keyHandlersByKey.get(l.key).add(l); if (!keyHandlersByOwner.containsKey(owner)) { keyHandlersByOwner.put(owner, new HashSet<>()); } keyHandlersByOwner.get(owner).add(l); // System.out.println("Registered handler for "+l.getComboName()+"; code is "+l.getKeyName()); } public static void unregisterAllHandlers(Object owner) { if (!keyHandlersByOwner.containsKey(owner)) { return; } keyHandlersByOwner.get(owner).stream().filter((handler) -> !(!keyHandlersByKey.containsKey(handler.key))).forEach((handler) -> { keyHandlersByKey.get(handler.key).remove(handler); }); keyHandlersByOwner.remove(owner); } public static void processKeyDownEvents(KeyEvent e) { if (keyHandlersByKey.containsKey(e.getCode())) { for (KeyHandler h : keyHandlersByKey.get(e.getCode())) { if (!h.match(e)) { continue; } boolean isHandled = h.handleKeyDown(e); if (isHandled) { e.consume(); return; } } } } public static void processKeyUpEvents(KeyEvent e) { if (keyHandlersByKey.containsKey(e.getCode())) { for (KeyHandler h : keyHandlersByKey.get(e.getCode())) { if (!h.match(e)) { continue; } boolean isHandled = h.handleKeyUp(e); if (isHandled) { e.consume(); return; } } } } public EventHandler<KeyEvent> getListener() { return (KeyEvent event) -> { if (event.getEventType() == KeyEvent.KEY_PRESSED) { keyPressed(event); } else if (event.getEventType() == KeyEvent.KEY_RELEASED) { keyReleased(event); } }; } public void keyPressed(KeyEvent e) { processKeyDownEvents(e); if (e.isConsumed()) { return; } char c = 255; if (e.getText().length() > 0) { c = e.getText().charAt(0); } switch (e.getCode()) { case LEFT: case KP_LEFT: c = 8; break; case RIGHT: case KP_RIGHT: c = 21; break; case UP: case KP_UP: c = 11; break; case DOWN: case KP_DOWN: c = 10; break; case ESCAPE: c = 27; break; case TAB: c = 9; break; case ENTER: c = 13; break; case BACK_SPACE: c = 127; break; default: } if (e.isShiftDown()) { c = fixShiftedChar(c); } if (e.isControlDown()) { if (c == 255) { return; } c = (char) (c & 0x01f); } if (c < 128) { pressKey((byte) c); } } private char fixShiftedChar(char c) { if (c >= 'a' && c <= 'z') { return (char) (c - 32); } else { switch (c) { case '0': return ')'; case '1': return '!'; case '2': return '@'; case '3': return '#'; case '4': return '$'; case '5': return '%'; case '6': return '^'; case '7': return '&'; case '8': return '*'; case '9': return '('; case '-': return '_'; case '=': return '+'; case '[': return '{'; case ']': return '}'; case '\\': return '|'; case ';': return ':'; case '\'': return '"'; case ',': return '<'; case '.': return '>'; case '/': return '?'; case '`': return '~'; } } return c; } public void keyReleased(KeyEvent e) { KeyCode code = e.getCode(); processKeyUpEvents(e); if (code == null || e.isConsumed()) { return; } e.consume(); } @InvokableAction(name = "Open Apple Key", alternatives = "OA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = "Alt", consumeKeyEvent = false) public void openApple(boolean pressed) { computer.pause(); SoftSwitches.PB0.getSwitch().setState(pressed); computer.resume(); } @InvokableAction(name = "Closed Apple Key", alternatives = "CA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = {"Shortcut","Meta","Command"}, consumeKeyEvent = false) public void solidApple(boolean pressed) { computer.pause(); SoftSwitches.PB1.getSwitch().setState(pressed); computer.resume(); } public static void pasteFromString(String text) { text = text.replaceAll("\\r?\\n|\\r", (char) 0x0d + ""); pasteBuffer = new StringReader(text); } @InvokableAction(name = "Paste clipboard", alternatives = "paste", category = "Keyboard", notifyOnRelease = false, defaultKeyMapping = {"Ctrl+Shift+V","Shift+Insert"}, consumeKeyEvent = true) public static void pasteFromClipboard() { try { Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); String contents = (String) clip.getData(DataFlavor.stringFlavor); if (contents != null && !"".equals(contents)) { contents = contents.replaceAll("\\r?\\n|\\r", (char) 0x0d + ""); pasteBuffer = new StringReader(contents); } } catch (UnsupportedFlavorException | IOException ex) { Logger.getLogger(Keyboard.class .getName()).log(Level.SEVERE, null, ex); } } static StringReader pasteBuffer = null; public static int getClipboardKeystroke() { if (pasteBuffer == null) { return -1; } try { int keypress = pasteBuffer.read(); // Handle end of paste buffer if (keypress == -1) { pasteBuffer.close(); pasteBuffer = null; return -1; } return (keypress & 0x0ff); } catch (IOException ex) { Logger.getLogger(Keyboard.class .getName()).log(Level.SEVERE, null, ex); } return -1; } @Override public String getName() { return "Keyboard"; } @Override public void reconfigure() { } }