diff --git a/README.md b/README.md index 91ecc8b..6dee62e 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ SYMON - A 6502 System Simulator **NOTE: THIS IS BETA QUALITY SOFTWARE UNDER ACTIVE DEVELOPMENT. Feedback is welcome!** -**Version:** 0.7.0 +**Version:** 0.8.0 -**Last Updated:** 9 December, 2012 +**Last Updated:** 29 December, 2012 Copyright (c) 2008-2012 Seth J. Morabito <web@loomcom.com> @@ -90,6 +90,7 @@ running. ## 4.0 Revision History + - 0.8.0: 29 December, 2012 - 0.7.0: 9 December, 2012 - 0.6: 5 November, 2012 - 0.5: 21 October, 2012 diff --git a/pom.xml b/pom.xml index 82fc9e3..a52f42b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.loomcom.symon symon jar - 0.7.0 + 0.8.0 symon http://www.loomcom.com/symon diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 82e0d65..1ffe79d 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -30,10 +30,8 @@ import com.loomcom.symon.exceptions.FifoUnderrunException; import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.exceptions.MemoryRangeException; import com.loomcom.symon.exceptions.SymonException; +import com.loomcom.symon.ui.*; import com.loomcom.symon.ui.Console; -import com.loomcom.symon.ui.PreferencesDialog; -import com.loomcom.symon.ui.StatusPanel; -import com.loomcom.symon.ui.TraceLog; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -79,8 +77,8 @@ public class Simulator implements Observer { 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. + // to refresh the status view on every simulated clock cycle. Instead, we will only refresh the status view + // after this number of steps when running normally. // // Since we're simulating a 1MHz 6502 here, we have a 1 us delay between steps. Setting this to 10000 // should give us a status update every 10 ms. @@ -110,16 +108,16 @@ public class Simulator implements Observer { */ private JFrame mainWindow; - /** - * The Console Window is connected to the ACIA's TX and RX lines. - */ - private JFrame consoleWindow; - /** * The Trace Window shows the most recent 50,000 CPU states. */ private TraceLog traceLog; + /** + * The Memory Window shows the contents of one page of memory. + */ + private MemoryWindow memoryWindow; + /** * The Zero Page Window shows the contents of page 0. */ @@ -236,6 +234,9 @@ public class Simulator implements Observer { // Prepare the log window traceLog = new TraceLog(); + // Prepare the memory window + memoryWindow = new MemoryWindow(bus); + mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); console.requestFocus(); @@ -296,9 +297,14 @@ public class Simulator implements Observer { private void handleStep() { try { step(); - if (traceLog.isVisible()) { - traceLog.refresh(); - } + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (traceLog.isVisible()) { + traceLog.refresh(); + } + statusPane.updateState(cpu); + } + }); } catch (SymonException ex) { logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage()); ex.printStackTrace(); @@ -402,6 +408,7 @@ public class Simulator implements Observer { SwingUtilities.invokeLater(new Runnable() { public void run() { try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // Create the main UI window Simulator simulator = new Simulator(); simulator.createAndShowUi(); @@ -628,6 +635,23 @@ public class Simulator implements Observer { } } + class ToggleMemoryWindowAction extends AbstractAction { + public ToggleMemoryWindowAction() { + super("Memory Window", null); + putValue(SHORT_DESCRIPTION, "Show or Hide the Memory Window"); + } + + public void actionPerformed(ActionEvent actionEvent) { + synchronized (memoryWindow) { + if (memoryWindow.isVisible()) { + memoryWindow.setVisible(false); + } else { + memoryWindow.setVisible(true); + } + } + } + } + class SimulatorMenu extends JMenuBar { // Menu Items private JMenuItem loadProgramItem; @@ -680,10 +704,8 @@ public class Simulator implements Observer { */ JMenu viewMenu = new JMenu("View"); - JMenu fontSubMenu = new JMenu("Font Size"); - + JMenu fontSubMenu = new JMenu("Console Font Size"); ButtonGroup fontSizeGroup = new ButtonGroup(); - makeFontSizeMenuItem(10, fontSubMenu, fontSizeGroup); makeFontSizeMenuItem(11, fontSubMenu, fontSizeGroup); makeFontSizeMenuItem(12, fontSubMenu, fontSizeGroup); @@ -695,13 +717,14 @@ public class Simulator implements Observer { makeFontSizeMenuItem(18, fontSubMenu, fontSizeGroup); makeFontSizeMenuItem(19, fontSubMenu, fontSizeGroup); makeFontSizeMenuItem(20, fontSubMenu, fontSizeGroup); - viewMenu.add(fontSubMenu); JRadioButtonMenuItem showTraceLog = new JRadioButtonMenuItem(new ToggleTraceWindowAction()); - viewMenu.add(showTraceLog); + JRadioButtonMenuItem showMemoryTable = new JRadioButtonMenuItem(new ToggleMemoryWindowAction()); + viewMenu.add(showMemoryTable); + add(viewMenu); } diff --git a/src/main/java/com/loomcom/symon/ui/MemoryWindow.java b/src/main/java/com/loomcom/symon/ui/MemoryWindow.java new file mode 100644 index 0000000..9e30833 --- /dev/null +++ b/src/main/java/com/loomcom/symon/ui/MemoryWindow.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2008-2012 Seth J. Morabito + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.loomcom.symon.ui; + +import com.loomcom.symon.Bus; +import com.loomcom.symon.exceptions.MemoryAccessException; +import com.loomcom.symon.util.HexUtil; + +import javax.swing.*; +import javax.swing.event.TableModelListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Display a page of memory. + */ +public class MemoryWindow extends JFrame implements ActionListener { + + private MemoryTableModel memoryTableModel; + private JTable memoryTable; + private JScrollPane scrollPane; + private JTextField pageNumberTextField; + private JLabel pageNumberLabel; + + private static final Dimension MINIMUM_SIZE = new Dimension(400, 450); + + public MemoryWindow(Bus bus) { + this.memoryTableModel = new MemoryTableModel(bus); + this.createUi(); + } + + /** + * Set the current memory page to be inspected by the table. + * + * @param pageNumber The page number, from 0 to 255 (00 to FF hex) + */ + public void setPageNumber(int pageNumber) { + memoryTableModel.setPageNumber(pageNumber); + } + + public int getPageNumber() { + return memoryTableModel.getPageNumber(); + } + + private void updatePageNumberTextField() { + pageNumberTextField.setText(HexUtil.byteToHex(getPageNumber())); + } + + private void createUi() { + setTitle("Memory Contents"); + this.memoryTable = new JTable(memoryTableModel); + + memoryTable.setDragEnabled(false); + memoryTable.setCellSelectionEnabled(false); + memoryTable.setShowGrid(false); + + pageNumberLabel = new JLabel("Page Number"); + pageNumberTextField = new JTextField(8); + pageNumberTextField.addActionListener(this); + + updatePageNumberTextField(); + + JPanel controlPanel = new JPanel(); + JPanel memoryPanel = new JPanel(); + memoryPanel.setLayout(new BorderLayout()); + + controlPanel.add(pageNumberLabel); + controlPanel.add(pageNumberTextField); + + this.scrollPane = new JScrollPane(memoryTable); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + + memoryPanel.add(scrollPane, BorderLayout.CENTER); + + setLayout(new BorderLayout()); + getContentPane().add(controlPanel, BorderLayout.NORTH); + getContentPane().add(memoryPanel, BorderLayout.CENTER); + + setMinimumSize(MINIMUM_SIZE); + setPreferredSize(MINIMUM_SIZE); + + pack(); + } + + /** + * Handle page numbers entered into the UI. + * + * @param e The action event + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == pageNumberTextField) { + + String pageNumberInput = pageNumberTextField.getText(); + try { + // Try to parse a hex value out of the pageNumber. + int newPageNumber = Integer.parseInt(pageNumberInput, 16); + setPageNumber(newPageNumber & 0xff); + memoryTable.updateUI(); + } catch (NumberFormatException ex) { + // An invalid number was entered. Log the error, but otherwise + // take no action. + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Can't parse page number " + + pageNumberInput); + } + updatePageNumberTextField(); + } + } + + /** + * The model that backs the Memory Table. + */ + private class MemoryTableModel extends AbstractTableModel { + + private Bus bus; + private int pageNumber; + + private static final int COLUMN_COUNT = 9; + private static final int ROW_COUNT = 32; + + public MemoryTableModel(Bus bus) { + this.bus = bus; + } + + /** + * Set the current memory page to be inspected by the table. + * + * @param pageNumber The page number, from 0 to 255 (00 to FF hex) + */ + public void setPageNumber(int pageNumber) { + this.pageNumber = pageNumber; + } + + /** + * Returns the current page number being inspected by the table. + * + * @return The page number being inspected, from 0 to 255 (00 to FF hex) + */ + public int getPageNumber() { + return this.pageNumber; + } + + public int getRowCount() { + return ROW_COUNT; + } + + public int getColumnCount() { + return COLUMN_COUNT; + } + + @Override + public String getColumnName(int i) { + return null; + } + + @Override + public Class getColumnClass(int i) { + return String.class; + } + + @Override + public boolean isCellEditable(int row, int column) { + return column > 0; + } + + public Object getValueAt(int row, int column) { + try { + if (column == 0) { + return HexUtil.wordToHex(fullAddress(row, 1)); + } else { + return HexUtil.byteToHex(bus.read(fullAddress(row, column))); + } + } catch (MemoryAccessException ex) { + return "??"; + } + } + + @Override + public void setValueAt(Object o, int row, int column) { + if (column > 0) { + try { + String hexValue = (String)o; + int fullAddress = fullAddress(row, column); + int newValue = Integer.parseInt(hexValue, 16); + bus.write(fullAddress, newValue); + } catch (MemoryAccessException ex) { + ; + } catch (NumberFormatException ex) { + ; + } catch (ClassCastException ex) { + ; + } + fireTableCellUpdated(row, column); + } + } + + private int fullAddress(int row, int column) { + int pageAddress = ((row * 8) + (column - 1)) & 0xff; + return (pageNumber << 8) | pageAddress; + } + + } + +} + diff --git a/src/main/java/com/loomcom/symon/ui/StatusPanel.java b/src/main/java/com/loomcom/symon/ui/StatusPanel.java index 2e046ef..fcdf8cb 100644 --- a/src/main/java/com/loomcom/symon/ui/StatusPanel.java +++ b/src/main/java/com/loomcom/symon/ui/StatusPanel.java @@ -1,6 +1,26 @@ -/** +/* + * Copyright (c) 2008-2012 Seth J. Morabito * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + package com.loomcom.symon.ui; import com.loomcom.symon.Cpu; @@ -52,6 +72,8 @@ public class StatusPanel extends JPanel { 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); + private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22); + private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22); public StatusPanel() { super(); @@ -126,11 +148,12 @@ public class StatusPanel extends JPanel { 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.insets = new Insets(2, 2, 2, 2); constraints.gridy = 3; add(opcodeField, constraints); @@ -138,7 +161,7 @@ public class StatusPanel extends JPanel { constraints.gridy = 4; add(pcLabel, constraints); - constraints.insets = new Insets(0, 0, 0, 0); + constraints.insets = new Insets(2, 2, 2, 2); constraints.gridy = 5; add(pcField, constraints); @@ -149,7 +172,7 @@ public class StatusPanel extends JPanel { constraints.gridx = 1; add(aLabel, constraints); - constraints.insets = new Insets(0, 0, 0, 0); + constraints.insets = new Insets(2, 2, 2, 2); constraints.gridx = 0; constraints.gridy = 7; add(spField, constraints); @@ -163,7 +186,7 @@ public class StatusPanel extends JPanel { constraints.gridx = 1; add(xLabel, constraints); - constraints.insets = new Insets(0, 0, 5, 0); + constraints.insets = new Insets(2, 2, 2, 2); constraints.gridx = 0; constraints.gridy = 9; add(xField, constraints); @@ -266,9 +289,6 @@ public class StatusPanel extends JPanel { return label; } - 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); @@ -276,6 +296,7 @@ public class StatusPanel extends JPanel { textField.setMinimumSize(size); textField.setMaximumSize(size); textField.setPreferredSize(size); + textField.setBackground(Color.WHITE); return textField; }