diff --git a/application/src/main/java/net/emustudio/application/cmdline/Runner.java b/application/src/main/java/net/emustudio/application/cmdline/Runner.java index 53bdbf16a..f9861854c 100644 --- a/application/src/main/java/net/emustudio/application/cmdline/Runner.java +++ b/application/src/main/java/net/emustudio/application/cmdline/Runner.java @@ -38,10 +38,13 @@ public class Runner implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(Runner.class); // if a command is being run, default behavior is: do nothing private static boolean runsSomeCommand; + @CommandLine.ArgGroup(heading = "Virtual computer%n") public Exclusive exclusive; + @CommandLine.Option(names = {"-i", "--input-file"}, description = "input file name (source code)", paramLabel = "FILE") public Path inputFile; + @CommandLine.Option(names = {"-cl", "--computers-list"}, description = "list all existing virtual computers") private boolean listConfigs; diff --git a/application/src/main/java/net/emustudio/application/gui/dialogs/EditorPanel.java b/application/src/main/java/net/emustudio/application/gui/dialogs/EditorPanel.java index 37200b984..d0a641bb4 100644 --- a/application/src/main/java/net/emustudio/application/gui/dialogs/EditorPanel.java +++ b/application/src/main/java/net/emustudio/application/gui/dialogs/EditorPanel.java @@ -2,7 +2,6 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.application.gui.dialogs; -import net.emustudio.application.Constants; import net.emustudio.application.gui.actions.CompileAction; import net.emustudio.application.gui.actions.editor.*; import net.emustudio.application.gui.editor.Editor; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/Assembler8080.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/Assembler8080.java index 709497b8b..d9362cc3e 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/Assembler8080.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/Assembler8080.java @@ -27,6 +27,8 @@ import java.io.FileReader; import java.io.IOException; import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.MissingResourceException; @@ -95,7 +97,7 @@ public void compile(Path inputPath, Optional outputPath) { notifyInfo(getTitle() + ", version " + getVersion()); Path finalOutputPath = outputPath.orElse(convertInputToOutputPath(inputPath, ".hex")); - try (Reader reader = new FileReader(inputPath.toFile())) { + try (Reader reader = Files.newBufferedReader(inputPath, StandardCharsets.UTF_8)) { org.antlr.v4.runtime.Lexer lexer = createLexer(CharStreams.fromReader(reader)); lexer.addErrorListener(new ParserErrorListener(inputPath.toString())); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/SSEMCompiler.java b/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/SSEMCompiler.java index 92f3e4465..673f384c0 100644 --- a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/SSEMCompiler.java +++ b/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/SSEMCompiler.java @@ -26,6 +26,8 @@ import java.io.IOException; import java.io.Reader; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.MissingResourceException; @@ -72,7 +74,7 @@ public void compile(Path inputPath, Optional outputPath) { notifyInfo(getTitle() + ", version " + getVersion()); Path finalOutputPath = outputPath.orElse(convertInputToOutputPath(inputPath, ".bssem")); - try (Reader reader = new FileReader(inputPath.toFile())) { + try (Reader reader = Files.newBufferedReader(inputPath, StandardCharsets.UTF_8)) { Lexer lexer = createLexer(CharStreams.fromReader(reader)); lexer.addErrorListener(new ParserErrorListener(inputPath.toString())); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/AssemblerZ80.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/AssemblerZ80.java index 736b4f97e..f311cb4d5 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/AssemblerZ80.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/AssemblerZ80.java @@ -27,6 +27,8 @@ import java.io.FileReader; import java.io.IOException; import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.MissingResourceException; @@ -95,7 +97,7 @@ public void compile(Path inputPath, Optional outputPath) { notifyInfo(getTitle() + ", version " + getVersion()); Path finalOutputPath = outputPath.orElse(convertInputToOutputPath(inputPath, ".hex")); - try (Reader reader = new FileReader(inputPath.toFile())) { + try (Reader reader = Files.newBufferedReader(inputPath, StandardCharsets.UTF_8)) { AsZ80Lexer lexer = createLexer(CharStreams.fromReader(reader)); lexer.addErrorListener(new ParserErrorListener(inputPath.toString())); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CompilerBrainduck.java b/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CompilerBrainduck.java index 852e21217..f5ea5cd9d 100644 --- a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CompilerBrainduck.java +++ b/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CompilerBrainduck.java @@ -25,6 +25,8 @@ import java.io.FileReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -119,7 +121,7 @@ private IntelHEX compileToHex(Path inputPath) throws Exception { Objects.requireNonNull(inputPath); notifyInfo(getTitle() + ", version " + getVersion()); - try (Reader reader = new FileReader(inputPath.toFile())) { + try (Reader reader = Files.newBufferedReader(inputPath, StandardCharsets.UTF_8)) { org.antlr.v4.runtime.Lexer lexer = createLexer(CharStreams.fromReader(reader)); lexer.addErrorListener(new ParserErrorListener()); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CompilerRAM.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CompilerRAM.java index 1a49bf331..76a4af186 100644 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CompilerRAM.java +++ b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CompilerRAM.java @@ -22,6 +22,8 @@ import java.io.FileReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.MissingResourceException; @@ -74,7 +76,7 @@ public void compile(Path inputPath, Optional outputPath) { notifyInfo(getTitle() + ", version " + getVersion()); Path finalOutputPath = outputPath.orElse(convertInputToOutputPath(inputPath, ".bram")); - try (Reader reader = new FileReader(inputPath.toFile())) { + try (Reader reader = Files.newBufferedReader(inputPath, StandardCharsets.UTF_8)) { org.antlr.v4.runtime.Lexer lexer = createLexer(CharStreams.fromReader(reader)); lexer.addErrorListener(new ParserErrorListener()); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CompilerRASP.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CompilerRASP.java index 060338874..dfc487886 100644 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CompilerRASP.java +++ b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CompilerRASP.java @@ -23,6 +23,8 @@ import java.io.FileReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -60,7 +62,7 @@ public void compile(Path inputPath, Optional outputPathX) { notifyInfo(getTitle() + ", version " + getVersion()); Path finalOutputPath = outputPathX.orElse(convertInputToOutputPath(inputPath, ".brasp")); - try (Reader reader = new FileReader(inputPath.toFile())) { + try (Reader reader = Files.newBufferedReader(inputPath, StandardCharsets.UTF_8)) { org.antlr.v4.runtime.Lexer lexer = createLexer(CharStreams.fromReader(reader)); lexer.addErrorListener(new ParserErrorListener()); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/plugins/device/88-dcdd/src/main/java/net/emustudio/plugins/device/mits88dcdd/cmdline/Cpmfs.java b/plugins/device/88-dcdd/src/main/java/net/emustudio/plugins/device/mits88dcdd/cmdline/Cpmfs.java index 38734f947..5b09fa0e7 100644 --- a/plugins/device/88-dcdd/src/main/java/net/emustudio/plugins/device/mits88dcdd/cmdline/Cpmfs.java +++ b/plugins/device/88-dcdd/src/main/java/net/emustudio/plugins/device/mits88dcdd/cmdline/Cpmfs.java @@ -11,6 +11,8 @@ import picocli.CommandLine.ParentCommand; import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.List; import java.util.Locale; @@ -67,7 +69,7 @@ public void copy(@Parameters(paramLabel = "SRC_FILE", index = "0", description = if (srcInCpm) { content.append(cpmfs.readFile(realSrc)); } else { - try (Reader reader = new FileReader(realSrc)) { + try (Reader reader = Files.newBufferedReader(new File(realSrc).toPath(), StandardCharsets.UTF_8)) { reader.transferTo(content); } } diff --git a/plugins/device/audiotape-player/src/main/resources/net/emustudio/plugins/device/audiotape_player/gui/.DS_Store b/plugins/device/audiotape-player/src/main/resources/net/emustudio/plugins/device/audiotape_player/gui/.DS_Store deleted file mode 100644 index 5008ddfcf..000000000 Binary files a/plugins/device/audiotape-player/src/main/resources/net/emustudio/plugins/device/audiotape_player/gui/.DS_Store and /dev/null differ diff --git a/plugins/device/simh-pseudo/src/main/java/net/emustudio/plugins/device/simh/commands/ReadURL.java b/plugins/device/simh-pseudo/src/main/java/net/emustudio/plugins/device/simh/commands/ReadURL.java index e79101feb..07e4550d2 100644 --- a/plugins/device/simh-pseudo/src/main/java/net/emustudio/plugins/device/simh/commands/ReadURL.java +++ b/plugins/device/simh-pseudo/src/main/java/net/emustudio/plugins/device/simh/commands/ReadURL.java @@ -85,6 +85,8 @@ private String readURL(String theUrl) { try { URL url = new URL(theUrl); URLConnection urlConnection = url.openConnection(); + urlConnection.setConnectTimeout(10_000); + urlConnection.setReadTimeout(10_000); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); String line; diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/DeviceImpl.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/DeviceImpl.java index 0822949db..d4bc9d0cb 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/DeviceImpl.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/DeviceImpl.java @@ -7,10 +7,8 @@ import net.emustudio.emulib.plugins.annotations.PluginRoot; import net.emustudio.emulib.plugins.device.AbstractDevice; import net.emustudio.emulib.runtime.ApplicationApi; -import net.emustudio.emulib.runtime.interaction.GuiUtils; import net.emustudio.emulib.runtime.settings.PluginSettings; import net.emustudio.plugins.device.zxspectrum.bus.api.ZxSpectrumBus; -import net.emustudio.plugins.device.zxspectrum.ula.gui.Keyboard; import net.emustudio.plugins.device.zxspectrum.ula.gui.DisplayWindow; import javax.swing.*; @@ -23,7 +21,6 @@ public class DeviceImpl extends AbstractDevice { private final boolean guiSupported; - private final Keyboard keyboard = new Keyboard(); private boolean guiIOset = false; private ULA ula; @@ -42,7 +39,6 @@ public void initialize() throws PluginInitializationException { this.ula = new ULA(bus); this.passedCyclesMediator = new PassedCyclesMediator(ula); bus.addPassedCyclesListener(passedCyclesMediator); - keyboard.addOnKeyListener(ula); bus.attachDevice(0xFE, ula); } @@ -53,7 +49,6 @@ public void reset() { @Override public void destroy() { - keyboard.close(); if (guiIOset || gui != null) { gui.destroy(); gui = null; @@ -75,9 +70,8 @@ public boolean isShowSettingsSupported() { public void showGUI(JFrame parent) { if (guiSupported) { if (!guiIOset) { - this.gui = new DisplayWindow(parent, ula, keyboard); + this.gui = new DisplayWindow(parent, ula); passedCyclesMediator.setCanvas(gui.getCanvas()); - GuiUtils.addKeyListener(gui, keyboard); guiIOset = true; this.gui.setVisible(true); } diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/ULA.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/ULA.java index c2f602fb7..d7e43c843 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/ULA.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/ULA.java @@ -4,14 +4,16 @@ import net.emustudio.plugins.cpu.intel8080.api.Context8080; import net.emustudio.plugins.device.zxspectrum.bus.api.ZxSpectrumBus; -import net.emustudio.plugins.device.zxspectrum.ula.gui.Keyboard; +import net.emustudio.plugins.device.zxspectrum.ula.gui.KeyboardDispatcher; import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.BiConsumer; +import static java.awt.event.KeyEvent.*; import static net.emustudio.plugins.device.zxspectrum.ula.ZxParameters.*; /** @@ -58,7 +60,7 @@ * - P2 to P0 is the PAPER colour * - I2 to I0 is the INK colour */ -public class ULA implements Context8080.CpuPortDevice, Keyboard.OnKeyListener { +public class ULA implements Context8080.CpuPortDevice, KeyboardDispatcher.OnKeyListener { private final static byte[] RST_7 = new byte[0x38]; // works for IM1 and IM2 modes private final static byte[] KEY_SHIFT = new byte[]{0, 1}; private final static byte[] KEY_SYM_SHIFT = new byte[]{7, 2}; @@ -72,49 +74,72 @@ public class ULA implements Context8080.CpuPortDevice, Keyboard.OnKeyListener { // maps host characters to ZX Spectrum key "commands" // Byte[] = {keymap index, "zero" value, shift, symshift} - private final static Map CHAR_MAPPING = new HashMap<>(); + private final static Map CHAR_MAPPING = new HashMap<>(); static { - CHAR_MAPPING.put('Z', new Byte[]{0, 2, -1, -1}); // z, COPY, ":" - CHAR_MAPPING.put('X', new Byte[]{0, 4, -1, -1}); // x, CLEAR, "£" - CHAR_MAPPING.put('C', new Byte[]{0, 8, -1, -1}); // c, CONT, "?" - CHAR_MAPPING.put('V', new Byte[]{0, 16, -1, -1}); // v, CLS, "/" - CHAR_MAPPING.put('B', new Byte[]{7, 16, -1, -1}); // b, BORDER, "*" - CHAR_MAPPING.put('N', new Byte[]{7, 8, -1, -1}); // n, NEXT, "," - CHAR_MAPPING.put('M', new Byte[]{7, 4, -1, -1}); // m, PAUSE, "." - CHAR_MAPPING.put(' ', new Byte[]{7, 1, -1, -1}); // " " - CHAR_MAPPING.put('\n', new Byte[]{6, 1, -1, -1}); // ENTER - CHAR_MAPPING.put('A', new Byte[]{1, 1, -1, -1}); // a, NEW, "STOP" - CHAR_MAPPING.put('S', new Byte[]{1, 2, -1, -1}); // s, SAVE, "NOT" - CHAR_MAPPING.put('D', new Byte[]{1, 4, -1, -1}); // d, DIM, "STEP" - CHAR_MAPPING.put('F', new Byte[]{1, 8, -1, -1}); // f, FOR, "TO" - CHAR_MAPPING.put('G', new Byte[]{1, 16, -1, -1}); // g, GOTO, "THEN" - CHAR_MAPPING.put('H', new Byte[]{6, 16, -1, -1}); // h, GOSUB, "↑" - CHAR_MAPPING.put('J', new Byte[]{6, 8, -1, -1}); // j, LOAD, "-" - CHAR_MAPPING.put('K', new Byte[]{6, 4, -1, -1}); // k, LIST, "+" - CHAR_MAPPING.put('L', new Byte[]{6, 2, -1, -1}); // l, LET, "=" - CHAR_MAPPING.put('Q', new Byte[]{2, 1, -1, -1}); // q, PLOT, "<=" - CHAR_MAPPING.put('W', new Byte[]{2, 2, -1, -1}); // w, DRAW, "<>" - CHAR_MAPPING.put('E', new Byte[]{2, 4, -1, -1}); // e, REM, ">=" - CHAR_MAPPING.put('R', new Byte[]{2, 8, -1, -1}); // r, RUN, "<" - CHAR_MAPPING.put('T', new Byte[]{2, 16, -1, -1}); // t, RAND, ">" - CHAR_MAPPING.put('Y', new Byte[]{5, 16, -1, -1}); // y, RETURN, "AND" - CHAR_MAPPING.put('U', new Byte[]{5, 8, -1, -1}); // u, IF, "OR" - CHAR_MAPPING.put('I', new Byte[]{5, 4, -1, -1}); // i, INPUT, "AT" - CHAR_MAPPING.put('O', new Byte[]{5, 2, -1, -1}); // o, POKE, ";" - CHAR_MAPPING.put('P', new Byte[]{5, 1, -1, -1}); // p, PRINT, " - CHAR_MAPPING.put('1', new Byte[]{3, 1, -1, -1}); // 1, "!" - CHAR_MAPPING.put('2', new Byte[]{3, 2, -1, -1}); // 2, "@" - CHAR_MAPPING.put('3', new Byte[]{3, 4, -1, -1}); // 3, "#" - CHAR_MAPPING.put('4', new Byte[]{3, 8, -1, -1}); // 4, "$" - CHAR_MAPPING.put('5', new Byte[]{3, 16, -1, -1}); // 5, "%" - CHAR_MAPPING.put('6', new Byte[]{4, 16, -1, -1}); // 6, "&" - CHAR_MAPPING.put('7', new Byte[]{4, 8, -1, -1}); // 7, "'" - CHAR_MAPPING.put('8', new Byte[]{4, 4, -1, -1}); // 8, "(" - CHAR_MAPPING.put('9', new Byte[]{4, 2, -1, -1}); // 9, ")" - CHAR_MAPPING.put('0', new Byte[]{4, 1, -1, -1}); // 0, "_" - CHAR_MAPPING.put('\b', new Byte[]{4, 1, 1, -1}); // backspace - CHAR_MAPPING.put((char) 127, new Byte[]{4, 1, 1, -1}); // delete + CHAR_MAPPING.put(VK_Z, new Byte[]{0, 2, -1, -1}); // z, COPY, ":" + CHAR_MAPPING.put(VK_COLON, new Byte[]{0, 2, 0, 1}); + CHAR_MAPPING.put(VK_X, new Byte[]{0, 4, -1, -1}); // x, CLEAR, "£" + CHAR_MAPPING.put(VK_C, new Byte[]{0, 8, -1, -1}); // c, CONT, "?" + CHAR_MAPPING.put(VK_V, new Byte[]{0, 16, -1, -1}); // v, CLS, "/" + CHAR_MAPPING.put(VK_SLASH, new Byte[]{0, 16, 0, 1}); + CHAR_MAPPING.put(VK_B, new Byte[]{7, 16, -1, -1}); // b, BORDER, "*" + CHAR_MAPPING.put(VK_ASTERISK, new Byte[]{7, 16, 0, 1}); + CHAR_MAPPING.put(VK_N, new Byte[]{7, 8, -1, -1}); // n, NEXT, "," + CHAR_MAPPING.put(VK_COMMA, new Byte[]{7, 8, 0, 1}); + CHAR_MAPPING.put(VK_M, new Byte[]{7, 4, -1, -1}); // m, PAUSE, "." + CHAR_MAPPING.put(VK_DECIMAL, new Byte[]{7, 4, 0, 1}); + CHAR_MAPPING.put(VK_PERIOD, new Byte[]{7, 4, 0, 1}); + CHAR_MAPPING.put(VK_SPACE, new Byte[]{7, 1, -1, -1}); // " " + CHAR_MAPPING.put(VK_ENTER, new Byte[]{6, 1, -1, -1}); // ENTER + CHAR_MAPPING.put(VK_A, new Byte[]{1, 1, -1, -1}); // a, NEW, "STOP" + CHAR_MAPPING.put(VK_S, new Byte[]{1, 2, -1, -1}); // s, SAVE, "NOT" + CHAR_MAPPING.put(VK_D, new Byte[]{1, 4, -1, -1}); // d, DIM, "STEP" + CHAR_MAPPING.put(VK_F, new Byte[]{1, 8, -1, -1}); // f, FOR, "TO" + CHAR_MAPPING.put(VK_G, new Byte[]{1, 16, -1, -1}); // g, GOTO, "THEN" + CHAR_MAPPING.put(VK_H, new Byte[]{6, 16, -1, -1}); // h, GOSUB, "↑" + CHAR_MAPPING.put(VK_UP, new Byte[]{6, 16, 0, 1}); + CHAR_MAPPING.put(VK_J, new Byte[]{6, 8, -1, -1}); // j, LOAD, "-" + CHAR_MAPPING.put(VK_SUBTRACT, new Byte[]{6, 8, 0, 1}); + CHAR_MAPPING.put(VK_K, new Byte[]{6, 4, -1, -1}); // k, LIST, "+" + CHAR_MAPPING.put(VK_ADD, new Byte[]{6, 4, 0, 1}); + CHAR_MAPPING.put(VK_L, new Byte[]{6, 2, -1, -1}); // l, LET, "=" + CHAR_MAPPING.put(VK_EQUALS, new Byte[]{6, 2, 0, 1}); + CHAR_MAPPING.put(VK_Q, new Byte[]{2, 1, -1, -1}); // q, PLOT, "<=" + CHAR_MAPPING.put(VK_W, new Byte[]{2, 2, -1, -1}); // w, DRAW, "<>" + CHAR_MAPPING.put(VK_E, new Byte[]{2, 4, -1, -1}); // e, REM, ">=" + CHAR_MAPPING.put(VK_R, new Byte[]{2, 8, -1, -1}); // r, RUN, "<" + CHAR_MAPPING.put(VK_LESS, new Byte[]{2, 8, 0, 1}); + CHAR_MAPPING.put(VK_T, new Byte[]{2, 16, -1, -1}); // t, RAND, ">" + CHAR_MAPPING.put(VK_GREATER, new Byte[]{2, 16, 0, 1}); + CHAR_MAPPING.put(VK_Y, new Byte[]{5, 16, -1, -1}); // y, RETURN, "AND" + CHAR_MAPPING.put(VK_U, new Byte[]{5, 8, -1, -1}); // u, IF, "OR" + CHAR_MAPPING.put(VK_I, new Byte[]{5, 4, -1, -1}); // i, INPUT, "AT" + CHAR_MAPPING.put(VK_O, new Byte[]{5, 2, -1, -1}); // o, POKE, ";" + CHAR_MAPPING.put(VK_SEMICOLON, new Byte[]{5, 2, 0, 1}); + CHAR_MAPPING.put(VK_P, new Byte[]{5, 1, -1, -1}); // p, PRINT, " + CHAR_MAPPING.put(VK_QUOTEDBL, new Byte[]{5, 1, 0, 1}); + CHAR_MAPPING.put(VK_1, new Byte[]{3, 1, -1, -1}); // 1, "!" + CHAR_MAPPING.put(VK_EXCLAMATION_MARK, new Byte[]{3, 1, 0, 1}); + CHAR_MAPPING.put(VK_2, new Byte[]{3, 2, -1, -1}); // 2, "@" + CHAR_MAPPING.put(VK_AT, new Byte[]{3, 2, 0, 1}); + CHAR_MAPPING.put(VK_3, new Byte[]{3, 4, -1, -1}); // 3, "#" + CHAR_MAPPING.put(VK_NUMBER_SIGN, new Byte[]{3, 4, 0, 1}); + CHAR_MAPPING.put(VK_4, new Byte[]{3, 8, -1, -1}); // 4, "$" + CHAR_MAPPING.put(VK_DOLLAR, new Byte[]{3, 8, 0, 1}); + CHAR_MAPPING.put(VK_5, new Byte[]{3, 16, -1, -1}); // 5, "%" + CHAR_MAPPING.put(VK_6, new Byte[]{4, 16, -1, -1}); // 6, "&" + CHAR_MAPPING.put(VK_AMPERSAND, new Byte[]{4, 16, 0, 1}); + CHAR_MAPPING.put(VK_7, new Byte[]{4, 8, -1, -1}); // 7, "'" + CHAR_MAPPING.put(VK_QUOTE, new Byte[]{4, 8, 0, 1}); + CHAR_MAPPING.put(VK_8, new Byte[]{4, 4, -1, -1}); // 8, "(" + CHAR_MAPPING.put(VK_LEFT_PARENTHESIS, new Byte[]{4, 4, 0, 1}); + CHAR_MAPPING.put(VK_9, new Byte[]{4, 2, -1, -1}); // 9, ")" + CHAR_MAPPING.put(VK_RIGHT_PARENTHESIS, new Byte[]{4, 2, 0, 1}); + CHAR_MAPPING.put(VK_0, new Byte[]{4, 1, -1, -1}); // 0, "_" + CHAR_MAPPING.put(VK_UNDERSCORE, new Byte[]{4, 1, 0, 1}); + CHAR_MAPPING.put(VK_BACK_SPACE, new Byte[]{4, 1, 1, 0}); // backspace + CHAR_MAPPING.put(VK_DELETE, new Byte[]{4, 1, 1, 0}); // delete } public boolean videoFlash = false; @@ -171,30 +196,23 @@ public byte read(int portAddress) { // If more than one address line is made low, the result is the logical AND of all single inputs byte result = (byte) 0xBF; // 1011 1111 // no EAR input - if ((portAddress & 0xFEFE) == 0xFEFE) { - // SHIFT, Z, X, C, V - result &= keymap[0]; - } else if ((portAddress & 0xFDFE) == 0xFDFE) { - // A, S, D, F, G - result &= keymap[1]; - } else if ((portAddress & 0xFBFE) == 0xFBFE) { - // Q, W, E, R, T - result &= keymap[2]; - } else if ((portAddress & 0xF7FE) == 0xF7FE) { - // 1, 2, 3, 4, 5 - result &= keymap[3]; - } else if ((portAddress & 0xEFFE) == 0xEFFE) { - // 0, 9, 8, 7, 6 - result &= keymap[4]; - } else if ((portAddress & 0xDFFE) == 0xDFFE) { - // P, O, I, U, Y - result &= keymap[5]; - } else if ((portAddress & 0xBFFE) == 0xBFFE) { - // ENTER, L, K, J, H - result &= keymap[6]; - } else if ((portAddress & 0x7FFE) == 0x7FFE) { - // SPACE, SYM SHIFT, M, N, B - result &= keymap[7]; + if ((portAddress & 0xFE) == 0xFE) { + int keyLine = 0; + portAddress >>>= 8; + while ((portAddress & 1) != 0) { + portAddress >>>= 1; + keyLine++; + } + + // FE = 0 1111 1110 + // FD = 1 1111 1101 + // FB = 2 1111 1011 + // F7 = 3 1111 0111 + // EF = 4 1110 1111 + // DF = 5 1101 1111 + // BF = 6 1011 1111 + // 7F = 7 0111 1111 + result &= keymap[keyLine]; } // LINE IN? @@ -219,59 +237,53 @@ public String toString() { } @Override - public void onKeyUp(KeyEvent evt) { - int keyCode = evt.getExtendedKeyCode(); - switch (keyCode) { - case KeyEvent.VK_CONTROL: - keymap[KEY_SYM_SHIFT[0]] |= KEY_SYM_SHIFT[1]; - break; - case KeyEvent.VK_SHIFT: - keymap[KEY_SHIFT[0]] |= KEY_SHIFT[1]; - break; - default: - Byte[] command = CHAR_MAPPING.get((char) keyCode); - if (command != null) { - if (command[2] == 1) { - keymap[KEY_SHIFT[0]] |= KEY_SHIFT[1]; - } else if (command[2] == 0) { - keymap[KEY_SHIFT[0]] &= (byte) ((~KEY_SHIFT[1]) & 0xFF); - } - if (command[3] == 1) { - keymap[KEY_SYM_SHIFT[0]] |= KEY_SYM_SHIFT[1]; - } else if (command[3] == 0) { - keymap[KEY_SYM_SHIFT[0]] &= (byte) ((~KEY_SYM_SHIFT[1]) & 0xFF); - } - keymap[command[0]] |= command[1]; - } + public boolean onKeyEvent(KeyEvent e) { + boolean pressed = e.getID() == KEY_PRESSED; + if (!pressed && e.getID() != KEY_RELEASED) { + return false; } - } + BiConsumer keySet = pressed ? this::andKeyMap : this::orKeyMap; + BiConsumer keyUnset = pressed ? this::orKeyMap : this::andKeyMap; - @Override - public void onKeyDown(KeyEvent evt) { - int keyCode = evt.getExtendedKeyCode(); - switch (keyCode) { - case KeyEvent.VK_CONTROL: - keymap[KEY_SYM_SHIFT[0]] &= (byte) ((~KEY_SYM_SHIFT[1]) & 0xFF); - break; - case KeyEvent.VK_SHIFT: - keymap[KEY_SHIFT[0]] &= (byte) ((~KEY_SHIFT[1]) & 0xFF); - break; - default: - Byte[] command = CHAR_MAPPING.get((char) keyCode); - if (command != null) { - if (command[2] == 1) { - keymap[KEY_SHIFT[0]] &= (byte) ((~KEY_SHIFT[1]) & 0xFF); - } else if (command[2] == 0) { - keymap[KEY_SHIFT[0]] |= KEY_SHIFT[1]; - } - if (command[3] == 1) { - keymap[KEY_SYM_SHIFT[0]] &= (byte) ((~KEY_SYM_SHIFT[1]) & 0xFF); - } else if (command[3] == 0) { - keymap[KEY_SYM_SHIFT[0]] |= KEY_SYM_SHIFT[1]; - } - keymap[command[0]] &= (byte) ((~command[1]) & 0xFF); - } + // shift / alt / ctrl are visible in modifiersEx only if pressed = true + boolean symShift = (e.getModifiersEx() & (KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)) != 0; + boolean shift = (e.getModifiersEx() & (KeyEvent.SHIFT_DOWN_MASK)) != 0; + + Byte[] command = CHAR_MAPPING.get(e.getKeyCode()); + if (command != null) { + if (command[2] == 1 || (command[2] == -1 && shift)) { + keySet.accept(KEY_SHIFT[0], KEY_SHIFT[1]); + } else if (command[2] == 0 || !shift) { + keyUnset.accept(KEY_SHIFT[0], KEY_SHIFT[1]); + } + if (command[3] == 1 || (command[3] == -1 && symShift)) { + keySet.accept(KEY_SYM_SHIFT[0], KEY_SYM_SHIFT[1]); + } else if (command[3] == 0 || !symShift) { + keyUnset.accept(KEY_SYM_SHIFT[0], KEY_SYM_SHIFT[1]); + } + // TODO: shift/symshift are toggling for some reason + keySet.accept(command[0], command[1]); + } else { + if (shift) { + keySet.accept(KEY_SHIFT[0], KEY_SHIFT[1]); + } else { + keyUnset.accept(KEY_SHIFT[0], KEY_SHIFT[1]); + } + if (symShift) { + keySet.accept(KEY_SYM_SHIFT[0], KEY_SYM_SHIFT[1]); + } else { + keyUnset.accept(KEY_SYM_SHIFT[0], KEY_SYM_SHIFT[1]); + } } + return true; + } + + private void andKeyMap(byte key, byte value) { + keymap[key] &= (byte) ((~value) & 0xFF); + } + + private void orKeyMap(byte key, byte value) { + keymap[key] |= value; } /** diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java index 41657530a..4763a9ee4 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java @@ -48,9 +48,11 @@ public class DisplayCanvas extends Canvas implements AutoCloseable { private final ULA ula; private final PaintCycle paintCycle = new PaintCycle(); + private final KeyboardCanvas keyboardCanvas; - public DisplayCanvas(ULA ula) { + public DisplayCanvas(ULA ula, KeyboardCanvas keyboardCanvas) { this.ula = Objects.requireNonNull(ula); + this.keyboardCanvas = Objects.requireNonNull(keyboardCanvas); this.screenImage.setAccelerationPriority(1.0f); this.screenImageData = ((DataBufferInt) this.screenImage.getRaster().getDataBuffer()).getData(); } @@ -169,6 +171,13 @@ protected void paint() { graphics.drawImage( screenImage, MARGIN, MARGIN, (int) (SCREEN_IMAGE_WIDTH * ZOOM), (int) (SCREEN_IMAGE_HEIGHT * ZOOM), null); + + Color color = graphics.getColor(); + graphics.setColor(new Color(0, 0, 0, 127)); + graphics.translate(0, SCREEN_IMAGE_HEIGHT * ZOOM - KeyboardCanvas.KEYBOARD_HEIGHT + MARGIN); + keyboardCanvas.paint(graphics); + graphics.setColor(color); + graphics.dispose(); } while (strategy.contentsRestored()); diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java index d6ee50907..8391a7ef6 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java @@ -5,21 +5,26 @@ import net.emustudio.plugins.device.zxspectrum.ula.ULA; import javax.swing.*; +import javax.swing.border.BevelBorder; +import java.awt.*; import java.awt.event.WindowEvent; import static net.emustudio.plugins.device.zxspectrum.ula.ZxParameters.SCREEN_IMAGE_HEIGHT; import static net.emustudio.plugins.device.zxspectrum.ula.ZxParameters.SCREEN_IMAGE_WIDTH; public class DisplayWindow extends JDialog { - private final DisplayCanvas canvas; - private final KeyboardCanvas keyboardCanvas; - public final static int MARGIN = 30; - public DisplayWindow(JFrame parent, ULA ula, Keyboard keyboard) { + private final static int BOUND_X = (int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_WIDTH + 2 * MARGIN); + private final static int BOUND_Y = (int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_HEIGHT + 2 * MARGIN); + + private final DisplayCanvas canvas; + private final KeyboardCanvas keyboardCanvas = new KeyboardCanvas(0); + private final JPanel statusBar = new JPanel(); + + public DisplayWindow(JFrame parent, ULA ula) { super(parent); - this.canvas = new DisplayCanvas(ula); - this.keyboardCanvas = new KeyboardCanvas(keyboard); + this.canvas = new DisplayCanvas(ula, keyboardCanvas); initComponents(); setLocationRelativeTo(parent); @@ -33,6 +38,12 @@ public void windowActivated(WindowEvent winEvt) { canvas.redrawNow(); } }); + KeyboardDispatcher keyboardDispatcher = new KeyboardDispatcher(); + KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + manager.addKeyEventDispatcher(keyboardDispatcher); + + keyboardDispatcher.addOnKeyListener(keyboardCanvas); + keyboardDispatcher.addOnKeyListener(ula); } public void destroy() { @@ -43,24 +54,41 @@ public void destroy() { private void initComponents() { setTitle("ZX Spectrum48K"); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - canvas.setBounds( - MARGIN, MARGIN, - (int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_WIDTH + 2 * MARGIN), - (int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_HEIGHT + 2 * MARGIN)); + canvas.setBounds(MARGIN, MARGIN, BOUND_X, BOUND_Y); + + statusBar.setLayout(new BoxLayout(statusBar, BoxLayout.X_AXIS)); + statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED)); + + JLabel lblOpacity = new JLabel("Keyboard opacity:"); + lblOpacity.setHorizontalAlignment(SwingConstants.LEFT); + statusBar.add(lblOpacity); + + JSlider sliderOpacity = new JSlider(); + sliderOpacity.setMinimum(0); + sliderOpacity.setMaximum(100); + sliderOpacity.setValue(keyboardCanvas.getAlpha()); + statusBar.add(sliderOpacity); + + JLabel lblOpacityPercent = new JLabel(keyboardCanvas.getAlpha() + "%"); + statusBar.add(lblOpacityPercent); + sliderOpacity.addChangeListener(e -> { + int value = sliderOpacity.getValue(); + keyboardCanvas.setAlpha(value); + lblOpacityPercent.setText(value + "%"); + }); GroupLayout layout = new GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(canvas) - .addComponent(keyboardCanvas)); + .addComponent(statusBar, GroupLayout.DEFAULT_SIZE, 400, 400)); // TODO: gap? layout.setVerticalGroup( layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(canvas, GroupLayout.DEFAULT_SIZE, 407, Short.MAX_VALUE) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(keyboardCanvas, GroupLayout.DEFAULT_SIZE, KeyboardCanvas.KEYBOARD_HEIGHT + 3, Short.MAX_VALUE) - .addContainerGap())); + .addComponent(statusBar, GroupLayout.PREFERRED_SIZE, 46, GroupLayout.PREFERRED_SIZE))); pack(); } diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/Keyboard.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/Keyboard.java deleted file mode 100644 index cacf50397..000000000 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/Keyboard.java +++ /dev/null @@ -1,67 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2025 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.device.zxspectrum.ula.gui; - -import net.emustudio.emulib.runtime.interaction.GuiUtils; -import net.jcip.annotations.ThreadSafe; - -import java.awt.event.ContainerEvent; -import java.awt.event.ContainerListener; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CopyOnWriteArrayList; - -@ThreadSafe -public class Keyboard implements AutoCloseable, KeyListener, ContainerListener { - private final List onKeyListeners = new CopyOnWriteArrayList<>(); - - public interface OnKeyListener { - - void onKeyDown(KeyEvent evt); - - void onKeyUp(KeyEvent evt); - } - - public void addOnKeyListener(OnKeyListener onKeyListener) { - onKeyListeners.add(Objects.requireNonNull(onKeyListener)); - } - - protected void notifyOnKeyDown(KeyEvent evt) { - onKeyListeners.forEach(c -> c.onKeyDown(evt)); - } - - protected void notifyOnKeyUp(KeyEvent evt) { - onKeyListeners.forEach(c -> c.onKeyUp(evt)); - } - - public void close() { - onKeyListeners.clear(); - } - - @Override - public void keyTyped(KeyEvent e) { - - } - - @Override - public void keyPressed(KeyEvent evt) { - notifyOnKeyDown(evt); - } - - @Override - public void keyReleased(KeyEvent evt) { - notifyOnKeyUp(evt); - } - - @Override - public void componentAdded(ContainerEvent e) { - GuiUtils.addKeyListener(e.getChild(), this); - } - - @Override - public void componentRemoved(ContainerEvent e) { - GuiUtils.removeKeyListener(e.getChild(), this); - } -} diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/KeyboardCanvas.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/KeyboardCanvas.java index 84b02cca3..87b4941fe 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/KeyboardCanvas.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/KeyboardCanvas.java @@ -7,6 +7,8 @@ import java.awt.event.KeyEvent; import java.awt.font.GlyphVector; +import static java.awt.event.KeyEvent.KEY_PRESSED; +import static java.awt.event.KeyEvent.KEY_RELEASED; import static net.emustudio.plugins.device.zxspectrum.ula.ZxParameters.SCREEN_IMAGE_WIDTH; import static net.emustudio.plugins.device.zxspectrum.ula.gui.DisplayCanvas.ZOOM; import static net.emustudio.plugins.device.zxspectrum.ula.gui.DisplayWindow.MARGIN; @@ -14,27 +16,26 @@ /** * Host-ZX Keyboard mapping visual representation. */ -public class KeyboardCanvas extends JComponent implements Keyboard.OnKeyListener { - private final static int bw = 45; // button width +public class KeyboardCanvas extends JComponent implements KeyboardDispatcher.OnKeyListener { + private final static int bw = 42; // button width private final static int bh = 33; // button height private final static int bsw = 70; // backspace width private final static int tabw = 60; // tab width private final static int lshiftw = 55; // left shift width - private final static int brakew = 300; // break width + private final static int brakew = 270; // break width private final static int s = 5; // space between buttons + private final static double sHalf = s / 2.0; // space between buttons private final static int arc = 15; // arc radius private final static int margin = 10; - private final static int rshiftw = 3 * bw - 3 * s - margin; // right shift width + private final static int rshiftw = 3 * bw - 3 * s - margin + (int) sHalf; // right shift width - public final static int KEYBOARD_WIDTH = 13 * (bw + s) + 10 + bsw + 10; - public final static int KEYBOARD_HEIGHT = 5 * (bh + s) + 2 * margin; + public final static int KEYBOARD_WIDTH = 13 * (bw + s) + bsw + 10 + 10; + public final static int KEYBOARD_HEIGHT = 5 * (bh + s) + 2 * margin - s; private final static int X_SHIFT = (int) ((ZOOM * SCREEN_IMAGE_WIDTH + 2 * MARGIN - KEYBOARD_WIDTH) / 2.0); private final static int X_SHIFT_L = X_SHIFT + margin; private final static int Y_SHIFT_T = margin; - - private final static Color USABLE_BUTTON_COLOR = Color.LIGHT_GRAY; - + private final static int STROKE_WIDTH = 3; private final static double[][] KEY_MAP = new double[][]{ new double[]{bw + s + bw / 2.0, bh}, // 1 @@ -47,7 +48,7 @@ public class KeyboardCanvas extends JComponent implements Keyboard.OnKeyListener new double[]{bw + s, 0}, // 8 new double[]{bw + s, 0}, // 9 new double[]{bw + s, 0}, // 0, - new double[]{-(11 * bw + s) + tabw + s, bh + s}, // Q + new double[]{-(11 * bw + s) + tabw + sHalf, bh + s}, // Q new double[]{bw + s, 0}, // W new double[]{bw + s, 0}, // E new double[]{bw + s, 0}, // R @@ -57,7 +58,7 @@ public class KeyboardCanvas extends JComponent implements Keyboard.OnKeyListener new double[]{bw + s, 0}, // I new double[]{bw + s, 0}, // O new double[]{bw + s, 0}, // P - new double[]{-(10 * bw + s) - tabw + bsw + s, bh + s}, // A + new double[]{-(10 * bw + s) - tabw + bsw + sHalf, bh + s}, // A new double[]{bw + s, 0}, // S new double[]{bw + s, 0}, // D new double[]{bw + s, 0}, // F @@ -101,36 +102,45 @@ public class KeyboardCanvas extends JComponent implements Keyboard.OnKeyListener "SHIFT", ":", "£", "?", "/", "*", ",", ".", "SHIFT", "SYM", "SYM" }; - private final BasicStroke outlineStroke = new BasicStroke(3.0f); + private final BasicStroke outlineStroke = new BasicStroke(STROKE_WIDTH); + private final Color usableButtonColor; + private final Color outlineColor; + private final Color brightColor; + private int alpha; private boolean symShift = false; private boolean shift = false; - public KeyboardCanvas(Keyboard keyboard) { + public KeyboardCanvas(int alpha) { setDoubleBuffered(true); - keyboard.addOnKeyListener(this); + this.alpha = alpha; + this.usableButtonColor = new Color( + Color.LIGHT_GRAY.getRed(), + Color.LIGHT_GRAY.getGreen(), + Color.LIGHT_GRAY.getBlue(), alpha); + this.outlineColor = new Color(0, 0, 0, alpha); + this.brightColor = new Color(255, 255, 255, alpha); } @Override - public void onKeyDown(KeyEvent evt) { - int keyCode = evt.getExtendedKeyCode(); - if (keyCode == KeyEvent.VK_CONTROL) { - symShift = true; - } else if (keyCode == KeyEvent.VK_SHIFT) { - shift = true; + public boolean onKeyEvent(KeyEvent e) { + boolean pressed = e.getID() == KEY_PRESSED; + if (!pressed && e.getID() != KEY_RELEASED) { + return false; } + + symShift = (e.getModifiersEx() & (KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)) != 0; + shift = (e.getModifiersEx() & (KeyEvent.SHIFT_DOWN_MASK)) != 0; repaint(); + return true; } - @Override - public void onKeyUp(KeyEvent evt) { - int keyCode = evt.getExtendedKeyCode(); - if (keyCode == KeyEvent.VK_CONTROL) { - symShift = false; - } else if (evt.getKeyCode() == KeyEvent.VK_SHIFT) { - shift = false; - } - repaint(); + public int getAlpha() { + return alpha; + } + + public void setAlpha(int alpha) { + this.alpha = alpha; } public void paint(Graphics g) { @@ -144,7 +154,7 @@ public void paint(Graphics g) { g2d.setFont(new Font("SansSerif", Font.PLAIN, 11)); g2d.setStroke(outlineStroke); - g2d.setColor(Color.WHITE); + g2d.setColor(adjustAlpha(brightColor)); g2d.translate(X_SHIFT_L, 0); for (int i = 0; i < KEY_MAP.length; i++) { @@ -163,7 +173,7 @@ public void paint(Graphics g) { int sw = g2d.getFontMetrics().stringWidth(text); g2d.translate(KEY_MAP[i][0] - sw / 2.0, KEY_MAP[i][1]); - g2d.setColor(Color.BLACK); + g2d.setColor(adjustAlpha(outlineColor)); g2d.fill(textShape); g2d.translate(sw / 2.0, 0); } @@ -171,17 +181,20 @@ public void paint(Graphics g) { private void drawKeyboard(Graphics2D g) { BasicStroke stroke = new BasicStroke(2.0f); + Color adjUsableButtonColor = adjustAlpha(usableButtonColor); + Color adjOutlineColor = adjustAlpha(outlineColor); // keyboard shape g.setStroke(stroke); - g.drawRoundRect(X_SHIFT, 0, KEYBOARD_WIDTH, KEYBOARD_HEIGHT, arc, arc); + g.setColor(adjOutlineColor); + g.drawRoundRect(X_SHIFT, -STROKE_WIDTH, KEYBOARD_WIDTH, KEYBOARD_HEIGHT, arc, arc); // top row for (int i = 0; i < 13; i++) { if (i >= 1 && i <= 10) { - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L + i * (bw + s), Y_SHIFT_T, bw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); } g.drawRoundRect(X_SHIFT_L + i * (bw + s), Y_SHIFT_T, bw, bh, arc, arc); } @@ -194,9 +207,9 @@ private void drawKeyboard(Graphics2D g) { g.drawRoundRect(X_SHIFT_L, y1, tabw, bh, arc, arc); for (int i = 0; i < 12; i++) { if (i < 10) { - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L + i * (bw + s) + tabw + s, y1, bw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); } g.drawRoundRect(X_SHIFT_L + i * (bw + s) + tabw + s, y1, bw, bh, arc, arc); } @@ -205,14 +218,14 @@ private void drawKeyboard(Graphics2D g) { int x0 = X_SHIFT_L + 12 * (bw + s) + tabw + s; int y0 = Y_SHIFT_T + bh + s; Polygon enterPolygon = new Polygon( - new int[]{x0, x0 + tabw - s, x0 + tabw - s, x0 + 2 * s, x0 + 2 * s, x0}, + new int[]{x0, x0 + tabw - 2 * s, x0 + tabw - 2 * s, x0 + 2 * s, x0 + 2 * s, x0}, new int[]{y0, y0, y0 + 2 * bh + s, y0 + 2 * bh + s, y0 + bh, y0 + bh}, 6 ); - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillPolygon(enterPolygon); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); g.drawPolygon(enterPolygon); // caps lock @@ -220,9 +233,9 @@ private void drawKeyboard(Graphics2D g) { g.drawRoundRect(X_SHIFT_L, y2, bsw, bh, arc, arc); for (int i = 0; i < 12; i++) { if (i < 9) { - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L + i * (bw + s) + bsw + s, y2, bw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); } g.drawRoundRect(X_SHIFT_L + i * (bw + s) + bsw + s, y2, bw, bh, arc, arc); } @@ -230,43 +243,50 @@ private void drawKeyboard(Graphics2D g) { // l shift int y3 = Y_SHIFT_T + (bh + s) * 3; - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L, y3, lshiftw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); g.drawRoundRect(X_SHIFT_L, y3, lshiftw, bh, arc, arc); for (int i = 0; i < 11; i++) { if (i >= 1 && i < 8) { - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L + i * (bw + s) + lshiftw + s, y3, bw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); } g.drawRoundRect(X_SHIFT_L + i * (bw + s) + lshiftw + s, y3, bw, bh, arc, arc); } - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L + 11 * (bw + s) + lshiftw + s, y3, rshiftw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); g.drawRoundRect(X_SHIFT_L + 11 * (bw + s) + lshiftw + s, y3, rshiftw, bh, arc, arc); // l ctrl int y4 = Y_SHIFT_T + (bh + s) * 4; - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L, y4, tabw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); g.drawRoundRect(X_SHIFT_L, y4, tabw, bh, arc, arc); g.drawRoundRect(X_SHIFT_L + tabw + s, y4, bw, bh, arc, arc); g.drawRoundRect(X_SHIFT_L + tabw + bw + 2 * s, y4, tabw, bh, arc, arc); - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L + 2 * (tabw + s) + bw + 2 * s, y4, brakew, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); g.drawRoundRect(X_SHIFT_L + 2 * (tabw + s) + bw + 2 * s, y4, brakew, bh, arc, arc); g.drawRoundRect(X_SHIFT_L + 2 * (tabw + s) + bw + 4 * s + brakew, y4, tabw, bh, arc, arc); - g.setColor(USABLE_BUTTON_COLOR); + g.setColor(adjUsableButtonColor); g.fillRoundRect(X_SHIFT_L + 3 * (tabw + s) + bw + 4 * s + brakew, y4, bw, bh, arc, arc); - g.setColor(Color.BLACK); + g.setColor(adjOutlineColor); g.drawRoundRect(X_SHIFT_L + 3 * (tabw + s) + bw + 4 * s + brakew, y4, bw, bh, arc, arc); // RCTRL g.drawRoundRect(X_SHIFT_L + 3 * (tabw + s) + 2 * bw + 5 * s + brakew, y4, bw, bh, arc, arc); g.drawRoundRect(X_SHIFT_L + 3 * (tabw + s) + 3 * bw + 6 * s + brakew, y4, tabw, bh, arc, arc); } + + private Color adjustAlpha(Color color) { + int r = color.getRed(); + int g = color.getGreen(); + int b = color.getBlue(); + return new Color(r, g, b, this.alpha); + } } diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/KeyboardDispatcher.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/KeyboardDispatcher.java new file mode 100644 index 000000000..28f13ba63 --- /dev/null +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/KeyboardDispatcher.java @@ -0,0 +1,46 @@ +/* SPDX-FileCopyrightText: 2006-2025 Peter Jakubčo + SPDX-License-Identifier: GPL-3.0-or-later */ +package net.emustudio.plugins.device.zxspectrum.ula.gui; + +import net.jcip.annotations.ThreadSafe; + +import java.awt.*; +import java.awt.event.KeyEvent; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +@ThreadSafe +public class KeyboardDispatcher implements AutoCloseable, KeyEventDispatcher { + private final List onKeyListeners = new CopyOnWriteArrayList<>(); + + public interface OnKeyListener { + + boolean onKeyEvent(KeyEvent e); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent e) { + boolean isConsumed = false; + if (!e.isConsumed()) { + boolean consumed = false; + for (OnKeyListener listener : onKeyListeners) { + consumed |= listener.onKeyEvent(e); + } + if (consumed) { + e.consume(); + isConsumed = true; + } + } + return isConsumed; + } + + @Override + public void close() { + onKeyListeners.clear(); + } + + public void addOnKeyListener(OnKeyListener onKeyListener) { + onKeyListeners.add(Objects.requireNonNull(onKeyListener)); + } +} diff --git a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java index 718fa0fdd..352c17251 100644 --- a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java +++ b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java @@ -119,10 +119,9 @@ public void setInputs(List inputs) { @SuppressWarnings("unchecked") public void deserialize(String filename) throws IOException, ClassNotFoundException { rwl.lockWrite(() -> { - try { - InputStream file = new FileInputStream(filename); - InputStream buffer = new BufferedInputStream(file); - ObjectInput input = new ObjectInputStream(buffer); + try (InputStream file = new FileInputStream(filename); + InputStream buffer = new BufferedInputStream(file); + ObjectInput input = new ObjectInputStream(buffer)) { labels.clear(); inputs.clear(); @@ -145,8 +144,6 @@ public String getLabel() { inputs.addAll((List) input.readObject()); memory.putAll((Map) input.readObject()); - - input.close(); } finally { notifyMemoryContentChanged(-1); notifyMemorySizeChanged(); diff --git a/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/MemoryContextImpl.java b/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/MemoryContextImpl.java index 2bc0bfad2..aca8ff259 100644 --- a/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/MemoryContextImpl.java +++ b/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/MemoryContextImpl.java @@ -123,10 +123,9 @@ public RaspMemory getSnapshot() { @SuppressWarnings("unchecked") public void deserialize(String filename, Consumer setProgramLocation) throws IOException, ClassNotFoundException { rwl.lockWrite(() -> { - try { - InputStream file = new FileInputStream(filename); - InputStream buffer = new BufferedInputStream(file); - ObjectInput input = new ObjectInputStream(buffer); + try (InputStream file = new FileInputStream(filename); + InputStream buffer = new BufferedInputStream(file); + ObjectInput input = new ObjectInputStream(buffer)) { labels.clear(); inputs.clear(); @@ -152,8 +151,6 @@ public String getLabel() { inputs.addAll((List) input.readObject()); memory.putAll((Map) input.readObject()); - - input.close(); } finally { notifyMemorySizeChanged(); notifyMemoryContentChanged(-1);