New UI layout, ROM loading, Font selection

- The UI layout has changed, and will likely change again in the future.

- Symon can now re-load ROM images from the File menu, under "Load ROM..."

- Font size can be changed under the "View" menu
This commit is contained in:
Seth Morabito 2012-12-05 23:19:34 -08:00
parent a7d9239ef1
commit 38a4458aff
14 changed files with 449 additions and 310 deletions

View File

@ -47,20 +47,33 @@ Maven will build Symon, run unit tests, and produce a jar file in the
Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type:
$ java -jar symon-0.6-jar-with-dependencies.jar
$ java -jar symon-0.6-snapshot.jar
When Symon is running, you should be presented with a simple graphical
interface.
### 3.2 ROM images
### 3.2 Loading A Program
The simulator requires a 16KB ROM image loaded at address $C000 to $FFFF to
work properly. Without a ROM in memory, the simulator will not be able to
reset, since the reset vector for the 6502 is located in this address space.
Programs in the form of raw binary object files can be loaded directly
into memory with the "Load" button.
By default, any 16KB file named 'rom.bin' that exists in the same directory
where Symon is launched will be loaded as a ROM image. ROM images can also
be swapped out at run-time with the "Load ROM Image..." in the File menu.
Right now, all programs are loaded starting at addres $0300. After
loading, the simulated CPU's reset vector is loaded with the values
$00, $03, and the CPU is reset.
The "samples" directory contains a ROM image named 'ehbasic.rom', containing
Lee Davison's Enhanced 6502 BASIC. This serves as a good starting point for
exploration.
### 3.3 Loading A Program
In addition to ROM images, programs in the form of raw binary object files can
be loaded directly into memory from "Load Program..." in the File menu.
Programs are loaded starting at addres $0300. After loading the program, the
simulated CPU's reset vector is loaded with the values $00, $03, and the CPU is
reset.
There are two very simple sample program in the "samples" directory,
for testing.
@ -69,33 +82,23 @@ for testing.
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
The sample directory also contains a ROM image of Lee Davison's
Ehanced 6502 BASIC. For instructions on loading the rom, please see
the README file in that directory.
### 3.3 ROM files
Any 12KB file named 'rom.bin' that exists in the same directory where
Symon is launched will be loaded at address $d000. If the file is
larger than 12KB, loading will fail. This functionality will be
improved in a future release!
### 3.4 Running
After loading a program or ROM image, clicking "Run" will start the simulator
running at address $0300.
running.
## 4.0 To Do
- Feedback (in the form of dialogs, status bar, etc).
- Better ROM image handling
- Better debugging tools from the UI, including memory inspection,
disassembly, breakpoints, and execution tracing.
- Better ROM loading (and re-loading)
- More accurate timing.
- Interrupt handling!
- Smarter interrupt handling.
- UI needs a ton more polish.
@ -109,8 +112,6 @@ running at address $0300.
- Implement CMOS 65C02 instructions and NMOS / CMOS mode flag.
- Allow a flag to disable breaking to monitor on BRK.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.

View File

@ -44,7 +44,7 @@
<dependency>
<groupId>com.grahamedgecombe.jterminal</groupId>
<artifactId>jterminal</artifactId>
<version>1.0.2.1-loomcom</version>
<version>1.0.2.2-loomcom</version>
</dependency>
</dependencies>

View File

@ -3,7 +3,7 @@
;;
.alias iobase $8000
.alias iobase $8800
.alias iostatus [iobase + 1]
.alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3]
@ -11,7 +11,7 @@
.org $0300
start: cli
lda #$09
lda #$0b
sta iocmd ; Set command status
lda #$1a
sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud

Binary file not shown.

Binary file not shown.

View File

@ -37,10 +37,12 @@ RES_vec
; Initialize the ACIA
ACIA_init
LDA #$09
STA ACIAcommand
LDA #$1A ; Set output for 8-N-1 2400
STA ACIAcontrol
LDA #$00
STA ACIAstatus ; Soft reset
LDA #$0B
STA ACIAcommand ; Parity disabled, IRQ disabled
LDA #$1E
STA ACIAcontrol ; Set output for 8-N-1 9600
; set up vectors and interrupt code, copy them to page 2
@ -77,25 +79,33 @@ LAB_nokey
LAB_dowarm
JMP LAB_WARM ; do EhBASIC warm start
; byte out to simulated ACIA
; byte out to ACIA
ACIAout
PHA ; save accumulator
ACIAout_w
@loop
LDA ACIAstatus ; Read 6551 status
AND #$10 ; Is tx buffer full?
BEQ ACIAout_w ; if not, loop back
BEQ @loop ; if not, loop back
PLA ; Otherwise, restore accumulator
STA ACIAdata ; write byte to 6551
RTS
; byte in from simulated ACIA
;
; byte in from ACIA. This subroutine will also force
; all lowercase letters to be uppercase.
;
ACIAin
LDA ACIAstatus ; Read 6551 status
AND #$08 ;
BEQ LAB_nobyw ; If rx buffer empty, no byte
LDA ACIAdata ; Read byte from 6551
CMP #'a' ; Is it < 'a'?
BCC @done ; Yes, we're done
CMP #'{' ; Is it >= '{'?
BCS @done ; Yes, we're done
AND #$5f ; Otherwise, mask to uppercase
@done
SEC ; Flag byte received
RTS
@ -137,16 +147,19 @@ NMI_CODE
END_CODE
; sign on string
LAB_mess
.byte $0D,$0A,"6502 EhBASIC [C]old/[W]arm ?",$00
; sign on string
.byte $0D,$0A,"Symon 6502 SBC (c) 2012, Seth Morabito"
.byte $0D,$0A,"[C]old/[W]arm ?",$00
; system vectors
.segment "VECTORS"
.org $FFFA
.word 0 ; NMI vector
.word NMI_vec ; NMI vector
.word RES_vec ; RESET vector
.word 0 ; IRQ vector
.word IRQ_vec ; IRQ vector

View File

@ -3,7 +3,7 @@
;;
.alias iobase $8000
.alias iobase $8800
.alias iostatus [iobase + 1]
.alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3]
@ -11,7 +11,7 @@
.org $0300
start: cli
lda #$09
lda #$0b
sta iocmd ; Set command status
lda #$1a
sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud

Binary file not shown.

View File

@ -6,17 +6,17 @@ public interface Preferences {
public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
public static final int DEFAULT_ACIA_ADDRESS = 0xc000;
public static final int DEFAULT_BORDER_WIDTH = 10;
public static final boolean DEFAULT_HALT_ON_BREAK = true;
public JDialog getDialog();
public int getProgramStartAddress();
public int getAciaAddress();
public int getBorderWidth();
public boolean getHaltOnBreak();
public void updateUi();
}

View File

