Introducing debug mode, Console and Memory windows
This commit is contained in:
		
							parent
							
								
									952605a8a4
								
							
						
					
					
						commit
						61bee38e3d
					
				| 
						 | 
				
			
			@ -7,6 +7,21 @@ locale {
 | 
			
		|||
# Main window
 | 
			
		||||
app {
 | 
			
		||||
 | 
			
		||||
  debug {
 | 
			
		||||
    (menu)      Debug
 | 
			
		||||
    console     Console
 | 
			
		||||
    memory      Memory
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  file {
 | 
			
		||||
    (menu)      File
 | 
			
		||||
    debug_mode  Debug mode
 | 
			
		||||
    exit        Exit
 | 
			
		||||
    game_mode   Game mode
 | 
			
		||||
    load_rom    Load ROM...
 | 
			
		||||
    new_window  New window
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  title {
 | 
			
		||||
    default     PVB Emulator
 | 
			
		||||
    mixed       {ctrl.number} {ctrl.filename} - {app.title.default}
 | 
			
		||||
| 
						 | 
				
			
			@ -14,13 +29,16 @@ app {
 | 
			
		|||
    rom         {ctrl.filename} - {app.title.default}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  file {
 | 
			
		||||
    (menu)      File
 | 
			
		||||
    load_rom    Load ROM...
 | 
			
		||||
    new_window  New window
 | 
			
		||||
    exit        Exit
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Console window
 | 
			
		||||
console {
 | 
			
		||||
  title     Console
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Memory window
 | 
			
		||||
memory {
 | 
			
		||||
  title     Memory
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Emulation core
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								makefile
								
								
								
								
							
							
						
						
									
										6
									
								
								makefile
								
								
								
								
							| 
						 | 
				
			
			@ -10,7 +10,7 @@ default:
 | 
			
		|||
	@echo
 | 
			
		||||
	@echo "Planet Virtual Boy Emulator"
 | 
			
		||||
	@echo "  https://www.planetvb.com/"
 | 
			
		||||
	@echo "  August 4, 2020"
 | 
			
		||||
	@echo "  August 5, 2020"
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Intended build environment: Debian i386 or amd64"
 | 
			
		||||
	@echo "  gcc-multilib"
 | 
			
		||||
| 
						 | 
				
			
			@ -84,8 +84,8 @@ native:
 | 
			
		|||
pack:
 | 
			
		||||
	$(eval jarname = "pvbemu_`date +%Y%m%d`.jar")
 | 
			
		||||
	@echo "  Bundling into $(jarname)"
 | 
			
		||||
	@jar -cfe $(jarname) Main *.class \
 | 
			
		||||
        app images locale native src util vue makefile license.txt
 | 
			
		||||
	@jar -cfe $(jarname) Main *.class license.txt \
 | 
			
		||||
        app images locale native util vue
 | 
			
		||||
 | 
			
		||||
# Performs a full build and packages it into a .jar
 | 
			
		||||
.PHONY: release
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,8 +42,14 @@ public class Main {
 | 
			
		|||
            catch (Error e) { }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Use the native module
 | 
			
		||||
        boolean useNative = false;
 | 
			
		||||
        for (String arg : args)
 | 
			
		||||
            if (arg.trim().toLowerCase().equals("native"))
 | 
			
		||||
                useNative = true;
 | 
			
		||||
 | 
			
		||||
        // Begin application operations
 | 
			
		||||
        new App();
 | 
			
		||||
        new App(useNative);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,10 +11,10 @@ import vue.*;
 | 
			
		|||
public class App {
 | 
			
		||||
 | 
			
		||||
    // Instance fields
 | 
			
		||||
    private Localizer.Locale[] locales;   // Language translations
 | 
			
		||||
    private Localizer          localizer; // UI localization manager
 | 
			
		||||
    private boolean            useNative; // Produce native core contexts
 | 
			
		||||
    private ArrayList<Window>  windows;   // Application windows
 | 
			
		||||
    private Localizer localizer; // UI localization manager
 | 
			
		||||
    private boolean   useNative; // Produce native core contexts
 | 
			
		||||
    private Localizer.Locale[]    locales; // Language translations
 | 
			
		||||
    private ArrayList<MainWindow> windows; // Application windows
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,14 +23,14 @@ public class App {
 | 
			
		|||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Default constructor
 | 
			
		||||
    public App() {
 | 
			
		||||
    public App(boolean useNative) {
 | 
			
		||||
 | 
			
		||||
        // Instance fields
 | 
			
		||||
        localizer     = new Localizer();
 | 
			
		||||
        windows       = new ArrayList<Window>();
 | 
			
		||||
        localizer = new Localizer();
 | 
			
		||||
        windows   = new ArrayList<MainWindow>();
 | 
			
		||||
 | 
			
		||||
        // Additional processing
 | 
			
		||||
        setUseNative(true);
 | 
			
		||||
        setUseNative(useNative);
 | 
			
		||||
        initLocales();
 | 
			
		||||
        addWindow();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ public class App {
 | 
			
		|||
 | 
			
		||||
    // Add a new program window
 | 
			
		||||
    void addWindow() {
 | 
			
		||||
        windows.add(new Window(this));
 | 
			
		||||
        windows.add(new MainWindow(this));
 | 
			
		||||
        windowsChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -70,12 +70,12 @@ public class App {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine the number of a window in the collection
 | 
			
		||||
    int numberOf(Window window) {
 | 
			
		||||
    int numberOf(MainWindow window) {
 | 
			
		||||
        return windows.indexOf(window) + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove a program window
 | 
			
		||||
    void removeWindow(Window window) {
 | 
			
		||||
    void removeWindow(MainWindow window) {
 | 
			
		||||
        windows.remove(window);
 | 
			
		||||
        windowsChanged();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +87,9 @@ public class App {
 | 
			
		|||
 | 
			
		||||
    // Specify whether using the native module
 | 
			
		||||
    boolean setUseNative(boolean useNative) {
 | 
			
		||||
        return this.useNative = useNative && VUE.isNativeLoaded();
 | 
			
		||||
        this.useNative = useNative && VUE.isNativeLoaded();
 | 
			
		||||
        System.out.println("Native: " + (this.useNative ? "Yes" : "No"));
 | 
			
		||||
        return this.useNative;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
package app;
 | 
			
		||||
 | 
			
		||||
// Java imports
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
 | 
			
		||||
// Project imports
 | 
			
		||||
import util.*;
 | 
			
		||||
 | 
			
		||||
// Child window
 | 
			
		||||
class ChildWindow extends JInternalFrame {
 | 
			
		||||
 | 
			
		||||
    // Instance fields
 | 
			
		||||
    MainWindow parent; // Parent application window
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                             Constructors                              //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Default constructor
 | 
			
		||||
    ChildWindow(MainWindow parent, String key) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        // Configure instanece fields
 | 
			
		||||
        this.parent = parent;
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        parent.app.getLocalizer().add(this, key);
 | 
			
		||||
        addInternalFrameListener(Util.onClose2(e->setVisible(false)));
 | 
			
		||||
        setClosable(true);
 | 
			
		||||
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
 | 
			
		||||
        setResizable(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                            Public Methods                             //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Show or hide the component
 | 
			
		||||
    public void setVisible(boolean visible) {
 | 
			
		||||
 | 
			
		||||
        // Making visible
 | 
			
		||||
        if (visible) {
 | 
			
		||||
 | 
			
		||||
            // Not currently visible: center in parent
 | 
			
		||||
            if (!isVisible()) {
 | 
			
		||||
                var parent = getParent().getParent();
 | 
			
		||||
                setLocation(
 | 
			
		||||
                    Math.max(0, (parent.getWidth () - getWidth ()) / 2),
 | 
			
		||||
                    Math.max(0, (parent.getHeight() - getHeight()) / 2)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Already visible: bring to front
 | 
			
		||||
            else moveToFront();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Change visibility
 | 
			
		||||
        super.setVisible(visible);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
package app;
 | 
			
		||||
 | 
			
		||||
// Java imports
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
 | 
			
		||||
// Console window
 | 
			
		||||
class Console extends ChildWindow {
 | 
			
		||||
 | 
			
		||||
    // Instance fields
 | 
			
		||||
    private boolean shown; // Component has been visible
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                             Constructors                              //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Default constructor
 | 
			
		||||
    Console(MainWindow parent) {
 | 
			
		||||
        super(parent, "console.title");
 | 
			
		||||
        getContentPane().setPreferredSize(new Dimension(384, 224));
 | 
			
		||||
        pack();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                            Package Methods                            //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Show the component on the first transition to debug mode
 | 
			
		||||
    void firstShow() {
 | 
			
		||||
        if (!shown)
 | 
			
		||||
            setVisible(shown = true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,19 +12,29 @@ import util.*;
 | 
			
		|||
import vue.*;
 | 
			
		||||
 | 
			
		||||
// Main application window
 | 
			
		||||
class Window extends JFrame {
 | 
			
		||||
class MainWindow extends JFrame {
 | 
			
		||||
 | 
			
		||||
    // Instance fields
 | 
			
		||||
    private App     app;     // Containing application
 | 
			
		||||
    private int     number;  // Window number within application
 | 
			
		||||
    private boolean only;    // This is the only application window
 | 
			
		||||
    private ROM     rom;     // Currently loaded ROM
 | 
			
		||||
    private File    romFile; // Currently loaded ROM file
 | 
			
		||||
    private VUE     vue;     // Emulation core context
 | 
			
		||||
    App app; // Containing application
 | 
			
		||||
    VUE vue; // Emulation core context
 | 
			
		||||
 | 
			
		||||
    // Private fields
 | 
			
		||||
    private boolean debugMode; // Window is in debug mode
 | 
			
		||||
    private int     number;    // Window number within application
 | 
			
		||||
    private boolean only;      // This is the only application window
 | 
			
		||||
    private File    pwd;       // Most recent working directory
 | 
			
		||||
    private ROM     rom;       // Currently loaded ROM
 | 
			
		||||
    private File    romFile;   // Currently loaded ROM file
 | 
			
		||||
 | 
			
		||||
    // UI components
 | 
			
		||||
    private File   pwd;   // Most recent working directory
 | 
			
		||||
    private JPanel video; // Video output
 | 
			
		||||
    private JPanel       client;  // Common client container
 | 
			
		||||
    private Console      console; // Console window
 | 
			
		||||
    private JDesktopPane desktop; // Container for child windows
 | 
			
		||||
    private Memory       memory;  // Memory window
 | 
			
		||||
    private JPanel       video;   // Video output
 | 
			
		||||
    private JMenu     mnuDebug;         // Debug menu
 | 
			
		||||
    private JMenuItem mnuFileDebugMode; // File -> Debug mode
 | 
			
		||||
    private JMenuItem mnuFileGameMode;  // File -> Game mode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +57,7 @@ class Window extends JFrame {
 | 
			
		|||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Default constructor
 | 
			
		||||
    Window(App app) {
 | 
			
		||||
    MainWindow(App app) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
| 
						 | 
				
			
			@ -56,22 +66,33 @@ class Window extends JFrame {
 | 
			
		|||
        vue      = VUE.create(app.getUseNative());
 | 
			
		||||
 | 
			
		||||
        // Configure video pane
 | 
			
		||||
        video = new JPanel() {
 | 
			
		||||
        video = new JPanel(null) {
 | 
			
		||||
            public void paintComponent(Graphics g) {
 | 
			
		||||
                super.paintComponent(g);
 | 
			
		||||
                onPaintVideo((Graphics2D) g, getWidth(), getHeight());
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        video.setPreferredSize(new Dimension(384, 224));
 | 
			
		||||
        video.setFocusable(true);
 | 
			
		||||
 | 
			
		||||
        // Configure client area
 | 
			
		||||
        client = new JPanel(new BorderLayout());
 | 
			
		||||
        client.add(video, BorderLayout.CENTER);
 | 
			
		||||
 | 
			
		||||
        // Configure window
 | 
			
		||||
        addWindowListener(Util.onClose(e->onClose()));
 | 
			
		||||
        setContentPane(video);
 | 
			
		||||
        setContentPane(client);
 | 
			
		||||
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
 | 
			
		||||
        setIconImage(APPICON);
 | 
			
		||||
        setJMenuBar(initMenus());
 | 
			
		||||
        app.getLocalizer().add(this, "app.title.default");
 | 
			
		||||
 | 
			
		||||
        // Configure child windows
 | 
			
		||||
        desktop = new JDesktopPane();
 | 
			
		||||
        desktop.setBackground(SystemColor.controlShadow);
 | 
			
		||||
        desktop.add(console = new Console(this));
 | 
			
		||||
        desktop.add(memory  = new Memory (this));
 | 
			
		||||
 | 
			
		||||
        // Display window
 | 
			
		||||
        pack();
 | 
			
		||||
        setLocationRelativeTo(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -89,10 +110,30 @@ class Window extends JFrame {
 | 
			
		|||
        var bar = new JMenuBar();
 | 
			
		||||
        var loc = app.getLocalizer();
 | 
			
		||||
        bar.setBorder(null);
 | 
			
		||||
        bar.add(initMenuFile(loc));
 | 
			
		||||
        bar.add(initMenuFile (loc));
 | 
			
		||||
        bar.add(initMenuDebug(loc));
 | 
			
		||||
        return bar;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize the Debug menu
 | 
			
		||||
    private JMenu initMenuDebug(Localizer loc) {
 | 
			
		||||
        mnuDebug = new JMenu();
 | 
			
		||||
        loc.add(mnuDebug, "app.debug.(menu)");
 | 
			
		||||
        mnuDebug.setVisible(false);
 | 
			
		||||
 | 
			
		||||
        var mnuDebugConsole = new JMenuItem();
 | 
			
		||||
        loc.add(mnuDebugConsole, "app.debug.console");
 | 
			
		||||
        mnuDebugConsole.addActionListener(e->console.setVisible(true));
 | 
			
		||||
        mnuDebug.add(mnuDebugConsole);
 | 
			
		||||
 | 
			
		||||
        var mnuDebugMemory = new JMenuItem();
 | 
			
		||||
        loc.add(mnuDebugMemory, "app.debug.memory");
 | 
			
		||||
        mnuDebugMemory.addActionListener(e->memory.setVisible(true));
 | 
			
		||||
        mnuDebug.add(mnuDebugMemory);
 | 
			
		||||
 | 
			
		||||
        return mnuDebug;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize the File menu
 | 
			
		||||
    private JMenu initMenuFile(Localizer loc) {
 | 
			
		||||
        var mnuFile = new JMenu();
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +144,17 @@ class Window extends JFrame {
 | 
			
		|||
        mnuFileLoadRom.addActionListener(e->onLoadROM());
 | 
			
		||||
        mnuFile.add(mnuFileLoadRom);
 | 
			
		||||
 | 
			
		||||
        mnuFileDebugMode = new JMenuItem();
 | 
			
		||||
        loc.add(mnuFileDebugMode, "app.file.debug_mode");
 | 
			
		||||
        mnuFileDebugMode.addActionListener(e->onDebugMode(true));
 | 
			
		||||
        mnuFile.add(mnuFileDebugMode);
 | 
			
		||||
 | 
			
		||||
        mnuFileGameMode = new JMenuItem();
 | 
			
		||||
        loc.add(mnuFileGameMode, "app.file.game_mode");
 | 
			
		||||
        mnuFileGameMode.addActionListener(e->onDebugMode(false));
 | 
			
		||||
        mnuFileGameMode.setVisible(false);
 | 
			
		||||
        mnuFile.add(mnuFileGameMode);
 | 
			
		||||
 | 
			
		||||
        mnuFile.addSeparator();
 | 
			
		||||
 | 
			
		||||
        var mnuFileNewWindow = new JMenuItem();
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +176,11 @@ class Window extends JFrame {
 | 
			
		|||
    //                            Package Methods                            //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Refresh all debug views
 | 
			
		||||
    void refreshDebug() {
 | 
			
		||||
        memory.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // A window has been added to or removed from the program state
 | 
			
		||||
    void windowsChanged(int number, boolean only) {
 | 
			
		||||
        this.number = number;
 | 
			
		||||
| 
						 | 
				
			
			@ -145,6 +202,32 @@ class Window extends JFrame {
 | 
			
		|||
        vue.dispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // File -> Debug mode, File -> Game mode
 | 
			
		||||
    private void onDebugMode(boolean debugMode) {
 | 
			
		||||
        this.debugMode = debugMode;
 | 
			
		||||
        mnuFileDebugMode.setVisible(!debugMode);
 | 
			
		||||
        mnuFileGameMode .setVisible( debugMode);
 | 
			
		||||
 | 
			
		||||
        // Transition to debug mode
 | 
			
		||||
        if (debugMode) {
 | 
			
		||||
            client.remove(video);
 | 
			
		||||
            client.add(desktop, BorderLayout.CENTER);
 | 
			
		||||
            console.setContentPane(video);
 | 
			
		||||
            console.firstShow();
 | 
			
		||||
            mnuDebug.setVisible(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Transition to game mode
 | 
			
		||||
        else {
 | 
			
		||||
            client.remove(desktop);
 | 
			
		||||
            client.add(video, BorderLayout.CENTER);
 | 
			
		||||
            mnuDebug.setVisible(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        client.revalidate();
 | 
			
		||||
        client.repaint();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // File -> Load ROM
 | 
			
		||||
    private void onLoadROM() {
 | 
			
		||||
        var loc = app.getLocalizer();
 | 
			
		||||
| 
						 | 
				
			
			@ -200,6 +283,7 @@ class Window extends JFrame {
 | 
			
		|||
        var bytes = rom.toByteArray();
 | 
			
		||||
        // Pause emulation
 | 
			
		||||
        vue.setROM(bytes, 0, bytes.length);
 | 
			
		||||
        refreshDebug();
 | 
			
		||||
        // Resume emulation
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,274 @@
 | 
			
		|||
package app;
 | 
			
		||||
 | 
			
		||||
// Java imports
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.awt.event.*;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
 | 
			
		||||
// Project imports
 | 
			
		||||
import util.*;
 | 
			
		||||
 | 
			
		||||
// Memory viewer and hex editor window
 | 
			
		||||
class Memory extends ChildWindow {
 | 
			
		||||
 | 
			
		||||
    // Private fields
 | 
			
		||||
    private int  address; // Address of top row
 | 
			
		||||
    private Font font;    // Display font
 | 
			
		||||
 | 
			
		||||
    // UI components
 | 
			
		||||
    private JPanel client;     // Client area
 | 
			
		||||
    private JLabel fontHeight; // Font height proxy
 | 
			
		||||
    private ArrayList<Row> rows; // Rows of text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                                Classes                                //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // One row of output
 | 
			
		||||
    private class Row {
 | 
			
		||||
        JLabel   address; // Address (row header)
 | 
			
		||||
        JLabel[] bytes;   // Hexadecimal bytes
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                             Constructors                              //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Default constructor
 | 
			
		||||
    Memory(MainWindow parent) {
 | 
			
		||||
        super(parent, "memory.title");
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        address    = 0x00000000;
 | 
			
		||||
        font       = new Font(Util.fontFamily(new String[]
 | 
			
		||||
            { "Consolas", Font.MONOSPACED } ), Font.PLAIN, 14);
 | 
			
		||||
        fontHeight = new JLabel(".");
 | 
			
		||||
        rows       = new ArrayList<Row>();
 | 
			
		||||
 | 
			
		||||
        // Configure client area
 | 
			
		||||
        client = new JPanel(null);
 | 
			
		||||
        client.addComponentListener(Util.onResize(e->onResize()));
 | 
			
		||||
        client.addKeyListener(Util.onKey(e->onKeyDown(e), null));
 | 
			
		||||
        client.addMouseWheelListener(e->onWheel(e));
 | 
			
		||||
        client.setFocusable(true);
 | 
			
		||||
        client.setPreferredSize(new Dimension(640, 480));
 | 
			
		||||
        client.setBackground(SystemColor.window);
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        setContentPane(client);
 | 
			
		||||
        setFont2(font);
 | 
			
		||||
        pack();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                            Package Methods                            //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update the display
 | 
			
		||||
    void refresh() {
 | 
			
		||||
 | 
			
		||||
        // The element is not ready
 | 
			
		||||
        if (client == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure working variables
 | 
			
		||||
        int height     = client.getHeight();
 | 
			
		||||
        int lineHeight = fontHeight.getPreferredSize().height;
 | 
			
		||||
        int count      = (height + lineHeight - 1) / lineHeight;
 | 
			
		||||
        var data       = new byte[count * 16];
 | 
			
		||||
        var widths     = new int[2];
 | 
			
		||||
 | 
			
		||||
        // Retrieve all visible bytes from the emulation context
 | 
			
		||||
        parent.vue.read(address, data, 0, data.length);
 | 
			
		||||
 | 
			
		||||
        // Update visible rows
 | 
			
		||||
        for (int x = 0; x < count; x++) {
 | 
			
		||||
            Row row;
 | 
			
		||||
 | 
			
		||||
            // Retrieve the row if it exists, make a new one otherwise
 | 
			
		||||
            if (x < rows.size())
 | 
			
		||||
                row = rows.get(x);
 | 
			
		||||
            else row = createRow();
 | 
			
		||||
 | 
			
		||||
            // Configure the row
 | 
			
		||||
            update(row, address + x * 16, data, x * 16);
 | 
			
		||||
            setVisible(row, true);
 | 
			
		||||
            measure(row, widths);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Hide any rows that are not visible
 | 
			
		||||
        for (int x = count; x < rows.size(); x++)
 | 
			
		||||
            setVisible(rows.get(x), false);
 | 
			
		||||
 | 
			
		||||
        // Position components
 | 
			
		||||
        for (int x = 0; x < rows.size(); x++)
 | 
			
		||||
            arrange(rows.get(x), x * lineHeight, widths, lineHeight);
 | 
			
		||||
 | 
			
		||||
        // Finalize layout
 | 
			
		||||
        client.revalidate();
 | 
			
		||||
        client.repaint();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new font
 | 
			
		||||
    void setFont2(Font font) {
 | 
			
		||||
        this.font = font;
 | 
			
		||||
        fontHeight.setFont(font);
 | 
			
		||||
        for (var row : rows) {
 | 
			
		||||
            row.address.setFont(font);
 | 
			
		||||
            for (var label : row.bytes)
 | 
			
		||||
                label.setFont(font);
 | 
			
		||||
        }
 | 
			
		||||
        onResize();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                            Event Handlers                             //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Key down
 | 
			
		||||
    private void onKeyDown(KeyEvent e) {
 | 
			
		||||
        int     code = e.getKeyCode();
 | 
			
		||||
        int     mods = e.getModifiersEx();
 | 
			
		||||
        boolean alt  = (mods & InputEvent.ALT_DOWN_MASK ) != 0;
 | 
			
		||||
        boolean ctrl = (mods & InputEvent.CTRL_DOWN_MASK) != 0;
 | 
			
		||||
        int     tall = Math.max(1, tall(false));
 | 
			
		||||
 | 
			
		||||
        // No Alt combinations
 | 
			
		||||
        if (alt) return;
 | 
			
		||||
 | 
			
		||||
        // Goto
 | 
			
		||||
        if (ctrl && code == KeyEvent.VK_G) {
 | 
			
		||||
            String addr = JOptionPane.showInputDialog(
 | 
			
		||||
                this, "Goto:", "Goto", JOptionPane.PLAIN_MESSAGE);
 | 
			
		||||
            if (addr != null && addr.trim().length() != 0) {
 | 
			
		||||
                try { setAddress((int) Long.parseLong(addr, 16)); }
 | 
			
		||||
                catch (Exception x) { }
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Seek
 | 
			
		||||
        switch (code) {
 | 
			
		||||
            case KeyEvent.VK_UP       : setAddress(address -        16); break;
 | 
			
		||||
            case KeyEvent.VK_DOWN     : setAddress(address +        16); break;
 | 
			
		||||
            case KeyEvent.VK_PAGE_UP  : setAddress(address - tall * 16); break;
 | 
			
		||||
            case KeyEvent.VK_PAGE_DOWN: setAddress(address + tall * 16); break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Client resize
 | 
			
		||||
    private void onResize() {
 | 
			
		||||
        refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mouse wheel
 | 
			
		||||
    private void onWheel(MouseWheelEvent e) {
 | 
			
		||||
        int     amount = e.getUnitsToScroll();
 | 
			
		||||
        int     mods   = e.getModifiersEx();
 | 
			
		||||
        boolean alt    = (mods & InputEvent.ALT_DOWN_MASK ) != 0;
 | 
			
		||||
        boolean ctrl   = (mods & InputEvent.CTRL_DOWN_MASK) != 0;
 | 
			
		||||
 | 
			
		||||
        // No Alt or Ctrl combinations
 | 
			
		||||
        if (amount == 0 || alt || ctrl)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Seek
 | 
			
		||||
        setAddress(address + 16 * amount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
    //                            Private Methods                            //
 | 
			
		||||
    ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Position the elements of a row
 | 
			
		||||
    private void arrange(Row row, int y, int[] widths, int height) {
 | 
			
		||||
        int spacing = (height + 1) / 2;
 | 
			
		||||
 | 
			
		||||
        // Size and position the address header
 | 
			
		||||
        row.address.setSize(row.address.getPreferredSize());
 | 
			
		||||
        row.address.setLocation(0, y);
 | 
			
		||||
 | 
			
		||||
        // Size and position the byte labels
 | 
			
		||||
        for (int z = 0, x = widths[0] + height; z < 16; z++) {
 | 
			
		||||
            var label = row.bytes[z];
 | 
			
		||||
            label.setBounds(x, y, widths[1], height);
 | 
			
		||||
            x += widths[1] + (z == 7 ? height : spacing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a new row of output
 | 
			
		||||
    private Row createRow() {
 | 
			
		||||
        var row = new Row();
 | 
			
		||||
        row.address =
 | 
			
		||||
 | 
			
		||||
        // Address label
 | 
			
		||||
        row.address = new JLabel();
 | 
			
		||||
        row.address.setFont(font);
 | 
			
		||||
        row.address.setForeground(SystemColor.windowText);
 | 
			
		||||
        row.address.setVisible(false);
 | 
			
		||||
        client.add(row.address);
 | 
			
		||||
 | 
			
		||||
        // Byte labels
 | 
			
		||||
        row.bytes = new JLabel[16];
 | 
			
		||||
        for (int x = 0; x < row.bytes.length; x++) {
 | 
			
		||||
            var label = row.bytes[x] = new JLabel();
 | 
			
		||||
            label.setFont(font);
 | 
			
		||||
            label.setForeground(SystemColor.windowText);
 | 
			
		||||
            label.setVisible(false);
 | 
			
		||||
            client.add(label);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add the row to the collection
 | 
			
		||||
        rows.add(row);
 | 
			
		||||
        return row;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Measure the minimum column widths for a row
 | 
			
		||||
    private void measure(Row row, int[] widths) {
 | 
			
		||||
        widths[0] = Math.max(widths[0], row.address.getPreferredSize().width);
 | 
			
		||||
        for (int x = 0; x < 16; x++)
 | 
			
		||||
            widths[1] = Math.max(widths[1],
 | 
			
		||||
                row.bytes[x].getPreferredSize().width);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the address of the top row of output
 | 
			
		||||
    private void setAddress(int address) {
 | 
			
		||||
        this.address = address & 0xFFFFFFF0;
 | 
			
		||||
        refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Show or hide a row
 | 
			
		||||
    private void setVisible(Row row, boolean visible) {
 | 
			
		||||
        row.address.setVisible(visible);
 | 
			
		||||
        for (var label : row.bytes)
 | 
			
		||||
            label.setVisible(visible);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine how many rows of output are visible
 | 
			
		||||
    private int tall(boolean partial) {
 | 
			
		||||
        int lineHeight = fontHeight.getPreferredSize().height;
 | 
			
		||||
        return lineHeight == 0 ? 0 :
 | 
			
		||||
            (client.getHeight() + (partial ? lineHeight + 1 : 0)) / lineHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update the text of a row
 | 
			
		||||
    private void update(Row row, int address, byte[] data, int offset) {
 | 
			
		||||
        row.address.setText(String.format("%08X", address));
 | 
			
		||||
        for (var label : row.bytes)
 | 
			
		||||
            label.setText(String.format("%02X", data[offset++] & 0xFF));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,11 +58,22 @@ public final class Util {
 | 
			
		|||
        new BOM(new byte[] { - 1, - 2      }, StandardCharsets.UTF_16LE),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Available font families
 | 
			
		||||
    private static final String[] FONTS;
 | 
			
		||||
 | 
			
		||||
    // Filesystem state manager for the current .jar (if any)
 | 
			
		||||
    private static final FileSystem JARFS;
 | 
			
		||||
 | 
			
		||||
    // Static initializer
 | 
			
		||||
    static {
 | 
			
		||||
 | 
			
		||||
        // Font families
 | 
			
		||||
        var fonts = GraphicsEnvironment
 | 
			
		||||
            .getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
 | 
			
		||||
        Arrays.sort(fonts, String.CASE_INSENSITIVE_ORDER);
 | 
			
		||||
        FONTS = fonts;
 | 
			
		||||
 | 
			
		||||
        // .jar filesystem
 | 
			
		||||
        FileSystem fs = null;
 | 
			
		||||
        try {
 | 
			
		||||
            fs = FileSystems.newFileSystem(
 | 
			
		||||
| 
						 | 
				
			
			@ -153,6 +164,15 @@ public final class Util {
 | 
			
		|||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Select a font family from a list, if avaiable
 | 
			
		||||
    public static String fontFamily(String[] families) {
 | 
			
		||||
        for (String family : families)
 | 
			
		||||
            if (Arrays.binarySearch(FONTS, family,
 | 
			
		||||
                String.CASE_INSENSITIVE_ORDER) >= 0)
 | 
			
		||||
                return family;
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Read an image file as an icon
 | 
			
		||||
    public static Icon iconRead(String filename) {
 | 
			
		||||
        BufferedImage img = imageRead(filename);
 | 
			
		||||
| 
						 | 
				
			
			@ -200,6 +220,13 @@ public final class Util {
 | 
			
		|||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Produce a list of available font family names
 | 
			
		||||
    public static String[] listFonts() {
 | 
			
		||||
        var ret = new String[FONTS.length];
 | 
			
		||||
        System.arraycopy(FONTS, 0, ret, 0, FONTS.length);
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Produce a WindowListener with a functional interface
 | 
			
		||||
    public static WindowListener onClose(OnClose close) {
 | 
			
		||||
        return new WindowListener() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue