mirror of
https://codeberg.org/sethm/symon.git
synced 2026-11-08 03:12:23 +00:00
Refactor of CRTC
This commit is contained in:
1 parent
e7e3c77e3f
commit
6dde766f5a
11 files changed
+543
-410
No files matched your search
@@ -152,17 +152,20 @@ public class Simulator {
|
|||||||
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
|
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
|
||||||
|
|
||||||
public Simulator() throws MemoryRangeException, IOException {
|
public Simulator() throws MemoryRangeException, IOException {
|
||||||
this.acia = new Acia(ACIA_BASE);
|
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
|
||||||
this.via = new Via(VIA_BASE);
|
|
||||||
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||||
this.cpu = new Cpu();
|
this.cpu = new Cpu();
|
||||||
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
|
this.via = new Via(VIA_BASE);
|
||||||
this.crtc = new Crtc(CRTC_BASE, ram, VIDEO_RAM_BASE);
|
this.acia = new Acia(ACIA_BASE);
|
||||||
|
this.crtc = new Crtc(CRTC_BASE, ram);
|
||||||
|
|
||||||
bus.addCpu(cpu);
|
bus.addCpu(cpu);
|
||||||
|
|
||||||
bus.addDevice(ram);
|
bus.addDevice(ram);
|
||||||
bus.addDevice(via);
|
bus.addDevice(via);
|
||||||
bus.addDevice(acia);
|
bus.addDevice(acia);
|
||||||
|
bus.addDevice(crtc);
|
||||||
|
|
||||||
// TODO: Make this configurable, of course.
|
// TODO: Make this configurable, of course.
|
||||||
File romImage = new File("rom.bin");
|
File romImage = new File("rom.bin");
|
||||||
@@ -174,13 +177,14 @@ public class Simulator {
|
|||||||
" not found, loading empty R/W memory image.");
|
" not found, loading empty R/W memory image.");
|
||||||
this.rom = Memory.makeRAM(ROM_BASE, ROM_SIZE);
|
this.rom = Memory.makeRAM(ROM_BASE, ROM_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
bus.addDevice(rom);
|
bus.addDevice(rom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the main simulator UI.
|
* Display the main simulator UI.
|
||||||
*/
|
*/
|
||||||
public void createAndShowUi() {
|
public void createAndShowUi() throws IOException {
|
||||||
mainWindow = new JFrame();
|
mainWindow = new JFrame();
|
||||||
mainWindow.setTitle("Symon 6502 Simulator");
|
mainWindow.setTitle("Symon 6502 Simulator");
|
||||||
mainWindow.setResizable(false);
|
mainWindow.setResizable(false);
|
||||||
@@ -264,8 +268,8 @@ public class Simulator {
|
|||||||
// Prepare the memory window
|
// Prepare the memory window
|
||||||
memoryWindow = new MemoryWindow(bus);
|
memoryWindow = new MemoryWindow(bus);
|
||||||
|
|
||||||
// Prepare the video window
|
// Composite Video and 6545 CRTC
|
||||||
videoWindow = new VideoWindow(crtc.getCrtPanel());
|
videoWindow = new VideoWindow(crtc, 2, 2);
|
||||||
|
|
||||||
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ package com.loomcom.symon.devices;
|
|||||||
|
|
||||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
import com.loomcom.symon.ui.CrtPanel;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,60 +12,101 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class Crtc extends Device {
|
public class Crtc extends Device {
|
||||||
|
|
||||||
public static final int CHAR_WIDTH = 8;
|
private static final Logger logger = Logger.getLogger(Crtc.class.getName());
|
||||||
public static final int CHAR_HEIGHT = 8;
|
|
||||||
public static final int SCAN_LINES = 9;
|
|
||||||
public static final int COLUMNS = 40;
|
|
||||||
public static final int ROWS = 25;
|
|
||||||
public static final int SCALE = 2;
|
|
||||||
|
|
||||||
public static final int REGISTER_SELECT = 0;
|
|
||||||
public static final int REGISTER_WRITE = 1;
|
|
||||||
|
|
||||||
public static String CHAR_ROM_RESOURCE = "/pet.rom";
|
// Memory locations in the CRTC address space
|
||||||
|
public static final int REGISTER_SELECT = 0;
|
||||||
|
public static final int REGISTER_WRITE = 1;
|
||||||
|
|
||||||
|
// Registers
|
||||||
|
public static final int HORIZONTAL_TOTAL = 0;
|
||||||
|
public static final int HORIZONTAL_DISPLAYED = 1;
|
||||||
|
public static final int HORIZONTAL_SYNC_POSITION = 2;
|
||||||
|
public static final int H_V_SYNC_WIDTHS = 3;
|
||||||
|
public static final int VERTICAL_TOTAL = 4;
|
||||||
|
public static final int VERTICAL_TOTAL_ADJUST = 5;
|
||||||
|
public static final int VERTICAL_DISPLAYED = 6;
|
||||||
|
public static final int VERTICAL_SYNC_POSITION = 7;
|
||||||
|
public static final int MODE_CONTROL = 8;
|
||||||
|
public static final int SCAN_LINE = 9;
|
||||||
|
public static final int CURSOR_START = 10;
|
||||||
|
public static final int CURSOR_END = 11;
|
||||||
|
public static final int DISPLAY_START_HIGH = 12;
|
||||||
|
public static final int DISPLAY_START_LOW = 13;
|
||||||
|
public static final int CURSOR_POSITION_HIGH = 14;
|
||||||
|
public static final int CURSOR_POSITION_LOW = 15;
|
||||||
|
public static final int LPEN_HIGH = 16;
|
||||||
|
public static final int LPEN_LOW = 17;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These will determine how the Character ROM is decoded,
|
||||||
|
* and are Character ROM dependent.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// R1 - Horizontal Displayed
|
||||||
|
private int horizontalDisplayed;
|
||||||
|
|
||||||
|
// R6 - Vertical Displayed
|
||||||
|
private int verticalDisplayed;
|
||||||
|
|
||||||
|
// R9 - Scan Lines: Number of scan lines per character, including spacing.
|
||||||
|
private int scanLinesPerRow;
|
||||||
|
|
||||||
|
// R10 - Cursor Start / Cursor Mode
|
||||||
|
private int cursorStartLine;
|
||||||
|
private boolean cursorEnabled;
|
||||||
|
private int cursorBlinkRate;
|
||||||
|
|
||||||
|
// R11 - Cursor End
|
||||||
|
private int cursorStopLine;
|
||||||
|
|
||||||
|
// R12, R13 - Display Start Address: The starting address in the video RAM of the displayed page.
|
||||||
|
private int startAddress;
|
||||||
|
|
||||||
|
// R14, R15 - Cursor Position
|
||||||
|
private int cursorPosition;
|
||||||
|
|
||||||
|
// The size, in bytes, of a displayed page of characters.
|
||||||
|
private int pageSize;
|
||||||
|
|
||||||
private CrtPanel crtPanel;
|
|
||||||
private int currentRegister = 0;
|
private int currentRegister = 0;
|
||||||
|
|
||||||
public Crtc(int deviceAddress, Memory memory, int videoRamStartAddress) throws MemoryRangeException, IOException {
|
private Memory memory;
|
||||||
|
|
||||||
|
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
|
||||||
super(deviceAddress, 2, "CRTC");
|
super(deviceAddress, 2, "CRTC");
|
||||||
this.crtPanel = new CrtPanel(loadCharRom(CHAR_ROM_RESOURCE), memory.getDmaAccess(), COLUMNS, ROWS,
|
this.memory = memory;
|
||||||
CHAR_WIDTH, CHAR_HEIGHT,
|
|
||||||
SCALE, SCALE, videoRamStartAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] loadCharRom(String resource) throws IOException {
|
// Defaults
|
||||||
BufferedInputStream bis = null;
|
this.horizontalDisplayed = 40;
|
||||||
try {
|
this.verticalDisplayed = 25;
|
||||||
bis = new BufferedInputStream(this.getClass().getResourceAsStream(resource));
|
this.scanLinesPerRow = 9;
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
this.cursorStartLine = 0;
|
||||||
while (bis.available() > 0) {
|
this.cursorStopLine = 8;
|
||||||
bos.write(bis.read());
|
this.startAddress = 0x7000;
|
||||||
}
|
this.cursorPosition = 0;
|
||||||
bos.flush();
|
this.pageSize = horizontalDisplayed * verticalDisplayed;
|
||||||
bos.close();
|
this.cursorEnabled = true;
|
||||||
return bos.toByteArray();
|
this.cursorBlinkRate = 500;
|
||||||
} finally {
|
|
||||||
if (bis != null) {
|
|
||||||
bis.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CrtPanel getCrtPanel() {
|
|
||||||
return crtPanel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int address, int data) throws MemoryAccessException {
|
public void write(int address, int data) throws MemoryAccessException {
|
||||||
|
logger.info("[write] Writing CRTC address=" + address + " data=" + data);
|
||||||
switch (address) {
|
switch (address) {
|
||||||
case REGISTER_SELECT:
|
case REGISTER_SELECT:
|
||||||
setCurrentRegister(data);
|
setCurrentRegister(data);
|
||||||
|
break;
|
||||||
case REGISTER_WRITE:
|
case REGISTER_WRITE:
|
||||||
writeRegisterValue(data);
|
writeRegisterValue(data);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new MemoryAccessException("No such address.");
|
throw new MemoryAccessException("No such address.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -87,15 +126,80 @@ public class Crtc extends Device {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int[] getDmaAccess() {
|
||||||
|
return memory.getDmaAccess();
|
||||||
|
}
|
||||||
|
|
||||||
private int status() {
|
private int status() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getHorizontalDisplayed() {
|
||||||
|
return horizontalDisplayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVerticalDisplayed() {
|
||||||
|
return verticalDisplayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScanLinesPerRow() {
|
||||||
|
return scanLinesPerRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorStartLine() {
|
||||||
|
return cursorStartLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorStopLine() {
|
||||||
|
return cursorStopLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorBlinkRate() {
|
||||||
|
return cursorBlinkRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCursorEnabled() {
|
||||||
|
return cursorEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartAddress() {
|
||||||
|
return startAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorPosition() {
|
||||||
|
return cursorPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageSize() {
|
||||||
|
return pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
private void setCurrentRegister(int registerNumber) {
|
private void setCurrentRegister(int registerNumber) {
|
||||||
this.currentRegister = registerNumber;
|
this.currentRegister = registerNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeRegisterValue(int data) {
|
private void writeRegisterValue(int data) {
|
||||||
|
logger.info("Writing CRTC Register #" + currentRegister + " with value " + String.format("$%02X", data));
|
||||||
|
switch (currentRegister) {
|
||||||
|
case HORIZONTAL_DISPLAYED:
|
||||||
|
horizontalDisplayed = data;
|
||||||
|
pageSize = horizontalDisplayed * verticalDisplayed;
|
||||||
|
break;
|
||||||
|
case VERTICAL_DISPLAYED:
|
||||||
|
verticalDisplayed = data;
|
||||||
|
pageSize = horizontalDisplayed * verticalDisplayed;
|
||||||
|
break;
|
||||||
|
case MODE_CONTROL:
|
||||||
|
// TODO: Implement multiple addressing modes.
|
||||||
|
break;
|
||||||
|
case SCAN_LINE:
|
||||||
|
scanLinesPerRow = data;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.info("Ignoring.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,8 +23,13 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.devices;
|
package com.loomcom.symon.devices;
|
||||||
|
|
||||||
import com.loomcom.symon.*;
|
import com.loomcom.symon.Bus;
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.MemoryRange;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A memory-mapped IO Device.
|
* A memory-mapped IO Device.
|
||||||
@@ -32,69 +37,93 @@ import com.loomcom.symon.exceptions.*;
|
|||||||
|
|
||||||
public abstract class Device implements Comparable<Device> {
|
public abstract class Device implements Comparable<Device> {
|
||||||
|
|
||||||
/** The memory range for this device. */
|
/**
|
||||||
private MemoryRange memoryRange;
|
* The memory range for this device.
|
||||||
|
*/
|
||||||
|
private MemoryRange memoryRange;
|
||||||
|
|
||||||
/** The name of the device. */
|
/**
|
||||||
private String name;
|
* The name of the device.
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
/** Reference to the bus where this Device is attached. */
|
/**
|
||||||
private Bus bus;
|
* Reference to the bus where this Device is attached.
|
||||||
|
*/
|
||||||
|
private Bus bus;
|
||||||
|
|
||||||
public Device(int address, int size, String name)
|
/**
|
||||||
throws MemoryRangeException {
|
* Listeners to notify on update.
|
||||||
this.memoryRange = new MemoryRange(address, address + size - 1);
|
*/
|
||||||
this.name = name;
|
private Set<DeviceChangeListener> deviceChangeListeners;
|
||||||
}
|
|
||||||
|
|
||||||
public Device(int address, int size) throws MemoryRangeException {
|
public Device(int address, int size, String name)
|
||||||
this(address, size, null);
|
throws MemoryRangeException {
|
||||||
}
|
this.memoryRange = new MemoryRange(address, address + size - 1);
|
||||||
|
this.name = name;
|
||||||
/* Methods required to be implemented by inheriting classes. */
|
this.deviceChangeListeners = new HashSet<DeviceChangeListener>();
|
||||||
public abstract void write(int address, int data) throws MemoryAccessException;
|
|
||||||
public abstract int read(int address) throws MemoryAccessException;
|
|
||||||
public abstract String toString();
|
|
||||||
|
|
||||||
public void setBus(Bus bus) {
|
|
||||||
this.bus = bus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bus getBus() {
|
|
||||||
return this.bus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MemoryRange getMemoryRange() {
|
|
||||||
return memoryRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int endAddress() {
|
|
||||||
return memoryRange.endAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int startAddress() {
|
|
||||||
return memoryRange.startAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two devices. The sort order is defined by the sort
|
|
||||||
* order of the device's memory ranges.
|
|
||||||
*/
|
|
||||||
public int compareTo(Device other) {
|
|
||||||
if (other == null) {
|
|
||||||
throw new NullPointerException("Cannot compare to null.");
|
|
||||||
}
|
}
|
||||||
if (this == other) {
|
|
||||||
return 0;
|
public Device(int address, int size) throws MemoryRangeException {
|
||||||
|
this(address, size, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Methods required to be implemented by inheriting classes. */
|
||||||
|
public abstract void write(int address, int data) throws MemoryAccessException;
|
||||||
|
|
||||||
|
public abstract int read(int address) throws MemoryAccessException;
|
||||||
|
|
||||||
|
public abstract String toString();
|
||||||
|
|
||||||
|
public Bus getBus() {
|
||||||
|
return this.bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBus(Bus bus) {
|
||||||
|
this.bus = bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryRange getMemoryRange() {
|
||||||
|
return memoryRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int endAddress() {
|
||||||
|
return memoryRange.endAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int startAddress() {
|
||||||
|
return memoryRange.startAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerListener(DeviceChangeListener listener) {
|
||||||
|
deviceChangeListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyListeners() {
|
||||||
|
for (DeviceChangeListener l : deviceChangeListeners) {
|
||||||
|
l.deviceStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two devices. The sort order is defined by the sort
|
||||||
|
* order of the device's memory ranges.
|
||||||
|
*/
|
||||||
|
public int compareTo(Device other) {
|
||||||
|
if (other == null) {
|
||||||
|
throw new NullPointerException("Cannot compare to null.");
|
||||||
|
}
|
||||||
|
if (this == other) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return getMemoryRange().compareTo(other.getMemoryRange());
|
||||||
}
|
}
|
||||||
return getMemoryRange().compareTo(other.getMemoryRange());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||||
|
*
|
||||||
|
* 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.devices;
|
||||||
|
|
||||||
|
public interface DeviceChangeListener {
|
||||||
|
public void deviceStateChanged();
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
* a copy of this software and associated documentation files (the
|
* a copy of this software and associated documentation files (the
|
||||||
@@ -23,14 +23,6 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.ui;
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.awt.event.KeyListener;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.awt.event.MouseListener;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.grahamedgecombe.jterminal.JTerminal;
|
import com.grahamedgecombe.jterminal.JTerminal;
|
||||||
import com.grahamedgecombe.jterminal.vt100.Vt100TerminalModel;
|
import com.grahamedgecombe.jterminal.vt100.Vt100TerminalModel;
|
||||||
import com.loomcom.symon.exceptions.FifoUnderrunException;
|
import com.loomcom.symon.exceptions.FifoUnderrunException;
|
||||||
@@ -39,6 +31,11 @@ import com.loomcom.symon.util.FifoRingBuffer;
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.BevelBorder;
|
import javax.swing.border.BevelBorder;
|
||||||
import javax.swing.border.Border;
|
import javax.swing.border.Border;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.KeyListener;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Console is a simulated 80 column x 24 row VT-100 terminal attached to
|
* The Console is a simulated 80 column x 24 row VT-100 terminal attached to
|
||||||
|
|||||||
@@ -1,275 +0,0 @@
|
|||||||
package com.loomcom.symon.ui;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simulates a CRT Display backed by a 6545 CRTC.
|
|
||||||
*/
|
|
||||||
public class CrtPanel extends JPanel {
|
|
||||||
|
|
||||||
// Character width and height are hardware-implementation specific
|
|
||||||
// and cannot be modified at runtime.
|
|
||||||
private final int charWidth;
|
|
||||||
private final int charHeight;
|
|
||||||
private final int scaleX, scaleY;
|
|
||||||
private final boolean shouldScale;
|
|
||||||
|
|
||||||
private Dimension dimensions;
|
|
||||||
private BufferedImage image;
|
|
||||||
|
|
||||||
private int[] charRom;
|
|
||||||
private int[] videoRam;
|
|
||||||
|
|
||||||
/* Fields corresponding to internal registers in the MOS/Rockwell 6545 */
|
|
||||||
|
|
||||||
// R1 - Horizontal Displayed
|
|
||||||
private final int horizontalDisplayed;
|
|
||||||
// R6 - Vertical Displayed
|
|
||||||
private final int verticalDisplayed;
|
|
||||||
// R9 - Scan Lines: Number of scan lines per character, including spacing.
|
|
||||||
private int scanLinesPerRow = 9;
|
|
||||||
// R10 - Cursor Start
|
|
||||||
private int cursorStartLine;
|
|
||||||
// R11 - Cursor End
|
|
||||||
private int cursorStopLine;
|
|
||||||
private boolean cursorEnabled;
|
|
||||||
private int cursorBlinkDelay;
|
|
||||||
private boolean cursorBlinkEnabled;
|
|
||||||
// R12, R13 - Display Start Address: The starting address in the video RAM of the displayed page.
|
|
||||||
private int startAddress;
|
|
||||||
// R14, R15 - Cursor Position
|
|
||||||
private int cursorPosition;
|
|
||||||
|
|
||||||
// The size, in bytes, of a displayed page of characters.
|
|
||||||
private int pageSize;
|
|
||||||
|
|
||||||
private ScheduledExecutorService scheduler;
|
|
||||||
private ScheduledFuture<?> cursorBlinker;
|
|
||||||
|
|
||||||
private class CursorBlinker implements Runnable {
|
|
||||||
public void run() {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (cursorBlinkEnabled) {
|
|
||||||
cursorEnabled = !cursorEnabled;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CrtPanel(byte[] charRom, int[] videoRam,
|
|
||||||
int horizontalDisplayed, int verticalDisplayed,
|
|
||||||
int charWidth, int charHeight,
|
|
||||||
int scaleX, int scaleY,
|
|
||||||
int startAddress) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.charRom = convertCharRom(charRom, charWidth);
|
|
||||||
this.videoRam = videoRam;
|
|
||||||
this.horizontalDisplayed = horizontalDisplayed;
|
|
||||||
this.verticalDisplayed = verticalDisplayed;
|
|
||||||
this.pageSize = horizontalDisplayed * verticalDisplayed;
|
|
||||||
this.scaleX = scaleX;
|
|
||||||
this.scaleY = scaleY;
|
|
||||||
this.startAddress = startAddress;
|
|
||||||
this.charWidth = charWidth;
|
|
||||||
this.charHeight = charHeight;
|
|
||||||
this.scanLinesPerRow = charHeight + 1;
|
|
||||||
this.cursorStartLine = 0;
|
|
||||||
this.cursorStopLine = charHeight - 1;
|
|
||||||
this.cursorBlinkEnabled = true;
|
|
||||||
this.cursorBlinkDelay = 500; // ms
|
|
||||||
|
|
||||||
this.shouldScale = (this.scaleX > 1 || this.scaleY > 1);
|
|
||||||
|
|
||||||
buildImage();
|
|
||||||
|
|
||||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
cursorBlinker = scheduler.scheduleAtFixedRate(new CursorBlinker(), cursorBlinkDelay, cursorBlinkDelay, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void paintComponent(Graphics g) {
|
|
||||||
for (int i = 0; i < pageSize; i++) {
|
|
||||||
int address = startAddress + i;
|
|
||||||
int originX = (i % horizontalDisplayed) * charWidth;
|
|
||||||
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
|
||||||
image.getRaster().setPixels(originX, originY, charWidth, scanLinesPerRow, getGlyph(i, videoRam[address]));
|
|
||||||
}
|
|
||||||
Graphics2D g2d = (Graphics2D)g;
|
|
||||||
if (shouldScale) {
|
|
||||||
g2d.scale(scaleX, scaleY);
|
|
||||||
}
|
|
||||||
g2d.drawImage(image, 0, 0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStartAddress(int address) {
|
|
||||||
startAddress = address;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStartAddress() {
|
|
||||||
return startAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of pixels (including extra scanlines, if any) corresponding to the
|
|
||||||
* Character ROM plus cursor overlay (if any). The cursor overlay simulates an XOR
|
|
||||||
* of the Character Rom output and the 6545 Cursor output.
|
|
||||||
*
|
|
||||||
* @param position The position within the character field, from 0 to (horizontalDisplayed * verticalDisplayed)
|
|
||||||
* @param chr The character value within the ROM to display.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private int[] getGlyph(int position, int chr) {
|
|
||||||
int romOffset = (chr & 0xff) * (charHeight * charWidth);
|
|
||||||
int[] glyph = new int[charWidth * scanLinesPerRow];
|
|
||||||
|
|
||||||
// Populate the character
|
|
||||||
for (int i = 0; i < (charWidth * Math.min(charHeight, scanLinesPerRow)); i++) {
|
|
||||||
glyph[i] = charRom[romOffset + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overlay the cursor
|
|
||||||
if (cursorEnabled && cursorPosition == position) {
|
|
||||||
int cursorStart = Math.min(glyph.length, cursorStartLine * charWidth);
|
|
||||||
int cursorStop = Math.min(glyph.length, (cursorStopLine + 1) * charWidth);
|
|
||||||
|
|
||||||
for (int i = cursorStart; i < cursorStop; i++) {
|
|
||||||
glyph[i] ^= 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return glyph;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWidth() {
|
|
||||||
return (int) dimensions.getWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHeight() {
|
|
||||||
return (int) dimensions.getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Dimension getPreferredSize() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Dimension getMaximumSize() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Dimension getMinimumSize() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCursorStartLine() {
|
|
||||||
return cursorStartLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCursorStartLine(int cursorStartLine) {
|
|
||||||
this.cursorStartLine = cursorStartLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCursorStopLine() {
|
|
||||||
return cursorStopLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCursorStopLine(int cursorStopLine) {
|
|
||||||
this.cursorStopLine = cursorStopLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCursorBlinkDelay() {
|
|
||||||
return cursorBlinkDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCursorBlinkDelay(int cursorBlinkDelay) {
|
|
||||||
this.cursorBlinkDelay = cursorBlinkDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCursorPosition() {
|
|
||||||
return cursorPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCursorPosition(int cursorPosition) {
|
|
||||||
this.cursorPosition = cursorPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCursorEnabled() {
|
|
||||||
return cursorEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCursorEnabled(boolean cursorEnabled) {
|
|
||||||
this.cursorEnabled = cursorEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCursorBlinkEnabled() {
|
|
||||||
return cursorBlinkEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCursorBlinkEnabled(boolean cursorBlinkEnabled) {
|
|
||||||
if (cursorBlinkEnabled && cursorBlinker == null) {
|
|
||||||
cursorBlinker = scheduler.scheduleAtFixedRate(new CursorBlinker(),
|
|
||||||
cursorBlinkDelay,
|
|
||||||
cursorBlinkDelay,
|
|
||||||
TimeUnit.MILLISECONDS);
|
|
||||||
} else if (!cursorBlinkEnabled && cursorBlinker != null) {
|
|
||||||
cursorBlinker.cancel(true);
|
|
||||||
cursorBlinker = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cursorBlinkEnabled = cursorBlinkEnabled;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setScanLinesPerRow(int scanLinesPerRow) {
|
|
||||||
this.scanLinesPerRow = scanLinesPerRow;
|
|
||||||
buildImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getScanLinesPerRow() {
|
|
||||||
return scanLinesPerRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildImage() {
|
|
||||||
int rasterWidth = charWidth * horizontalDisplayed;
|
|
||||||
int rasterHeight = scanLinesPerRow * verticalDisplayed;
|
|
||||||
|
|
||||||
this.image = new BufferedImage(rasterWidth, rasterHeight, BufferedImage.TYPE_BYTE_BINARY);
|
|
||||||
this.dimensions = new Dimension(rasterWidth * scaleX, rasterHeight * scaleY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a raw binary Character ROM image into an array of pixel data usable
|
|
||||||
* by the Raster underlying the display's BufferedImage.
|
|
||||||
*
|
|
||||||
* @param rawBytes
|
|
||||||
* @param charWidth
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private int[] convertCharRom(byte[] rawBytes, int charWidth) {
|
|
||||||
int[] converted = new int[rawBytes.length * charWidth];
|
|
||||||
|
|
||||||
int romIndex = 0;
|
|
||||||
for (int i = 0; i < converted.length;) {
|
|
||||||
byte charRow = rawBytes[romIndex++];
|
|
||||||
|
|
||||||
for (int j = 7; j >= 0; j--) {
|
|
||||||
converted[i++] = ((charRow & (1 << j)) == 0) ? 0 : 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return converted;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
* a copy of this software and associated documentation files (the
|
* a copy of this software and associated documentation files (the
|
||||||
@@ -28,13 +28,9 @@ import com.loomcom.symon.exceptions.MemoryAccessException;
|
|||||||
import com.loomcom.symon.util.HexUtil;
|
import com.loomcom.symon.util.HexUtil;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.Border;
|
|
||||||
import javax.swing.border.CompoundBorder;
|
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
import javax.swing.border.MatteBorder;
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import javax.swing.table.TableCellRenderer;
|
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
import javax.swing.text.JTextComponent;
|
import javax.swing.text.JTextComponent;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
* a copy of this software and associated documentation files (the
|
* a copy of this software and associated documentation files (the
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
* a copy of this software and associated documentation files (the
|
* a copy of this software and associated documentation files (the
|
||||||
@@ -26,7 +26,8 @@ package com.loomcom.symon.ui;
|
|||||||
import com.loomcom.symon.Cpu;
|
import com.loomcom.symon.Cpu;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.*;
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.border.EtchedBorder;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
* a copy of this software and associated documentation files (the
|
* a copy of this software and associated documentation files (the
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.loomcom.symon.ui;
|
package com.loomcom.symon.ui;
|
||||||
|
|||||||
@@ -1,34 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||||
|
*
|
||||||
|
* 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;
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.loomcom.symon.devices.Crtc;
|
||||||
|
import com.loomcom.symon.devices.DeviceChangeListener;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class VideoWindow extends JFrame {
|
public class VideoWindow extends JFrame implements DeviceChangeListener {
|
||||||
|
|
||||||
private CrtPanel crtPanel;
|
private static final Logger logger = Logger.getLogger(VideoWindow.class.getName());
|
||||||
|
|
||||||
|
private static final int CHAR_WIDTH = 8;
|
||||||
|
private static final int CHAR_HEIGHT = 8;
|
||||||
|
|
||||||
|
private final int scaleX, scaleY;
|
||||||
|
private final boolean shouldScale;
|
||||||
|
|
||||||
|
private BufferedImage image;
|
||||||
|
private int[] charRom;
|
||||||
|
private int[] videoRam;
|
||||||
|
|
||||||
|
private int horizontalDisplayed;
|
||||||
|
private int verticalDisplayed;
|
||||||
|
private int scanLinesPerRow;
|
||||||
|
private int cursorBlinkRate;
|
||||||
|
private boolean showCursor;
|
||||||
|
|
||||||
|
private Dimension dimensions;
|
||||||
|
private Crtc crtc;
|
||||||
|
|
||||||
|
private ScheduledExecutorService scheduler;
|
||||||
|
private ScheduledFuture<?> cursorBlinker;
|
||||||
|
|
||||||
|
private class VideoPanel extends JPanel {
|
||||||
|
@Override
|
||||||
|
public void paintComponent(Graphics g) {
|
||||||
|
for (int i = 0; i < crtc.getPageSize(); i++) {
|
||||||
|
int address = crtc.getStartAddress() + i;
|
||||||
|
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
|
||||||
|
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
||||||
|
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(i, videoRam[address]));
|
||||||
|
}
|
||||||
|
Graphics2D g2d = (Graphics2D)g;
|
||||||
|
if (shouldScale) {
|
||||||
|
g2d.scale(scaleX, scaleY);
|
||||||
|
}
|
||||||
|
g2d.drawImage(image, 0, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getMinimumSize() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
public VideoWindow(CrtPanel crtPanel) {
|
|
||||||
this.crtPanel = crtPanel;
|
|
||||||
createUi();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createUi() {
|
private class CursorBlinker implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (cursorBlinkRate > 0) {
|
||||||
|
showCursor = !showCursor;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoWindow(Crtc crtc, int scaleX, int scaleY) throws IOException {
|
||||||
|
crtc.registerListener(this);
|
||||||
|
|
||||||
|
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
this.crtc = crtc;
|
||||||
|
this.charRom = convertCharRom(loadCharRom("/pet.rom"), CHAR_WIDTH);
|
||||||
|
this.videoRam = crtc.getDmaAccess();
|
||||||
|
this.scaleX = scaleX;
|
||||||
|
this.scaleY = scaleY;
|
||||||
|
this.shouldScale = (scaleX > 1 || scaleY > 1);
|
||||||
|
this.cursorBlinkRate = crtc.getCursorBlinkRate();
|
||||||
|
|
||||||
|
if (cursorBlinkRate > 0) {
|
||||||
|
this.cursorBlinker = scheduler.scheduleAtFixedRate(new CursorBlinker(),
|
||||||
|
cursorBlinkRate,
|
||||||
|
cursorBlinkRate,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture some state from the CRTC that will define the
|
||||||
|
// window size. When these values change, the window will
|
||||||
|
// need to re-pack and redraw.
|
||||||
|
this.horizontalDisplayed = crtc.getHorizontalDisplayed();
|
||||||
|
this.verticalDisplayed = crtc.getVerticalDisplayed();
|
||||||
|
this.scanLinesPerRow = crtc.getScanLinesPerRow();
|
||||||
|
|
||||||
|
buildImage();
|
||||||
|
|
||||||
|
createAndShowUi();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] loadCharRom(String resource) throws IOException {
|
||||||
|
BufferedInputStream bis = null;
|
||||||
|
try {
|
||||||
|
bis = new BufferedInputStream(this.getClass().getResourceAsStream(resource));
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
while (bis.available() > 0) {
|
||||||
|
bos.write(bis.read());
|
||||||
|
}
|
||||||
|
bos.flush();
|
||||||
|
bos.close();
|
||||||
|
return bos.toByteArray();
|
||||||
|
} finally {
|
||||||
|
if (bis != null) {
|
||||||
|
bis.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createAndShowUi() {
|
||||||
setTitle("Composite Video");
|
setTitle("Composite Video");
|
||||||
|
|
||||||
int borderWidth = (int) (crtPanel.getWidth() * 0.08);
|
int borderWidth = 20;
|
||||||
int borderHeight = (int) (crtPanel.getHeight() * 0.08);
|
int borderHeight = 20;
|
||||||
|
|
||||||
JPanel containerPane = new JPanel();
|
JPanel containerPane = new JPanel();
|
||||||
containerPane.setBorder(BorderFactory.createEmptyBorder(borderHeight, borderWidth, borderHeight, borderWidth));
|
containerPane.setBorder(BorderFactory.createEmptyBorder(borderHeight, borderWidth, borderHeight, borderWidth));
|
||||||
containerPane.setLayout(new BorderLayout());
|
containerPane.setLayout(new BorderLayout());
|
||||||
containerPane.setBackground(Color.black);
|
containerPane.setBackground(Color.black);
|
||||||
containerPane.add(crtPanel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
getContentPane().add(containerPane);
|
containerPane.add(new VideoPanel(), BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getContentPane().add(containerPane, BorderLayout.CENTER);
|
||||||
setResizable(false);
|
setResizable(false);
|
||||||
pack();
|
pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void buildImage() {
|
||||||
|
int rasterWidth = CHAR_WIDTH * horizontalDisplayed;
|
||||||
|
int rasterHeight = scanLinesPerRow * verticalDisplayed;
|
||||||
|
this.image = new BufferedImage(rasterWidth, rasterHeight, BufferedImage.TYPE_BYTE_BINARY);
|
||||||
|
this.dimensions = new Dimension(rasterWidth * scaleX, rasterHeight * scaleY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the CRTC on state change.
|
||||||
|
*/
|
||||||
|
public void deviceStateChanged() {
|
||||||
|
|
||||||
|
// Certain state
|
||||||
|
boolean repackNeeded = false;
|
||||||
|
|
||||||
|
if (horizontalDisplayed != crtc.getHorizontalDisplayed()) {
|
||||||
|
horizontalDisplayed = crtc.getHorizontalDisplayed();
|
||||||
|
repackNeeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verticalDisplayed != crtc.getVerticalDisplayed()) {
|
||||||
|
verticalDisplayed = crtc.getVerticalDisplayed();
|
||||||
|
repackNeeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanLinesPerRow != crtc.getScanLinesPerRow()) {
|
||||||
|
scanLinesPerRow = crtc.getScanLinesPerRow();
|
||||||
|
repackNeeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursorBlinkRate != crtc.getCursorBlinkRate()) {
|
||||||
|
cursorBlinkRate = crtc.getCursorBlinkRate();
|
||||||
|
|
||||||
|
if (cursorBlinker != null) {
|
||||||
|
cursorBlinker.cancel(true);
|
||||||
|
cursorBlinker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursorBlinkRate > 0) {
|
||||||
|
cursorBlinker = scheduler.scheduleAtFixedRate(new CursorBlinker(),
|
||||||
|
cursorBlinkRate,
|
||||||
|
cursorBlinkRate,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repackNeeded) {
|
||||||
|
buildImage();
|
||||||
|
invalidate();
|
||||||
|
pack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a raw binary Character ROM image into an array of pixel data usable
|
||||||
|
* by the Raster underlying the display's BufferedImage.
|
||||||
|
*
|
||||||
|
* @param rawBytes
|
||||||
|
* @param charWidth
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private int[] convertCharRom(byte[] rawBytes, int charWidth) {
|
||||||
|
int[] converted = new int[rawBytes.length * charWidth];
|
||||||
|
|
||||||
|
int romIndex = 0;
|
||||||
|
for (int i = 0; i < converted.length;) {
|
||||||
|
byte charRow = rawBytes[romIndex++];
|
||||||
|
|
||||||
|
for (int j = 7; j >= 0; j--) {
|
||||||
|
converted[i++] = ((charRow & (1 << j)) == 0) ? 0 : 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of pixels (including extra scanlines, if any) corresponding to the
|
||||||
|
* Character ROM plus cursor overlay (if any). The cursor overlay simulates an XOR
|
||||||
|
* of the Character Rom output and the 6545 Cursor output.
|
||||||
|
*
|
||||||
|
* @param position The position within the character field, from 0 to (horizontalDisplayed * verticalDisplayed)
|
||||||
|
* @param chr The character value within the ROM to display.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private int[] getGlyph(int position, int chr) {
|
||||||
|
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
|
||||||
|
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
|
||||||
|
|
||||||
|
// Populate the character
|
||||||
|
for (int i = 0; i < (CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow)); i++) {
|
||||||
|
glyph[i] = charRom[romOffset + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlay the cursor
|
||||||
|
if (showCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == position) {
|
||||||
|
int cursorStart = Math.min(glyph.length, crtc.getCursorStartLine() * CHAR_WIDTH);
|
||||||
|
int cursorStop = Math.min(glyph.length, (crtc.getCursorStopLine() + 1) * CHAR_WIDTH);
|
||||||
|
|
||||||
|
for (int i = cursorStart; i < cursorStop; i++) {
|
||||||
|
glyph[i] ^= 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
public void refreshDisplay() {
|
public void refreshDisplay() {
|
||||||
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
|
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
|
||||||
if (isVisible()) {
|
if (isVisible()) {
|
||||||
|
|||||||
Reference in new issue
Block a user