@ -33,9 +33,9 @@ import com.loomcom.symon.exceptions.SymonException;
import com.loomcom.symon.ui.Console;
import com.loomcom.symon.ui.PreferencesDialog;
import com.loomcom.symon.ui.StatusPanel;
import sun.rmi.rmic.iiop.DirectoryLoader;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@ -55,7 +55,7 @@ import java.util.logging.Logger;
* with a basic 80x25 character display.
*
*/
public class Simulator implements ActionListener, Observer {
public class Simulator implements Observer {
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
@ -75,6 +75,9 @@ public class Simulator implements ActionListener, Observer {
private static final int ROM_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000;
private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
// Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
// to refresh the view on every simulated clock cycle. Instead, we will only refresh the view after this
// number of steps when running normally.
@ -100,25 +103,37 @@ public class Simulator implements ActionListener, Observer {
// requested
private int stepsSinceLastUpdate = 0;
private JFrame mainWindow;
private RunLoop runLoop;
private Console console;
private StatusPanel statusPane;
/**
* The Main Window is the primary control point for the simulator.
* It is in charge of the menu, and sub-windows. It also shows the
* CPU status at all times.
*/
private JFrame mainWindow;
private JButton runStopButton;
private JButton stepButton;
private JButton resetButton;
/**
* The Console Window is connected to the ACIA's TX and RX lines.
*/
private JFrame consoleWindow;
// The most recently read key code
private char keyBuffer;
/**
* The Trace Window shows the most recent 50,000 CPU states.
*/
private JFrame traceWindow;
// TODO: loadProgramItem seriously violates encapsulation!
// A far better solution would be to extend JMenu and add callback
// methods to enable and disable menus as required.
/**
* The Zero Page Window shows the contents of page 0.
*/
private JFrame zeroPageWindow;
// Menu Items
private JMenuItem loadProgramItem;
private JMenuItem loadRomItem;
private SimulatorMenu menuBar;
private RunLoop runLoop;
private Console console;
private StatusPanel statusPane;
private JButton runStopButton;
private JButton stepButton;
private JButton resetButton;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
@ -154,10 +169,11 @@ public class Simulator implements ActionListener, Observer {
mainWindow.getContentPane().setLayout(new BorderLayout());
// The Menu
mainWindow.setJMenuBar(createMenuBar());
menuBar = new SimulatorMenu();
mainWindow.setJMenuBar(menuBar);
// UI components used for I/O.
this.console = new com.loomcom.symon.ui.Console();
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT);
this.statusPane = new StatusPanel();
// File Chooser
@ -166,15 +182,11 @@ public class Simulator implements ActionListener, Observer {
preferences.addObserver(this);
// Panel for Console and Buttons
JPanel controlsContainer = new JPanel();
JPanel consoleContainer = new JPanel();
JPanel buttonContainer = new JPanel();
Dimension buttonPanelSize = new Dimension(console.getWidth(), 36);
buttonContainer.setMinimumSize(buttonPanelSize);
buttonContainer.setMaximumSize(buttonPanelSize);
buttonContainer.setPreferredSize(buttonPanelSize);
controlsContainer.setLayout(new BorderLayout());
consoleContainer.setLayout(new BorderLayout());
consoleContainer.setBorder(new EmptyBorder(10, 10, 10, 0));
buttonContainer.setLayout(new FlowLayout());
runStopButton = new JButton("Run");
@ -186,8 +198,8 @@ public class Simulator implements ActionListener, Observer {
buttonContainer.add(resetButton);
// Left side - console
controlsContainer.add(console, BorderLayout.PAGE_START);
mainWindow.getContentPane().add(controlsContainer, BorderLayout.LINE_START);
consoleContainer.add(console, BorderLayout.CENTER);
mainWindow.getContentPane().add(consoleContainer, BorderLayout.LINE_START);
// Right side - status pane
mainWindow.getContentPane().add(statusPane, BorderLayout.LINE_END);
@ -195,9 +207,27 @@ public class Simulator implements ActionListener, Observer {
// Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(this);
stepButton.addActionListener(this);
resetButton.addActionListener(this);
runStopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
handleStop();
} else {
handleStart();
}
}
});
stepButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleStep();
}
});
resetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleReset();
}
});
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@ -207,78 +237,6 @@ public class Simulator implements ActionListener, Observer {
mainWindow.setVisible(true);
}
private JMenuBar createMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
loadProgramItem = new JMenuItem("Load Program");
loadProgramItem.setMnemonic(KeyEvent.VK_L);
loadRomItem = new JMenuItem("Load ROM...");
loadRomItem.setMnemonic(KeyEvent.VK_R);
JMenuItem prefsItem = new JMenuItem("Preferences...");
prefsItem.setMnemonic(KeyEvent.VK_P);
JMenuItem quitItem = new JMenuItem("Quit");
quitItem.setMnemonic(KeyEvent.VK_Q);
loadProgramItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleProgramLoad();
}
});
loadRomItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleRomLoad();
}
});
prefsItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
showAndUpdatePreferences();
}
});
quitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleQuit();
}
});
fileMenu.add(loadProgramItem);
fileMenu.add(loadRomItem);
fileMenu.add(prefsItem);
fileMenu.add(quitItem);
return menuBar;
}
public void showAndUpdatePreferences() {
preferences.getDialog().setVisible(true);
}
/**
* Receive an ActionEvent from the UI, and act on it.
*/
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == resetButton) {
coldReset();
} else if (actionEvent.getSource() == stepButton) {
handleStep();
} else if (actionEvent.getSource() == runStopButton) {
if (runLoop != null && runLoop.isRunning()) {
handleStop();
} else {
handleStart();
}
}
}
private void handleStart() {
// Shift focus to the console.
console.requestFocus();
@ -311,85 +269,10 @@ public class Simulator implements ActionListener, Observer {
// TODO: Update memory window, if frame is visible.
}
// TODO: Alert user of errors.
private void handleRomLoad() {
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
File romFile = fileChooser.getSelectedFile();
if (romFile.canRead()) {
long fileSize = romFile.length();
if (fileSize != ROM_SIZE) {
throw new IOException("ROM file must be exactly " + String.valueOf(fileSize) + " bytes.");
} else {
if (rom != null) {
// Unload the existing ROM image.
bus.removeDevice(rom);
}
// Load the new ROM image
rom = Memory.makeROM(ROM_BASE, ROM_SIZE, romFile);
bus.addDevice(rom);
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
String.format("0x%04X", ROM_BASE));
}
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read file: " + ex.getMessage());
ex.printStackTrace();
} catch (MemoryRangeException ex) {
logger.log(Level.SEVERE, "Memory range error loading ROM");
ex.printStackTrace();
}
}
/**
* Display a file chooser prompting the user to load a binary program.
* After the user selects a file, read it in starting at PROGRAM_START_ADDRESS.
/*
* Perform a reset.
*/
private void handleProgramLoad() {
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
File f = fileChooser.getSelectedFile();
if (f.canRead()) {
long fileSize = f.length();
if (fileSize > MEMORY_SIZE) {
throw new IOException("Program will not fit in available memory.");
} else {
byte[] program = new byte[(int) fileSize];
int i = 0;
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
while (dis.available() != 0) {
program[i++] = dis.readByte();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
console.reset();
}
});
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
}
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read file: " + ex.getMessage());
ex.printStackTrace();
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory access error loading program");
ex.printStackTrace();
}
}
private void coldReset() {
private void handleReset() {
if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop();
runLoop.interrupt();
@ -412,7 +295,6 @@ public class Simulator implements ActionListener, Observer {
});
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
ex.printStackTrace();
}
}
@ -429,17 +311,6 @@ public class Simulator implements ActionListener, Observer {
}
}
/**
* Handle a request to quit.
*/
private void handleQuit() {
if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop();
runLoop.interrupt();
}
System.exit(0);
}
/**
* Perform a single step of the simulated system.
*/
@ -516,7 +387,7 @@ public class Simulator implements ActionListener, Observer {
Simulator simulator = new Simulator();
simulator.createAndShowUi();
// Reset the simulator.
simulator.coldReset();
simulator.handleReset();
} catch (Exception e) {
e.printStackTrace();
}
@ -533,8 +404,6 @@ public class Simulator implements ActionListener, Observer {
public void update(Observable observable, Object o) {
// Instance equality should work here, there is only one instance.
if (observable == preferences) {
// TODO: Update ACIA base address if it has changed.
int oldBorderWidth = console.getBorderWidth();
if (oldBorderWidth != preferences.getBorderWidth()) {
// Resize the main window if the border width has changed.
@ -544,7 +413,6 @@ public class Simulator implements ActionListener, Observer {
}
}
/**
* The main run thread.
*/
@ -569,16 +437,14 @@ public class Simulator implements ActionListener, Observer {
console.startListening();
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
loadProgramItem.setEnabled(false);
loadRomItem.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
}
});
try {
// TODO: Interrupts - both software and hardware. i.e., jump to address stored in FFFE/FFFF on BRK.
while (isRunning && !cpu.getBreakFlag()) {
while (isRunning && !(preferences.getHaltOnBreak() && cpu.getBreakFlag())) {
step();
}
} catch (SymonException ex) {
@ -591,16 +457,235 @@ public class Simulator implements ActionListener, Observer {
console.stopListening();
// Allow step while the simulator is stopped
stepButton.setEnabled(true);
loadProgramItem.setEnabled(true);
loadRomItem.setEnabled(true);
menuBar.simulatorDidStop();
runStopButton.setText("Run");
// Now update the state
statusPane.updateState(cpu);
}
});
logger.log(Level.INFO, "Exiting main run loop. BREAK=" + cpu.getBreakBit() + "; RUN_FLAG=" + isRunning);
isRunning = false;
}
}
class LoadProgramAction extends AbstractAction {
public LoadProgramAction() {
super("Load Program...", null);
putValue(SHORT_DESCRIPTION, "Load a program into memory");
putValue(MNEMONIC_KEY, KeyEvent.VK_L);
}
public void actionPerformed(ActionEvent actionEvent) {
// TODO: Error dialogs on failure.
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
File f = fileChooser.getSelectedFile();
if (f.canRead()) {
long fileSize = f.length();
if (fileSize > MEMORY_SIZE) {
throw new IOException("Program will not fit in available memory.");
} else {
byte[] program = new byte[(int) fileSize];
int i = 0;
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
while (dis.available() != 0) {
program[i++] = dis.readByte();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
console.reset();
}
});
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
}
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read program file: " + ex.getMessage());
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory access error loading program: " + ex.getMessage());
}
}
}
class LoadRomAction extends AbstractAction {
public LoadRomAction() {
super("Load ROM...", null);
putValue(SHORT_DESCRIPTION, "Load a ROM image");
putValue(MNEMONIC_KEY, KeyEvent.VK_R);
}
public void actionPerformed(ActionEvent actionEvent) {
// TODO: Error dialogs on failure.
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
File romFile = fileChooser.getSelectedFile();
if (romFile.canRead()) {
long fileSize = romFile.length();
if (fileSize != ROM_SIZE) {
throw new IOException("ROM file must be exactly " + String.valueOf(ROM_SIZE) + " bytes.");
} else {
if (rom != null) {
// Unload the existing ROM image.
bus.removeDevice(rom);
}
// Load the new ROM image
rom = Memory.makeROM(ROM_BASE, ROM_SIZE, romFile);
bus.addDevice(rom);
// Now, reset
cpu.reset();
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
String.format("0x%04X", ROM_BASE));
}
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read ROM file: " + ex.getMessage());
} catch (MemoryRangeException ex) {
logger.log(Level.SEVERE, "Memory range error while loading ROM file: " + ex.getMessage());
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory access error while loading ROM file: " + ex.getMessage());
}
}
}
class ShowPrefsAction extends AbstractAction {
public ShowPrefsAction() {
super("Preferences...", null);
putValue(SHORT_DESCRIPTION, "Show Preferences Dialog");
putValue(MNEMONIC_KEY, KeyEvent.VK_P);
}
public void actionPerformed(ActionEvent actionEvent) {
preferences.getDialog().setVisible(true);
}
}
class QuitAction extends AbstractAction {
public QuitAction() {
super("Quit", null);
putValue(SHORT_DESCRIPTION, "Exit the Simulator");
putValue(MNEMONIC_KEY, KeyEvent.VK_Q);
}
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop();
runLoop.interrupt();
}
System.exit(0);
}
}
class SetFontAction extends AbstractAction {
private int size;
public SetFontAction(int size) {
super(Integer.toString(size) + " pt", null);
this.size = size;
putValue(SHORT_DESCRIPTION, "Set font to " + Integer.toString(size) + "pt.");
}
public void actionPerformed(ActionEvent actionEvent) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
}
});
}
}
class SimulatorMenu extends JMenuBar {
// Menu Items
private JMenuItem loadProgramItem;
private JMenuItem loadRomItem;
/**
* Create a new SimulatorMenu instance.
*/
public SimulatorMenu() {
initMenu();
}
/**
* Disable menu items that should not be available during simulator execution.
*/
public void simulatorDidStart() {
loadProgramItem.setEnabled(false);
loadRomItem.setEnabled(false);
}
/**
* Enable menu items that should be available while the simulator is stopped.
*/
public void simulatorDidStop() {
loadProgramItem.setEnabled(true);
loadRomItem.setEnabled(true);
}
private void initMenu() {
/*
* File Menu
*/
JMenu fileMenu = new JMenu("File");
loadProgramItem = new JMenuItem(new LoadProgramAction());
loadRomItem = new JMenuItem(new LoadRomAction());
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
JMenuItem quitItem = new JMenuItem(new QuitAction());
fileMenu.add(loadProgramItem);
fileMenu.add(loadRomItem);
fileMenu.add(prefsItem);
fileMenu.add(quitItem);
add(fileMenu);
/*
* View Menu
*/
JMenu viewMenu = new JMenu("View");
JMenu fontSubMenu = new JMenu("Font Size");
ButtonGroup group = new ButtonGroup();
makeFontSizeMenuItem(10, fontSubMenu, group);
makeFontSizeMenuItem(11, fontSubMenu, group);
makeFontSizeMenuItem(12, fontSubMenu, group);
makeFontSizeMenuItem(13, fontSubMenu, group);
makeFontSizeMenuItem(14, fontSubMenu, group);
makeFontSizeMenuItem(15, fontSubMenu, group);
makeFontSizeMenuItem(16, fontSubMenu, group);
makeFontSizeMenuItem(17, fontSubMenu, group);
makeFontSizeMenuItem(18, fontSubMenu, group);
makeFontSizeMenuItem(19, fontSubMenu, group);
makeFontSizeMenuItem(20, fontSubMenu, group);
viewMenu.add(fontSubMenu);
add(viewMenu);
}
private void makeFontSizeMenuItem(int size, JMenu fontSubMenu, ButtonGroup group) {
Action action = new SetFontAction(size);
JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
item.setSelected(size == DEFAULT_FONT_SIZE);
fontSubMenu.add(item);
group.add(item);
}
}
}

