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.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>();
|
||||
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
|
||||
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
|
||||
private VUE vue; // Emulation core context
|
||||
|
||||
// UI components
|
||||
private File pwd; // Most recent working directory
|
||||
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