Completely reworked how hotkeys are handled, and now invokable actions are a real concept in the emulator!

This commit is contained in:
Brendan Robert 2015-02-18 08:41:59 -06:00
parent af8466ca54
commit a477abac03
10 changed files with 242 additions and 237 deletions

View File

@ -34,6 +34,7 @@ import java.util.Map;
public class Emulator { public class Emulator {
public static Emulator instance; public static Emulator instance;
public static EmulatorUILogic logic = new EmulatorUILogic();
public static Thread mainThread; public static Thread mainThread;
// public static void main(String... args) { // public static void main(String... args) {

View File

@ -23,6 +23,7 @@ import jace.apple2e.RAM128k;
import jace.apple2e.SoftSwitches; import jace.apple2e.SoftSwitches;
import jace.config.ConfigurationPanel; import jace.config.ConfigurationPanel;
import jace.config.InvokableAction; import jace.config.InvokableAction;
import jace.config.Reconfigurable;
import jace.core.CPU; import jace.core.CPU;
import jace.core.Debugger; import jace.core.Debugger;
import jace.core.RAM; import jace.core.RAM;
@ -63,7 +64,7 @@ import javax.swing.JTextField;
* *
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com * @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/ */
public class EmulatorUILogic { public class EmulatorUILogic implements Reconfigurable {
static Debugger debugger; static Debugger debugger;
@ -193,7 +194,8 @@ public class EmulatorUILogic {
name = "BRUN file", name = "BRUN file",
category = "file", category = "file",
description = "Loads a binary file in memory and executes it. File should end with #06xxxx, where xxxx is the start address in hex", description = "Loads a binary file in memory and executes it. File should end with #06xxxx, where xxxx is the start address in hex",
alternatives = "Execute program;Load binary;Load program;Load rom;Play single-load game") alternatives = "Execute program;Load binary;Load program;Load rom;Play single-load game",
defaultKeyMapping = "ctrl+shift+b")
public static void runFile() { public static void runFile() {
Emulator.computer.pause(); Emulator.computer.pause();
JFileChooser select = new JFileChooser(); JFileChooser select = new JFileChooser();
@ -243,7 +245,9 @@ public class EmulatorUILogic {
name = "Adjust display", name = "Adjust display",
category = "display", category = "display",
description = "Adjusts window size to 1:1 aspect ratio for optimal viewing.", description = "Adjusts window size to 1:1 aspect ratio for optimal viewing.",
alternatives = "Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;") alternatives = "Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;",
defaultKeyMapping = "ctrl+shift+a")
static public void scaleIntegerRatio() { static public void scaleIntegerRatio() {
// AbstractEmulatorFrame frame = Emulator.getFrame(); // AbstractEmulatorFrame frame = Emulator.getFrame();
// if (frame == null) { // if (frame == null) {
@ -258,7 +262,8 @@ public class EmulatorUILogic {
name = "Toggle Debug", name = "Toggle Debug",
category = "debug", category = "debug",
description = "Show/hide the debug panel", description = "Show/hide the debug panel",
alternatives = "Show Debug;Hide Debug") alternatives = "Show Debug;Hide Debug",
defaultKeyMapping = "ctrl+shift+d")
public static void toggleDebugPanel() { public static void toggleDebugPanel() {
// AbstractEmulatorFrame frame = Emulator.getFrame(); // AbstractEmulatorFrame frame = Emulator.getFrame();
// if (frame == null) { // if (frame == null) {
@ -283,7 +288,8 @@ public class EmulatorUILogic {
name = "Save Raw Screenshot", name = "Save Raw Screenshot",
category = "general", category = "general",
description = "Save raw (RAM) format of visible screen", description = "Save raw (RAM) format of visible screen",
alternatives = "screendump, raw screenshot") alternatives = "screendump, raw screenshot",
defaultKeyMapping = "ctrl+shift+z")
public static void saveScreenshotRaw() throws FileNotFoundException, IOException { public static void saveScreenshotRaw() throws FileNotFoundException, IOException {
SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
String timestamp = df.format(new Date()); String timestamp = df.format(new Date());
@ -323,7 +329,8 @@ public class EmulatorUILogic {
name = "Save Screenshot", name = "Save Screenshot",
category = "general", category = "general",
description = "Save image of visible screen", description = "Save image of visible screen",
alternatives = "Save image,save framebuffer,screenshot") alternatives = "Save image,save framebuffer,screenshot",
defaultKeyMapping = "ctrl+shift+s")
public static void saveScreenshot() throws HeadlessException, IOException { public static void saveScreenshot() throws HeadlessException, IOException {
JFileChooser select = new JFileChooser(); JFileChooser select = new JFileChooser();
Emulator.computer.pause(); Emulator.computer.pause();
@ -352,7 +359,8 @@ public class EmulatorUILogic {
name = "Configuration", name = "Configuration",
category = "general", category = "general",
description = "Edit emulator configuraion", description = "Edit emulator configuraion",
alternatives = "Reconfigure,Preferences,Settings") alternatives = "Reconfigure,Preferences,Settings",
defaultKeyMapping = {"f4","ctrl+shift+c"})
public static void showConfig() { public static void showConfig() {
// if (Emulator.getFrame().getModalDialogUI(CONFIGURATION_DIALOG_NAME) == null) { // if (Emulator.getFrame().getModalDialogUI(CONFIGURATION_DIALOG_NAME) == null) {
// JPanel ui = new ConfigurationPanel(); // JPanel ui = new ConfigurationPanel();
@ -368,7 +376,8 @@ public class EmulatorUILogic {
name = "Media Manager", name = "Media Manager",
category = "general", category = "general",
description = "Show the media manager", description = "Show the media manager",
alternatives = "Insert disk;Eject disk;Browse;Download;Select") alternatives = "Insert disk;Eject disk;Browse;Download;Select",
defaultKeyMapping = {"f1","ctrl+shift+o"})
public static void showMediaManager() { public static void showMediaManager() {
// if (Emulator.getFrame().getModalDialogUI(MEDIA_MANAGER_DIALOG_NAME) == null) { // if (Emulator.getFrame().getModalDialogUI(MEDIA_MANAGER_DIALOG_NAME) == null) {
// Emulator.getFrame().registerModalDialog(MediaLibrary.getInstance().buildUserInterface(), MEDIA_MANAGER_DIALOG_NAME, null, false); // Emulator.getFrame().registerModalDialog(MediaLibrary.getInstance().buildUserInterface(), MEDIA_MANAGER_DIALOG_NAME, null, false);
@ -380,4 +389,18 @@ public class EmulatorUILogic {
// return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Emulator.getFrame(), message); // return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Emulator.getFrame(), message);
return false; return false;
} }
}
@Override
public String getName() {
return "Jace User Interface";
}
@Override
public String getShortName() {
return "UI";
}
@Override
public void reconfigure() {
}
}

View File

@ -19,6 +19,7 @@
package jace.cheat; package jace.cheat;
import jace.apple2e.RAM128k; import jace.apple2e.RAM128k;
import jace.config.InvokableAction;
import jace.core.Computer; import jace.core.Computer;
import jace.core.KeyHandler; import jace.core.KeyHandler;
import jace.core.Keyboard; import jace.core.Keyboard;
@ -145,6 +146,7 @@ public class MetaCheats extends Cheats {
model.addRow(new Object[]{loc, val + " ("+hex(val,4)+")"}); model.addRow(new Object[]{loc, val + " ("+hex(val,4)+")"});
}); });
} }
@InvokableAction(name="Show Cheats", category = "cheats", defaultKeyMapping = "home")
public void showCheatForm() { public void showCheatForm() {
if (form == null) { if (form == null) {
form = new MetaCheatForm(); form = new MetaCheatForm();
@ -153,6 +155,7 @@ public class MetaCheats extends Cheats {
} }
MemorySpy spy = null; MemorySpy spy = null;
@InvokableAction(name="Show Memory Spy", category = "cheats", defaultKeyMapping = "end")
public void showMemorySpy() { public void showMemorySpy() {
if (spy == null) { if (spy == null) {
spy = new MemorySpy(); spy = new MemorySpy();
@ -199,32 +202,7 @@ public class MetaCheats extends Cheats {
t.start(); t.start();
} }
} }
}); });
Keyboard.registerKeyHandler(new KeyHandler(KeyCode.END) {
@Override
public boolean handleKeyUp(KeyEvent e) {
showCheatForm();
return false;
}
@Override
public boolean handleKeyDown(KeyEvent e) {
return false;
}
}, this);
Keyboard.registerKeyHandler(new KeyHandler(KeyCode.HOME) {
@Override
public boolean handleKeyUp(KeyEvent e) {
showMemorySpy();
return false;
}
@Override
public boolean handleKeyDown(KeyEvent e) {
return false;
}
}, this);
} }
@Override @Override

View File

@ -19,7 +19,9 @@
package jace.config; package jace.config;
import jace.Emulator; import jace.Emulator;
import jace.EmulatorUILogic;
import jace.core.Computer; import jace.core.Computer;
import jace.core.Keyboard;
import jace.core.Utility; import jace.core.Utility;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -31,13 +33,14 @@ import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -56,6 +59,15 @@ import javax.swing.tree.TreePath;
*/ */
public class Configuration implements Reconfigurable { public class Configuration implements Reconfigurable {
private static Method findAnyMethodByName(Class<? extends Reconfigurable> aClass, String m) {
for (Method method : aClass.getMethods()) {
if (method.getName().equals(m)) {
return method;
}
}
return null;
}
public String getName() { public String getName() {
return "Configuration"; return "Configuration";
} }
@ -136,8 +148,9 @@ public class Configuration implements Reconfigurable {
public transient ConfigNode parent; public transient ConfigNode parent;
public transient Reconfigurable subject; public transient Reconfigurable subject;
private Map<String, Serializable> settings; private Map<String, Serializable> settings;
private Map<String, String[]> hotkeys;
protected Map<String, ConfigNode> children; protected Map<String, ConfigNode> children;
private boolean changed = false; private boolean changed = true;
@Override @Override
public String toString() { public String toString() {
@ -155,6 +168,7 @@ public class Configuration implements Reconfigurable {
public ConfigNode(ConfigNode parent, Reconfigurable subject) { public ConfigNode(ConfigNode parent, Reconfigurable subject) {
this.subject = subject; this.subject = subject;
this.settings = new TreeMap<>(); this.settings = new TreeMap<>();
this.hotkeys = new TreeMap<>();
this.children = new TreeMap<>(); this.children = new TreeMap<>();
this.parent = parent; this.parent = parent;
if (this.parent != null) { if (this.parent != null) {
@ -189,6 +203,7 @@ public class Configuration implements Reconfigurable {
} }
} }
public final static ConfigNode BASE; public final static ConfigNode BASE;
public static EmulatorUILogic ui = Emulator.logic;
public static Computer emulator = Emulator.computer; public static Computer emulator = Emulator.computer;
@ConfigurableField(name = "Autosave Changes", description = "If unchecked, changes are only saved when the Save button is pressed.") @ConfigurableField(name = "Autosave Changes", description = "If unchecked, changes are only saved when the Save button is pressed.")
public static boolean saveAutomatically = false; public static boolean saveAutomatically = false;
@ -199,13 +214,20 @@ public class Configuration implements Reconfigurable {
} }
public static void buildTree() { public static void buildTree() {
buildTree(BASE, new HashSet()); buildTree(BASE, new LinkedHashSet());
} }
private static void buildTree(ConfigNode node, Set visited) { private static void buildTree(ConfigNode node, Set visited) {
if (node.subject == null) { if (node.subject == null) {
return; return;
} }
for (Method m : node.subject.getClass().getMethods()) {
InvokableAction action = m.getDeclaredAnnotation(InvokableAction.class);
if (action == null) continue;
node.hotkeys.put(m.getName(), action.defaultKeyMapping());
}
for (Field f : node.subject.getClass().getFields()) { for (Field f : node.subject.getClass().getFields()) {
// System.out.println("Evaluating field " + f.getName()); // System.out.println("Evaluating field " + f.getName());
try { try {
@ -213,6 +235,7 @@ public class Configuration implements Reconfigurable {
if (/*o == null ||*/visited.contains(o)) { if (/*o == null ||*/visited.contains(o)) {
continue; continue;
} }
visited.add(o);
// System.out.println(o.getClass().getName()); // System.out.println(o.getClass().getName());
// If the object in question is not reconfigurable, // If the object in question is not reconfigurable,
// skip over it and investigate its fields instead // skip over it and investigate its fields instead
@ -232,7 +255,6 @@ public class Configuration implements Reconfigurable {
if (o instanceof Reconfigurable) { if (o instanceof Reconfigurable) {
Reconfigurable r = (Reconfigurable) o; Reconfigurable r = (Reconfigurable) o;
visited.add(r);
ConfigNode child = node.children.get(f.getName()); ConfigNode child = node.children.get(f.getName());
if (child == null || !child.subject.equals(o)) { if (child == null || !child.subject.equals(o)) {
child = new ConfigNode(node, r); child = new ConfigNode(node, r);
@ -275,7 +297,6 @@ public class Configuration implements Reconfigurable {
} else { } else {
children = Arrays.asList((Reconfigurable[]) o); children = Arrays.asList((Reconfigurable[]) o);
} }
visited.add(o);
for (int i = 0; i < children.size(); i++) { for (int i = 0; i < children.size(); i++) {
Reconfigurable child = children.get(i); Reconfigurable child = children.get(i);
String childName = fieldName + i; String childName = fieldName + i;
@ -301,7 +322,8 @@ public class Configuration implements Reconfigurable {
name = "Save settings", name = "Save settings",
description = "Save all configuration settings as defaults", description = "Save all configuration settings as defaults",
category = "general", category = "general",
alternatives = "save preferences;save defaults" alternatives = "save preferences;save defaults",
defaultKeyMapping = "meta+ctrl+s"
) )
public static void saveSettings() { public static void saveSettings() {
FileOutputStream fos = null; FileOutputStream fos = null;
@ -331,16 +353,19 @@ public class Configuration implements Reconfigurable {
name = "Load settings", name = "Load settings",
description = "Load all configuration settings previously saved", description = "Load all configuration settings previously saved",
category = "general", category = "general",
alternatives = "load preferences;revert settings;revert preferences" alternatives = "load preferences;revert settings;revert preferences",
defaultKeyMapping = "meta+ctrl+r"
) )
public static void loadSettings() { public static void loadSettings() {
{ {
boolean successful = false;
ObjectInputStream ois = null; ObjectInputStream ois = null;
FileInputStream fis = null; FileInputStream fis = null;
try { try {
ois = new ObjectInputStream(new FileInputStream(getSettingsFile())); ois = new ObjectInputStream(new FileInputStream(getSettingsFile()));
ConfigNode newRoot = (ConfigNode) ois.readObject(); ConfigNode newRoot = (ConfigNode) ois.readObject();
applyConfigTree(newRoot, BASE); applyConfigTree(newRoot, BASE);
successful = true;
} catch (ClassNotFoundException ex) { } catch (ClassNotFoundException ex) {
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
} catch (FileNotFoundException ex) { } catch (FileNotFoundException ex) {
@ -353,6 +378,9 @@ public class Configuration implements Reconfigurable {
if (ois != null) { if (ois != null) {
ois.close(); ois.close();
} }
if (!successful) {
applySettings(BASE);
}
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
} }
@ -409,6 +437,7 @@ public class Configuration implements Reconfigurable {
return; return;
} }
oldRoot.settings = newRoot.settings; oldRoot.settings = newRoot.settings;
oldRoot.hotkeys = newRoot.hotkeys;
if (oldRoot.subject != null) { if (oldRoot.subject != null) {
doApply(oldRoot); doApply(oldRoot);
buildTree(oldRoot, new HashSet()); buildTree(oldRoot, new HashSet());
@ -421,6 +450,17 @@ public class Configuration implements Reconfigurable {
private static void doApply(ConfigNode node) { private static void doApply(ConfigNode node) {
List<String> removeList = new ArrayList<>(); List<String> removeList = new ArrayList<>();
Keyboard.unregisterAllHandlers(node.subject);
for (String m : node.hotkeys.keySet()) {
Method method = findAnyMethodByName(node.subject.getClass(), m);
if (method == null) continue;
InvokableAction action = method.getAnnotation(InvokableAction.class);
if (action == null) continue;
for (String code : node.hotkeys.get(m)) {
Keyboard.registerInvokableAction(action, node.subject, method, code);
}
}
for (String f : node.settings.keySet()) { for (String f : node.settings.keySet()) {
try { try {
Field ff = node.subject.getClass().getField(f); Field ff = node.subject.getClass().getField(f);

View File

@ -69,4 +69,19 @@ public @interface InvokableAction {
* reboot might have alternatives as "warm start;cold start;boot;restart". * reboot might have alternatives as "warm start;cold start;boot;restart".
*/ */
public String alternatives() default ""; public String alternatives() default "";
/*
* If true, the key event will be consumed and not processed by any other event handlers
* If the corresponding method returns a boolean, that value will be used instead.
* True = consume (stop processing keystroke), false = pass-through to other handlers
*/
public boolean consumeKeyEvent() default true;
/*
* If false (default) event is only triggered on press, not release. If true,
* method is notified on press and on release
*/
public boolean notifyOnRelease() default false;
/*
* Standard keyboard mapping
*/
public String[] defaultKeyMapping();
} }

View File

@ -114,14 +114,23 @@ public abstract class Computer implements Reconfigurable {
name = "Cold boot", name = "Cold boot",
description = "Process startup sequence from power-up", description = "Process startup sequence from power-up",
category = "general", category = "general",
alternatives = "Full reset;reset emulator") alternatives = "Full reset;reset emulator",
consumeKeyEvent = true,
defaultKeyMapping = {"Ctrl+Shift+Backspace","Ctrl+Shift+Delete"})
public void invokeColdStart() {
coldStart();
}
public abstract void coldStart(); public abstract void coldStart();
@InvokableAction( @InvokableAction(
name = "Warm boot", name = "Warm boot",
description = "Process user-initatiated reboot (ctrl+apple+reset)", description = "Process user-initatiated reboot (ctrl+apple+reset)",
category = "general", category = "general",
alternatives = "reboot;reset;three-finger-salute") alternatives = "reboot;reset;three-finger-salute",
defaultKeyMapping = {"Ctrl+Ignore Alt+Ignore Meta+Backspace","Ctrl+Ignore Alt+Ignore Meta+Delete"})
public void invokeWarmStart() {
warmStart();
}
public abstract void warmStart(); public abstract void warmStart();
public Keyboard getKeyboard() { public Keyboard getKeyboard() {
@ -134,14 +143,14 @@ public abstract class Computer implements Reconfigurable {
protected abstract void doResume(); protected abstract void doResume();
@InvokableAction(name = "Pause", description = "Stops the computer, allowing reconfiguration of core elements", alternatives = "freeze;halt") @InvokableAction(name = "Pause", description = "Stops the computer, allowing reconfiguration of core elements", alternatives = "freeze;halt", defaultKeyMapping = {"meta+pause","alt+pause"})
public boolean pause() { public boolean pause() {
boolean result = isRunning(); boolean result = isRunning();
doPause(); doPause();
return result; return result;
} }
@InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume") @InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume", defaultKeyMapping = {"meta+shift+pause","alt+shift+pause"})
public void resume() { public void resume() {
doResume(); doResume();
} }

View File

@ -18,8 +18,9 @@
*/ */
package jace.core; package jace.core;
import java.util.EnumMap;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
/** /**
@ -33,45 +34,54 @@ import javafx.scene.input.KeyEvent;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com * @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/ */
public abstract class KeyHandler { public abstract class KeyHandler {
public KeyCombination keyCodeCombination;
public static enum Modifiers {
alt, control, shift, meta, shortcut,ignore
};
public KeyCode key; public KeyCode key;
public EnumMap<Modifiers, Boolean> modifiers; public KeyHandler(String comboText) {
KeyCode testCode = KeyCode.getKeyCode(comboText);
public KeyHandler(KeyCode key, Modifiers... flags) { if (testCode != null) {
this.key = key; key = testCode;
this.modifiers = new EnumMap<>(Modifiers.class); } else {
for (Modifiers flag : flags) { init((KeyCodeCombination) KeyCodeCombination.valueOf(comboText));
modifiers.put(flag, true);
} }
} }
public boolean matchesModifiers(KeyEvent e) { public KeyHandler(KeyCodeCombination keyCodeCombo) {
if (modifiers.get(Modifiers.ignore)) { init(keyCodeCombo);
return true; }
private void init(KeyCombination keyCodeCombo) {
keyCodeCombination = keyCodeCombo;
if (keyCodeCombo instanceof KeyCodeCombination) {
key = ((KeyCodeCombination) keyCodeCombo).getCode();
} }
if (e.isAltDown() != modifiers.get(Modifiers.alt)) { }
return false;
public boolean match(KeyEvent e) {
if (keyCodeCombination != null) {
return keyCodeCombination.match(e);
} else {
return e.getCode().equals(key);
} }
if (e.isControlDown() != modifiers.get(Modifiers.control)) {
return false;
}
if (e.isMetaDown() != modifiers.get(Modifiers.meta)) {
return false;
}
if (e.isShiftDown() != modifiers.get(Modifiers.shift)) {
return false;
}
if (e.isShortcutDown() != modifiers.get(Modifiers.shortcut)) {
return false;
}
return true;
} }
public abstract boolean handleKeyUp(KeyEvent e); public abstract boolean handleKeyUp(KeyEvent e);
public abstract boolean handleKeyDown(KeyEvent e); public abstract boolean handleKeyDown(KeyEvent e);
public String getComboName() {
if (keyCodeCombination != null) {
return keyCodeCombination.getName();
} else {
return key.getName();
}
}
public String getKeyName() {
if (key != null) {
return key.getName();
} else {
return null;
}
}
} }

View File

@ -22,6 +22,7 @@ import jace.EmulatorUILogic;
import jace.apple2e.SoftSwitches; import jace.apple2e.SoftSwitches;
import jace.apple2e.Speaker; import jace.apple2e.Speaker;
import jace.apple2e.softswitch.KeyboardSoftSwitch; import jace.apple2e.softswitch.KeyboardSoftSwitch;
import jace.config.InvokableAction;
import jace.config.Reconfigurable; import jace.config.Reconfigurable;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.Clipboard;
@ -29,6 +30,8 @@ import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -38,6 +41,7 @@ import java.util.logging.Logger;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import jdk.nashorn.internal.codegen.types.Type;
/** /**
* Keyboard manages all keyboard-related activities. For now, all hotkeys are * Keyboard manages all keyboard-related activities. For now, all hotkeys are
@ -89,6 +93,51 @@ public class Keyboard implements Reconfigurable {
private static Map<KeyCode, Set<KeyHandler>> keyHandlersByKey = new HashMap<>(); private static Map<KeyCode, Set<KeyHandler>> keyHandlersByKey = new HashMap<>();
private static Map<Object, Set<KeyHandler>> keyHandlersByOwner = new HashMap<>(); private static Map<Object, Set<KeyHandler>> keyHandlersByOwner = new HashMap<>();
public static void registerInvokableAction(InvokableAction action, Object owner, Method method, String code) {
registerKeyHandler(new KeyHandler(code) {
@Override
public boolean handleKeyUp(KeyEvent e) {
if (!action.notifyOnRelease()) {
return false;
}
System.out.println("Key up: "+method.toString());
Object returnValue = null;
try {
if (method.getParameterCount() > 0) {
returnValue = method.invoke(owner, false);
} else {
returnValue = method.invoke(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(owner, true);
} else {
returnValue = method.invoke(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();
}
}, owner);
}
public static void registerKeyHandler(KeyHandler l, Object owner) { public static void registerKeyHandler(KeyHandler l, Object owner) {
if (!keyHandlersByKey.containsKey(l.key)) { if (!keyHandlersByKey.containsKey(l.key)) {
keyHandlersByKey.put(l.key, new HashSet<>()); keyHandlersByKey.put(l.key, new HashSet<>());
@ -98,6 +147,7 @@ public class Keyboard implements Reconfigurable {
keyHandlersByOwner.put(owner, new HashSet<>()); keyHandlersByOwner.put(owner, new HashSet<>());
} }
keyHandlersByOwner.get(owner).add(l); keyHandlersByOwner.get(owner).add(l);
System.out.println("Registered handler for "+l.getComboName()+"; code is "+l.getKeyName());
} }
public static void unregisterAllHandlers(Object owner) { public static void unregisterAllHandlers(Object owner) {
@ -107,12 +157,13 @@ public class Keyboard implements Reconfigurable {
keyHandlersByOwner.get(owner).stream().filter((handler) -> !(!keyHandlersByKey.containsKey(handler.key))).forEach((handler) -> { keyHandlersByOwner.get(owner).stream().filter((handler) -> !(!keyHandlersByKey.containsKey(handler.key))).forEach((handler) -> {
keyHandlersByKey.get(handler.key).remove(handler); keyHandlersByKey.get(handler.key).remove(handler);
}); });
keyHandlersByOwner.remove(owner);
} }
public static void processKeyDownEvents(KeyEvent e) { public static void processKeyDownEvents(KeyEvent e) {
if (keyHandlersByKey.containsKey(e.getCode())) { if (keyHandlersByKey.containsKey(e.getCode())) {
for (KeyHandler h : keyHandlersByKey.get(e.getCode())) { for (KeyHandler h : keyHandlersByKey.get(e.getCode())) {
if (!h.matchesModifiers(e)) { if (!h.match(e)) {
continue; continue;
} }
boolean isHandled = h.handleKeyDown(e); boolean isHandled = h.handleKeyDown(e);
@ -127,7 +178,7 @@ public class Keyboard implements Reconfigurable {
public static void processKeyUpEvents(KeyEvent e) { public static void processKeyUpEvents(KeyEvent e) {
if (keyHandlersByKey.containsKey(e.getCode())) { if (keyHandlersByKey.containsKey(e.getCode())) {
for (KeyHandler h : keyHandlersByKey.get(e.getCode())) { for (KeyHandler h : keyHandlersByKey.get(e.getCode())) {
if (!h.matchesModifiers(e)) { if (!h.match(e)) {
continue; continue;
} }
boolean isHandled = h.handleKeyUp(e); boolean isHandled = h.handleKeyUp(e);
@ -155,11 +206,11 @@ public class Keyboard implements Reconfigurable {
return; return;
} }
char c=255; char c = 255;
if (e.getText().length() > 0) { if (e.getText().length() > 0) {
c = e.getText().charAt(0); c = e.getText().charAt(0);
} }
switch (e.getCode()) { switch (e.getCode()) {
case LEFT: case LEFT:
case KP_LEFT: case KP_LEFT:
@ -189,21 +240,12 @@ public class Keyboard implements Reconfigurable {
case BACK_SPACE: case BACK_SPACE:
c = 127; c = 127;
break; break;
case ALT:
pressOpenApple();
break;
case META:
case COMMAND:
pressSolidApple();
break;
default: default:
} }
if (c < 128) { if (c < 128) {
pressKey((byte) c); pressKey((byte) c);
} }
// e.consume();
} }
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {
@ -212,97 +254,21 @@ public class Keyboard implements Reconfigurable {
if (code == null || e.isConsumed()) { if (code == null || e.isConsumed()) {
return; return;
} }
switch (code) {
case INSERT:
if (e.isShiftDown()) {
doPaste();
}
break;
case F10:
EmulatorUILogic.toggleDebugPanel();
break;
case F12:
case PAGE_UP:
case BACK_SPACE:
case PAUSE:
if (e.isControlDown()) {
computer.warmStart();
}
break;
case F1:
EmulatorUILogic.showMediaManager();
break;
case F4:
EmulatorUILogic.showConfig();
break;
case F7:
Speaker.toggleFileOutput();
break;
case F8:
EmulatorUILogic.scaleIntegerRatio();
break;
case F9:
EmulatorUILogic.toggleFullscreen();
break;
case PRINTSCREEN:
case SCROLL_LOCK:
try {
if (e.isShiftDown()) {
EmulatorUILogic.saveScreenshotRaw();
} else {
EmulatorUILogic.saveScreenshot();
}
} catch (IOException ex) {
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
}
computer.resume();
break;
case ALT:
releaseOpenApple();
break;
case META:
case COMMAND:
releaseSolidApple();
break;
}
// if ((e.getModifiers() & (KeyEvent.ALT_MASK | KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK)) > 0) {
// // explicit left and right here because other locations
// // can be sent as well, e.g. KEY_LOCATION_STANDARD
// if (e.getKeyLocation() == KeyEvent.KEY_LOCATION_LEFT) {
// releaseOpenApple();
// } else if (e.getKeyLocation() == KeyEvent.KEY_LOCATION_RIGHT) {
// releaseSolidApple();
// }
// }
e.consume(); e.consume();
// e.setKeyChar((char) 0);
// e.setKeyCode(0);
} }
private void pressOpenApple() { @InvokableAction(name = "Open Apple Key", alternatives = "OA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = "Alt", consumeKeyEvent = false)
public void openApple(boolean pressed) {
computer.pause(); computer.pause();
SoftSwitches.PB0.getSwitch().setState(true); SoftSwitches.PB0.getSwitch().setState(pressed);
computer.resume(); computer.resume();
} }
private void pressSolidApple() { @InvokableAction(name = "Closed Apple Key", alternatives = "CA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = "Meta", consumeKeyEvent = false)
public void solidApple(boolean pressed) {
computer.pause(); computer.pause();
SoftSwitches.PB1.getSwitch().setState(true); SoftSwitches.PB1.getSwitch().setState(pressed);
computer.resume();
}
private void releaseOpenApple() {
computer.pause();
SoftSwitches.PB0.getSwitch().setState(false);
computer.resume();
}
private void releaseSolidApple() {
computer.pause();
SoftSwitches.PB1.getSwitch().setState(false);
computer.resume(); computer.resume();
} }
@ -310,7 +276,8 @@ public class Keyboard implements Reconfigurable {
pasteBuffer = new StringReader(text); pasteBuffer = new StringReader(text);
} }
private static void doPaste() { @InvokableAction(name = "Paste clipboard", alternatives = "paste", category = "Keyboard", notifyOnRelease = false, defaultKeyMapping = {"Ctrl+Shift+V","Shift+Insert"}, consumeKeyEvent = false)
public static void doPaste() {
try { try {
Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
String contents = (String) clip.getData(DataFlavor.stringFlavor); String contents = (String) clip.getData(DataFlavor.stringFlavor);
@ -340,8 +307,6 @@ public class Keyboard implements Reconfigurable {
return -1; return -1;
} }
KeyboardSoftSwitch key
= (KeyboardSoftSwitch) SoftSwitches.KEYBOARD.getSwitch();
return (keypress & 0x0ff); return (keypress & 0x0ff);
} catch (IOException ex) { } catch (IOException ex) {

View File

@ -19,7 +19,6 @@
package jace.core; package jace.core;
import jace.state.Stateful; import jace.state.Stateful;
import jace.Emulator;
import jace.config.ConfigurableField; import jace.config.ConfigurableField;
import jace.config.InvokableAction; import jace.config.InvokableAction;
import javafx.scene.image.Image; import javafx.scene.image.Image;
@ -274,7 +273,8 @@ public abstract class Video extends Device {
@InvokableAction(name = "Refresh screen", @InvokableAction(name = "Refresh screen",
category = "display", category = "display",
description = "Marks screen contents as changed, forcing full screen redraw", description = "Marks screen contents as changed, forcing full screen redraw",
alternatives = "redraw") alternatives = "redraw",
defaultKeyMapping = "ctrl+shift+r")
public final void forceRefresh() { public final void forceRefresh() {
lineDirty = true; lineDirty = true;
screenDirty = true; screenDirty = true;

View File

@ -21,9 +21,9 @@ package jace.hardware;
import jace.apple2e.SoftSwitches; import jace.apple2e.SoftSwitches;
import jace.apple2e.softswitch.MemorySoftSwitch; import jace.apple2e.softswitch.MemorySoftSwitch;
import jace.config.ConfigurableField; import jace.config.ConfigurableField;
import jace.config.InvokableAction;
import jace.core.Computer; import jace.core.Computer;
import jace.core.Device; import jace.core.Device;
import jace.core.KeyHandler;
import jace.core.Keyboard; import jace.core.Keyboard;
import jace.core.RAMEvent; import jace.core.RAMEvent;
import jace.core.RAMListener; import jace.core.RAMListener;
@ -36,8 +36,6 @@ import java.awt.Robot;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
/** /**
* Simple implementation of joystick support that supports mouse or keyboard. * Simple implementation of joystick support that supports mouse or keyboard.
@ -216,65 +214,31 @@ public class Joystick extends Device {
} }
}; };
@InvokableAction(name="Left", category = "joystick", defaultKeyMapping = "left", notifyOnRelease = true)
public boolean joystickLeft(boolean pressed) {
leftPressed = pressed;
return hogKeyboard;
};
@InvokableAction(name="Right", category = "joystick", defaultKeyMapping = "right", notifyOnRelease = true)
public boolean joystickRight(boolean pressed) {
rightPressed = pressed;
return hogKeyboard;
};
@InvokableAction(name="Up", category = "joystick", defaultKeyMapping = "up", notifyOnRelease = true)
public boolean joystickUp(boolean pressed) {
upPressed = pressed;
return hogKeyboard;
};
@InvokableAction(name="Down", category = "joystick", defaultKeyMapping = "down", notifyOnRelease = true)
public boolean joystickDown(boolean pressed) {
leftPressed = pressed;
return hogKeyboard;
};
private void registerListeners() { private void registerListeners() {
computer.getMemory().addListener(listener); computer.getMemory().addListener(listener);
if (useKeyboard) {
System.out.println("Registering key handlers");
Keyboard.registerKeyHandler(new KeyHandler(KeyCode.LEFT, KeyHandler.Modifiers.ignore) {
@Override
public boolean handleKeyUp(KeyEvent e) {
leftPressed = false;
return hogKeyboard;
}
@Override
public boolean handleKeyDown(KeyEvent e) {
leftPressed = true;
return hogKeyboard;
}
}, this);
Keyboard.registerKeyHandler(new KeyHandler(KeyCode.RIGHT, KeyHandler.Modifiers.ignore) {
@Override
public boolean handleKeyUp(KeyEvent e) {
rightPressed = false;
return hogKeyboard;
}
@Override
public boolean handleKeyDown(KeyEvent e) {
rightPressed = true;
return hogKeyboard;
}
}, this);
Keyboard.registerKeyHandler(new KeyHandler(KeyCode.UP, KeyHandler.Modifiers.ignore) {
@Override
public boolean handleKeyUp(KeyEvent e) {
upPressed = false;
return hogKeyboard;
}
@Override
public boolean handleKeyDown(KeyEvent e) {
upPressed = true;
return hogKeyboard;
}
}, this);
Keyboard.registerKeyHandler(new KeyHandler(KeyCode.DOWN, KeyHandler.Modifiers.ignore) {
@Override
public boolean handleKeyUp(KeyEvent e) {
downPressed = false;
return hogKeyboard;
}
@Override
public boolean handleKeyDown(KeyEvent e) {
downPressed = true;
return hogKeyboard;
}
}, this);
}
} }
private void removeListeners() { private void removeListeners() {
computer.getMemory().removeListener(listener); computer.getMemory().removeListener(listener);
Keyboard.unregisterAllHandlers(this); Keyboard.unregisterAllHandlers(this);