View File

@ -170,8 +170,8 @@ public class Acia extends Device {
*/
private long calculateBaudRateDelay() {
if (baudRate > 0) {
// This is a pretty rough approximation based on 8 bits per character,
// and 1/baudRate per bit.
// TODO: This is a pretty rough approximation based on 8 bits per character,
// and 1/baudRate per bit. It could certainly be improved
return (long)((1.0 / baudRate) * 1000000000 * 8);
} else {
return 0;

View File

@ -1,5 +1,6 @@
package com.loomcom.symon.ui;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
@ -36,12 +37,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
private FifoRingBuffer<Character> typeAheadBuffer;
public Console() {
this(DEFAULT_COLUMNS, DEFAULT_ROWS);
}
public Console(int columns, int rows) {
super(new Vt100TerminalModel(columns, rows));
public Console(int columns, int rows, Font font) {
super(new Vt100TerminalModel(columns, rows), font);
// A small type-ahead buffer, as might be found in any real
// VT100-style serial terminal.
this.typeAheadBuffer = new FifoRingBuffer<Character>(128);

View File

@ -12,13 +12,13 @@ public class PreferencesDialog extends Observable implements Preferences {
private final JDialog dialog;
private JTextField aciaAddressField;
private JCheckBox haltOnBreakCheckBox;
private JTextField programLoadAddressField;
private JTextField borderWidthField;
private int programLoadAddress = DEFAULT_PROGRAM_LOAD_ADDRESS;
private int aciaAddress = DEFAULT_ACIA_ADDRESS;
private int borderWidth = DEFAULT_BORDER_WIDTH;
private boolean haltOnBreak = DEFAULT_HALT_ON_BREAK;
public PreferencesDialog(Frame parent, boolean modal) {
this.dialog = new JDialog(parent, modal);
@ -43,15 +43,14 @@ public class PreferencesDialog extends Observable implements Preferences {
GridBagLayout layout = new GridBagLayout();
settingsContainer.setLayout(layout);
final JLabel aciaAddressLabel = new JLabel("ACIA Address");
final JLabel haltOnBreakLabel = new JLabel("Halt on BRK");
final JLabel programLoadAddressLabel = new JLabel("Program Load Address");
final JLabel borderWidthLabel = new JLabel("Console Border Width");
aciaAddressField = new JTextField(8);
haltOnBreakCheckBox = new JCheckBox();
programLoadAddressField = new JTextField(8);
borderWidthField = new JTextField(8);
aciaAddressLabel.setLabelFor(aciaAddressField);
programLoadAddressLabel.setLabelFor(programLoadAddressField);
borderWidthLabel.setLabelFor(borderWidthField);
@ -61,11 +60,10 @@ public class PreferencesDialog extends Observable implements Preferences {
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 0;
constraints.gridy = 0;
settingsContainer.add(aciaAddressLabel, constraints);
settingsContainer.add(haltOnBreakLabel, constraints);
constraints.gridx = 1;
settingsContainer.add(aciaAddressField, constraints);
settingsContainer.add(haltOnBreakCheckBox, constraints);
constraints.gridy = 1;
constraints.gridx = 0;
@ -94,8 +92,8 @@ public class PreferencesDialog extends Observable implements Preferences {
applyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText());
aciaAddress = hexToInt(aciaAddressField.getText());
borderWidth = Integer.parseInt(borderWidthField.getText());
updateUi();
// TODO: Actually check to see if values have changed, don't assume.
@ -118,10 +116,6 @@ public class PreferencesDialog extends Observable implements Preferences {
return programLoadAddress;
}
public int getAciaAddress() {
return aciaAddress;
}
/**
* @return The width of the console border, in pixels.
*/
@ -129,8 +123,15 @@ public class PreferencesDialog extends Observable implements Preferences {
return borderWidth;
}
/**
* @return True if 'halt on break' is desired, false otherwise.
*/
public boolean getHaltOnBreak() {
return haltOnBreak;
}
public void updateUi() {
aciaAddressField.setText(intToHex(aciaAddress));
haltOnBreakCheckBox.setSelected(haltOnBreak);
programLoadAddressField.setText(intToHex(programLoadAddress));
borderWidthField.setText(Integer.toString(borderWidth));
}

View File

@ -26,6 +26,7 @@ public class StatusPanel extends JPanel {
private final ImageIcon negativeOn;
private final ImageIcon negativeOff;
private final JLabel statusFlagsLabel;
private final JLabel carryFlagLabel;
private final JLabel zeroFlagLabel;
private final JLabel irqDisableFlagLabel;
@ -40,7 +41,6 @@ public class StatusPanel extends JPanel {
private JTextField aField;
private JTextField xField;
private JTextField yField;
// private JTextField stepCountField;
private final JLabel opcodeLabel;
private final JLabel pcLabel;
@ -48,11 +48,10 @@ public class StatusPanel extends JPanel {
private final JLabel aLabel;
private final JLabel xLabel;
private final JLabel yLabel;
// private final JLabel stepCountLabel;
private static final int EMPTY_BORDER = 5;
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 4, 0, 0);
private static final Font LABEL_FONT = new Font("sansserif", Font.PLAIN, 11);
private static final int EMPTY_BORDER = 10;
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
public StatusPanel() {
super();
@ -63,7 +62,10 @@ public class StatusPanel extends JPanel {
setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder));
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
GridBagLayout layout = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints();
setLayout(layout);
JPanel statusFlagsPanel = new JPanel();
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
@ -101,37 +103,72 @@ public class StatusPanel extends JPanel {
statusFlagsPanel.add(carryFlagLabel);
// Create and add register and address labels
opcodeLabel = makeLabel("Instruction");
pcLabel = makeLabel("Program Counter");
spLabel = makeLabel("Stack Pointer");
aLabel = makeLabel("Accumulator");
xLabel = makeLabel("X Register");
yLabel = makeLabel("Y Register");
// stepCountLabel = new JLabel("Steps");
statusFlagsLabel = makeLabel("Flags");
opcodeLabel = makeLabel("IR");
pcLabel = makeLabel("PC");
spLabel = makeLabel("SP");
aLabel = makeLabel("A");
xLabel = makeLabel("X");
yLabel = makeLabel("Y");
opcodeField = makeTextField();
pcField = makeTextField();
spField = makeTextField();
aField = makeTextField();
xField = makeTextField();
yField = makeTextField();
// stepCountField = new JTextField("");
opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE);
pcField = makeTextField(LARGE_TEXT_FIELD_SIZE);
spField = makeTextField(SMALL_TEXT_FIELD_SIZE);
aField = makeTextField(SMALL_TEXT_FIELD_SIZE);
xField = makeTextField(SMALL_TEXT_FIELD_SIZE);
yField = makeTextField(SMALL_TEXT_FIELD_SIZE);
add(statusFlagsPanel);
add(opcodeLabel);
add(opcodeField);
add(pcLabel);
add(pcField);
add(spLabel);
add(spField);
add(aLabel);
add(aField);
add(xLabel);
add(xField);
add(yLabel);
add(yField);
// add(stepCountLabel);
// add(stepCountField);
constraints.anchor = GridBagConstraints.LINE_START;
constraints.gridwidth = 2;
constraints.gridx = 0;
constraints.gridy = 0;
add(statusFlagsLabel, constraints);
constraints.gridy = 1;
add(statusFlagsPanel, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridy = 2;
add(opcodeLabel, constraints);
constraints.insets = new Insets(0, 0, 0, 0);
constraints.gridy = 3;
add(opcodeField, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridy = 4;
add(pcLabel, constraints);
constraints.insets = new Insets(0, 0, 0, 0);
constraints.gridy = 5;
add(pcField, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridwidth = 1;
constraints.gridy = 6;
add(spLabel, constraints);
constraints.gridx = 1;
add(aLabel, constraints);
constraints.insets = new Insets(0, 0, 0, 0);
constraints.gridx = 0;
constraints.gridy = 7;
add(spField, constraints);
constraints.gridx = 1;
add(aField, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridx = 0;
constraints.gridy = 8;
add(yLabel, constraints);
constraints.gridx = 1;
add(xLabel, constraints);
constraints.insets = new Insets(0, 0, 5, 0);
constraints.gridx = 0;
constraints.gridy = 9;
add(xField, constraints);
constraints.gridx = 1;
add(yField, constraints);
}
/**
@ -158,7 +195,6 @@ public class StatusPanel extends JPanel {
aField.setText(cpu.getAccumulatorStatus());
xField.setText(cpu.getXRegisterStatus());
yField.setText(cpu.getYRegisterStatus());
// stepCountField.setText(Long.toString(cpu.getStepCounter()));
repaint();
}
@ -228,10 +264,16 @@ public class StatusPanel extends JPanel {
return label;
}
private JTextField makeTextField() {
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
private JTextField makeTextField(Dimension size) {
JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(false);
textField.setMinimumSize(size);
textField.setMaximumSize(size);
textField.setPreferredSize(size);
return textField;
}