diff --git a/src/main/java/jace/Emulator.java b/src/main/java/jace/Emulator.java index 10c5587..3f9e68a 100644 --- a/src/main/java/jace/Emulator.java +++ b/src/main/java/jace/Emulator.java @@ -18,15 +18,15 @@ */ package jace; -import jace.config.Configuration; import jace.apple2e.Apple2e; +import jace.config.Configuration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Created on January 15, 2007, 10:10 PM - * @author Brendan Robert (BLuRry) brendan.robert@gmail.com + * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class Emulator { @@ -51,6 +51,10 @@ public class Emulator { Configuration.buildTree(); Configuration.loadSettings(); mainThread = Thread.currentThread(); + applyConfiguration(args); + } + + public void applyConfiguration(List args) { Map settings = new LinkedHashMap<>(); if (args != null) { for (int i = 0; i < args.size(); i++) { diff --git a/src/main/java/jace/EmulatorUILogic.java b/src/main/java/jace/EmulatorUILogic.java index 3e4b26c..e22bd01 100644 --- a/src/main/java/jace/EmulatorUILogic.java +++ b/src/main/java/jace/EmulatorUILogic.java @@ -30,7 +30,6 @@ import jace.core.Computer; import jace.core.Debugger; import jace.core.RAM; import jace.core.RAMListener; -import static jace.core.Utility.*; import jace.ide.IdeController; import java.io.File; import java.io.FileInputStream; @@ -59,6 +58,8 @@ import javafx.scene.layout.AnchorPane; import javafx.stage.FileChooser; import javafx.stage.Stage; +import static jace.core.Utility.*; + /** * This class contains miscellaneous user-invoked actions such as debugger * operations and running arbitrary files in the emulator. It is possible for @@ -81,7 +82,7 @@ public class EmulatorUILogic implements Reconfigurable { } }; } - + @ConfigurableField( category = "General", name = "Speed Setting" @@ -450,7 +451,7 @@ public class EmulatorUILogic implements Reconfigurable { } }); } - + @InvokableAction( name = "About", category = "general", @@ -459,7 +460,7 @@ public class EmulatorUILogic implements Reconfigurable { defaultKeyMapping = {"ctrl+shift+."}) public static void showAboutWindow() { //TODO: Implement - } + } public static boolean confirm(String message) { // return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Emulator.getFrame(), message); @@ -560,6 +561,8 @@ public class EmulatorUILogic implements Reconfigurable { @Override public void reconfigure() { - JaceApplication.getApplication().controller.setSpeed(speedSetting); + if (JaceApplication.getApplication() != null) { + JaceApplication.getApplication().controller.setSpeed(speedSetting); + } } } diff --git a/src/main/java/jace/apple2e/VideoNTSC.java b/src/main/java/jace/apple2e/VideoNTSC.java index 172d497..6a31210 100644 --- a/src/main/java/jace/apple2e/VideoNTSC.java +++ b/src/main/java/jace/apple2e/VideoNTSC.java @@ -20,7 +20,6 @@ package jace.apple2e; import jace.Emulator; import jace.EmulatorUILogic; -import static jace.apple2e.VideoDHGR.BLACK; import jace.config.ConfigurableField; import jace.config.InvokableAction; import jace.core.Computer; @@ -34,6 +33,8 @@ import javafx.scene.image.PixelWriter; import javafx.scene.image.WritableImage; import javafx.scene.paint.Color; +import static jace.apple2e.VideoDHGR.BLACK; + /** * Provides a clean color monitor simulation, complete with text-friendly * palette and mixed color/bw (mode 7) rendering. This class extends the @@ -59,6 +60,9 @@ public class VideoNTSC extends VideoDHGR { int[] scanline = new int[20]; static public int[] divBy28 = new int[560]; + @ConfigurableField(name = "Video Mode", category = "video", shortName = "mode", defaultValue = "TextFriendly", description = "Set Video Mode (Color|TextFriendly|Mode7|Mode7TextFriendly|Monochrome|Greenscreen|Amber)") + public static VideoMode videoMode = VideoMode.TextFriendly; + static { for (int i = 0; i < 560; i++) { divBy28[i] = i / 28; @@ -81,58 +85,68 @@ public class VideoNTSC extends VideoDHGR { Greenscreen("Green"), Amber("Amber"); String name; + VideoMode(String n) { name = n; } } - - static int currentMode = -1; + @InvokableAction(name = "Toggle video mode", category = "video", alternatives = "Gfx mode;color;b&w;monochrome", defaultKeyMapping = {"ctrl+shift+g"}) public static void changeVideoMode() { VideoNTSC thiss = (VideoNTSC) Emulator.computer.video; + int currentMode = Arrays.asList(VideoMode.values()).indexOf(thiss.getVideoMode()); currentMode++; if (currentMode >= VideoMode.values().length) { currentMode = 0; } - thiss.monochomeMode = false; + thiss.setVideoMode(VideoMode.values()[currentMode]); + } + + public void setVideoMode(VideoMode mode) { + videoMode = mode; + monochomeMode = false; WHITE = Color.WHITE; - switch (VideoMode.values()[currentMode]) { + switch (mode) { case Amber: - thiss.monochomeMode = true; + monochomeMode = true; WHITE = Color.web("ff8000"); break; case Greenscreen: - thiss.monochomeMode = true; + monochomeMode = true; WHITE = Color.web("0ccc68"); break; case Monochrome: - thiss.monochomeMode = true; + monochomeMode = true; break; case Color: - thiss.useTextPalette = false; - thiss.enableVideo7 = false; + useTextPalette = false; + enableVideo7 = false; break; case Mode7: - thiss.useTextPalette = false; - thiss.enableVideo7 = true; + useTextPalette = false; + enableVideo7 = true; break; case Mode7TextFriendly: - thiss.useTextPalette = true; - thiss.enableVideo7 = true; + useTextPalette = true; + enableVideo7 = true; break; case TextFriendly: - thiss.useTextPalette = true; - thiss.enableVideo7 = false; + useTextPalette = true; + enableVideo7 = false; break; } - thiss.activePalette = thiss.useTextPalette ? TEXT_PALETTE : SOLID_PALETTE; - EmulatorUILogic.notify("Video mode: "+VideoMode.values()[currentMode].name); + activePalette = useTextPalette ? TEXT_PALETTE : SOLID_PALETTE; + EmulatorUILogic.notify("Video mode: " + mode.name); forceRefresh(); } - + + public VideoMode getVideoMode() { + return videoMode; + } + @Override protected void showBW(WritableImage screen, int x, int y, int dhgrWord) { int pos = divBy28[x]; @@ -235,6 +249,7 @@ public class VideoNTSC extends VideoDHGR { } boolean monochomeMode = false; + private void renderScanline(WritableImage screen, int y) { int p = 0; if (rowStart != 0) { @@ -364,12 +379,13 @@ public class VideoNTSC extends VideoDHGR { @Override public void reconfigure() { + setVideoMode(videoMode); activePalette = useTextPalette ? TEXT_PALETTE : SOLID_PALETTE; super.reconfigure(); } // The following section captures changes to the RGB mode // The details of this are in Brodener's patent application #4631692 - // http://www.freepatentsonline.com/4631692.pdf + // http://www.freepatentsonline.com/4631692.pdf // as well as the AppleColor adapter card manual // http://apple2.info/download/Ext80ColumnAppleColorCardHR.pdf rgbMode graphicsMode = rgbMode.MIX; @@ -426,7 +442,7 @@ public class VideoNTSC extends VideoDHGR { })); rgbStateListeners.add(memory.observe(RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> { // When reset hook is called, reset the graphics mode - // This is useful in case a program is running that + // This is useful in case a program is running that // is totally clueless how to set the RGB state correctly. f1 = true; f2 = true; diff --git a/src/main/java/jace/apple2e/softswitch/MemorySoftSwitch.java b/src/main/java/jace/apple2e/softswitch/MemorySoftSwitch.java index 22166af..4003b0b 100644 --- a/src/main/java/jace/apple2e/softswitch/MemorySoftSwitch.java +++ b/src/main/java/jace/apple2e/softswitch/MemorySoftSwitch.java @@ -25,7 +25,7 @@ import jace.core.SoftSwitch; * A memory softswitch is a softswitch which triggers a memory reconfiguration * after its value is changed. * - * @author Brendan Robert (BLuRry) brendan.robert@gmail.com + * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class MemorySoftSwitch extends SoftSwitch { @@ -48,11 +48,15 @@ public class MemorySoftSwitch extends SoftSwitch { // Todo: Implement floating bus, maybe? @Override protected byte readSwitch() { - byte value = computer.getVideo().getFloatingBus(); - if (getState()) { - return (byte) (value | 0x080); + if (computer.getVideo() == null) { + return 0x00; } else { - return (byte) (value & 0x07f); + byte value = computer.getVideo().getFloatingBus(); + if (getState()) { + return (byte) (value | 0x080); + } else { + return (byte) (value & 0x07f); + } } } -} \ No newline at end of file +} diff --git a/src/main/java/jace/config/Configuration.java b/src/main/java/jace/config/Configuration.java index c45119d..1527e26 100644 --- a/src/main/java/jace/config/Configuration.java +++ b/src/main/java/jace/config/Configuration.java @@ -52,7 +52,6 @@ import java.util.logging.Logger; import java.util.stream.Stream; import javafx.collections.ObservableList; import javafx.scene.control.TreeItem; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; /** @@ -190,16 +189,17 @@ public class Configuration implements Reconfigurable { } public void setFieldValue(String field, Serializable value) { - setChanged(true); if (value != null) { if (value.equals(getFieldValue(field))) { return; } } else { if (getFieldValue(field) == null) { + setChanged(false); return; } } + setChanged(true); setRawFieldValue(field, value); } diff --git a/src/main/java/jace/core/Utility.java b/src/main/java/jace/core/Utility.java index 1cc9162..7a44110 100644 --- a/src/main/java/jace/core/Utility.java +++ b/src/main/java/jace/core/Utility.java @@ -18,6 +18,7 @@ */ package jace.core; +import jace.Emulator; import jace.config.Configuration; import jace.config.InvokableAction; import java.io.File; @@ -25,7 +26,6 @@ import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import org.reflections.Reflections; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -38,7 +38,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import java.util.stream.Stream; import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.control.Alert; @@ -49,6 +48,7 @@ import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; +import org.reflections.Reflections; /** * This is a set of helper functions which do not belong anywhere else. @@ -104,7 +104,7 @@ public class Utility { } return dist[m][n]; } - + /** * Normalize distance based on longest string * @param s @@ -114,7 +114,7 @@ public class Utility { public static int adjustedLevenshteinDistance(String s, String t) { return Math.max(s.length(), t.length()) - levenshteinDistance(s, t); } - + /** * Compare strings based on a tally of similar patterns found, using a fixed @@ -156,6 +156,9 @@ public class Utility { public static void setHeadlessMode(boolean headless) { isHeadless = headless; + if (Emulator.instance == null && headless) { + Emulator.instance = new Emulator(Collections.emptyList()); + } } public static boolean isHeadlessMode() { @@ -515,7 +518,7 @@ public class Utility { // } return actions.get(actionsList.get(0)); } - + private static int getActionNameMatch(String str, InvokableAction action) { int nameMatch = levenshteinDistance(str, action.name()); if (action.alternatives() != null) { diff --git a/src/test/java/jace/scripting/TestCommandlineArgs.java b/src/test/java/jace/scripting/TestCommandlineArgs.java new file mode 100644 index 0000000..4d1b74f --- /dev/null +++ b/src/test/java/jace/scripting/TestCommandlineArgs.java @@ -0,0 +1,79 @@ +/* + * Copyright 2019 Brendan Robert + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package jace.scripting; + +import jace.Emulator; +import jace.apple2e.Apple2e; +import jace.apple2e.MOS65C02; +import jace.apple2e.VideoNTSC; +import jace.apple2e.VideoNTSC.VideoMode; +import jace.core.Computer; +import jace.core.RAM; +import jace.core.SoundMixer; +import jace.core.Utility; +import java.util.Arrays; +import javafx.embed.swing.JFXPanel; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Check out various command line arguments to see if they do the right thing + */ +public class TestCommandlineArgs { + + static Computer computer; + static MOS65C02 cpu; + static RAM ram; + + @BeforeClass + public static void initJavaFX() { + new JFXPanel(); + } + + @Before + public void setup() { + Utility.setHeadlessMode(true); + SoundMixer.MUTE = true; + computer = new Apple2e(); + cpu = (MOS65C02) computer.getCpu(); + ram = computer.getMemory(); + Emulator.computer = (Apple2e) computer; + computer.pause(); + cpu.suspend(); + cpu.clearState(); + } + + static String GRAPHICS_MODE = "vid.videomode"; + + @Test + public void testVideoModes() { + for (VideoMode mode : VideoMode.values()) { + Emulator.instance.applyConfiguration(Arrays.asList("-" + GRAPHICS_MODE, mode.name().toLowerCase())); + assertTrue("Should be NTSC video module", computer.video instanceof VideoNTSC); + VideoNTSC video = (VideoNTSC) computer.video; + assertEquals("Should have switched to " + mode.name() + " mode", mode, video.getVideoMode()); + + Emulator.instance.applyConfiguration(Arrays.asList("-" + GRAPHICS_MODE, mode.name().toUpperCase())); + assertTrue("Should be NTSC video module", computer.video instanceof VideoNTSC); + video = (VideoNTSC) computer.video; + assertEquals("Should have switched to " + mode.name() + " mode", mode, video.getVideoMode()); + } + } +}