jace/src/main/java/jace/ui/AbstractEmulatorFrame.java
Brendan Robert 0ccb63558f A lot of things have been deactivated to sever the link to the old Swing UI. Indicators, namely, have been commented out in many places. Ultimately the emulator is wholly unusable in this state.
The video rendering was re-written to use writableImages and is displaying (something) but keyboard input and configurations are broken so nothing much happens after the inital boot.  Basically the underlying part to make this show up in JavaFX is starting to take shape.
2015-02-03 00:55:25 -06:00

494 lines
17 KiB
Java

/*
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package jace.ui;
import jace.config.ConfigurableField;
import jace.config.Reconfigurable;
import jace.core.Computer;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* This is an abstraction of the emulator user interface. It defines a lot of
* the management of user interface elements (such as screen indicators) as well
* as window management (resize, fullscreen, etc)
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public abstract class AbstractEmulatorFrame extends javax.swing.JFrame implements Reconfigurable {
Computer computer;
/**
* Creates new form AbstractEmulatorFrame
*/
public AbstractEmulatorFrame() {
initComponents();
}
public void setComputer(Computer computer) {
this.computer = computer;
}
@Override
public synchronized void addKeyListener(KeyListener l) {
super.addKeyListener(l);
getScreen().addKeyListener(l);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
pack();
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
@Override
abstract public String getShortName();
@ConfigurableField(name = "Show Debug Panel")
public Boolean showDebug = false;
@Override
public void reconfigure() {
getDebuggerPanel().setVisible(isShowDebug());
resizeVideo();
}
abstract public DebuggerPanel getDebuggerPanel();
/**
* @return the showDebug
*/
public boolean isShowDebug() {
return showDebug;
}
/**
* @param showDebug the showDebug to set
*/
public void setShowDebug(boolean showDebug) {
this.showDebug = showDebug;
}
public void addIndicator(Object owner, ImageIcon icon, int time) {
synchronized (indicators) {
Set<ImageIcon> ind = indicators.get(owner);
if (ind == null) {
ind = new HashSet<ImageIcon>();
indicators.put(owner, ind);
}
removeTime.put(icon, System.currentTimeMillis() + time);
if (ind.contains(icon)) {
return;
}
ind.add(icon);
}
redrawIndicators();
}
public static int DEFAULT_INDICATOR_TIME = 250;
public void addIndicator(Object owner, ImageIcon icon) {
addIndicator(owner, icon, DEFAULT_INDICATOR_TIME);
}
public void removeIndicator(Object owner, ImageIcon icon, boolean redraw) {
synchronized (indicators) {
Set<ImageIcon> ind = indicators.get(owner);
if (ind != null) {
ind.remove(icon);
}
removeTime.remove(icon);
}
if (redraw) {
redrawIndicators();
}
}
public void removeIndicator(Object owner, ImageIcon icon) {
removeIndicator(owner, icon, true);
}
public void removeIndicators(Object owner) {
synchronized (indicators) {
Set<ImageIcon> ind = indicators.get(owner);
if (ind == null) {
return;
}
for (ImageIcon i : ind) {
removeTime.remove(i);
}
indicators.remove(owner);
}
redrawIndicators();
}
Lock indicatorLock = new ReentrantLock();
Thread indicatorThread;
public void redrawIndicators() {
final HashSet<ImageIcon> i = new HashSet<ImageIcon>();
HashSet<ImageIcon> removeList = new HashSet<ImageIcon>();
long now = System.currentTimeMillis();
long soonest = Long.MAX_VALUE;
synchronized (indicators) {
for (Object owner : indicators.keySet()) {
Set<ImageIcon> ind = indicators.get(owner);
for (ImageIcon icon : ind) {
long rt = removeTime.get(icon);
if (rt <= now) {
removeList.add(icon);
} else {
i.add(icon);
if (rt < soonest) {
soonest = rt;
}
}
}
for (ImageIcon remove : removeList) {
removeIndicator(owner, remove, false);
}
}
}
boolean changed = false;
if (visibleIndicators == null) {
changed = true;
} else if (i.size() != visibleIndicators.size() || !i.containsAll(visibleIndicators)) {
changed = true;
}
if (changed) {
visibleIndicators = i;
doRedrawIndicators(visibleIndicators);
}
resumeIndicatorLoop();
}
Set<ImageIcon> visibleIndicators;
private void resumeIndicatorLoop() {
try {
indicatorLock.lock();
if (indicatorThread == null || !indicatorThread.isAlive()) {
indicatorThread = new Thread(new Runnable() {
@Override
public void run() {
while (visibleIndicators != null) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
return;
}
redrawIndicators();
if (visibleIndicators.isEmpty()) {
visibleIndicators = null;
}
}
}
});
indicatorThread.start();
}
} finally {
indicatorLock.unlock();
}
}
private void suspendIndicatorLoop() {
try {
indicatorLock.lock();
visibleIndicators.clear();
indicatorThread.interrupt();
indicatorThread = null;
} finally {
indicatorLock.unlock();
}
}
abstract public void doRedrawIndicators(Set<ImageIcon> indicators);
private Map<ImageIcon, Long> removeTime = new HashMap<ImageIcon, Long>();
private Map<Object, Set<ImageIcon>> indicators = new HashMap<Object, Set<ImageIcon>>();
public void resizeVideo() {
EventQueue.invokeLater(() -> {
computer.pause();
computer.getVideo().suspend();
JPanel debugger = getDebuggerPanel();
Component screen = getScreen();
Rectangle bounds = screen.getParent().getBounds();
int width1 = (int) bounds.getWidth();
int height1 = (int) bounds.getHeight();
if (debugger.isVisible()) {
debugger.setBounds(width1 - debugger.getWidth(), 0, debugger.getWidth(), height1);
width1 = (int) bounds.getWidth() - debugger.getWidth() + 1;
screen.setSize(width1, height1);
debugger.revalidate();
} else {
screen.setSize(width1, height1);
}
computer.getVideo().setWidth(width1);
computer.getVideo().setHeight(height1);
if (!isFullscreen || !fullscreenEnforceRatio) {
// computer.getVideo().setScreen(getScreenGraphics());
}
computer.getVideo().forceRefresh();
screen.validate();
screen.requestFocusInWindow();
computer.resume();
computer.getVideo().resume();
});
}
abstract public Component getScreen();
boolean fullscreenEnforceRatio = false;
public void enforceIntegerRatio() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
int ww = getWidth();
int wh = getHeight();
int w = getContentPane().getWidth();
int h = getContentPane().getHeight();
int bw = ww - w;
int bh = wh - h;
double dhscale = w / 560.0;
double dvscale = h / 384.0;
int hscale = (int) Math.round(dhscale);
int vscale = (int) Math.round(dvscale);
int scale = Math.min(hscale, vscale);
if (scale < 1) {
scale = 1;
}
Rectangle b = getBounds();
if (!isFullscreen) {
b.setSize(bw + 560 * scale, bh + 384 * scale);
setBounds(b);
} else {
fullscreenEnforceRatio = !fullscreenEnforceRatio;
if (fullscreenEnforceRatio) {
int sw = getBounds().width;
int sh = getBounds().height;
while ((560 * scale) > sw || (384 * scale) > sh) {
scale--;
}
b.setSize(560 * scale, 384 * scale);
b.x = (w / 2) - (b.width / 2);
b.y = (h / 2) - (b.height / 2);
computer.pause();
computer.getVideo().suspend();
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
Graphics2D g = (Graphics2D) getScreenGraphics();
g.setColor(new Color(0, 0, 0x040));
g.fill(getBounds());
Graphics2D gg = (Graphics2D) g.create(b.x, b.y, b.width, b.height);
gg.scale((double) b.width / (double) sw, (double) b.height / (double) sh);
// computer.getVideo().setScreen(gg);
computer.getVideo().resume();
computer.resume();
} else {
b = getBounds();
getScreen().setBounds(getBounds());
}
}
resizeVideo();
}
});
}
abstract public void repaintIndicators();
public Graphics2D getScreenGraphics() {
return (Graphics2D) getScreen().getGraphics();
}
boolean isFullscreen = false;
public boolean isFullscreenActive() {
return isFullscreen;
}
public void toggleFullscreen() {
final JFrame window = this;
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
computer.pause();
computer.getVideo().suspend();
isFullscreen = !isFullscreen;
GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
if (isFullscreen) {
removeNotify();
setUndecorated(true);
validate();
addNotify();
device.setFullScreenWindow(window);
fullscreenEnforceRatio = false;
} else {
removeNotify();
setUndecorated(false);
addNotify();
device.setFullScreenWindow(null);
}
resizeVideo();
computer.getVideo().resume();
computer.resume();
}
});
}
private class Dialog {
JPanel ui;
String name;
Dialog parent;
boolean disposeOnClose;
}
Dialog visibleDialog = null;
Map<String, Dialog> registeredModalDialogs = new HashMap<String, Dialog>();
public JPanel getModalDialogUI(String name) {
Dialog dialog = registeredModalDialogs.get(name);
return dialog != null ? dialog.ui : null;
}
public void registerModalDialog(JPanel ui, String name, String parent, boolean disposeOnClose) {
Dialog dialog = new Dialog();
dialog.ui = ui;
dialog.parent = registeredModalDialogs.get(parent);
dialog.name = name;
dialog.disposeOnClose = disposeOnClose;
registeredModalDialogs.put(name, dialog);
}
public void closeDialog(String name) {
Dialog d = registeredModalDialogs.get(name);
if (d == null) return;
Dialog parent = d.parent;
if (d.disposeOnClose) {
removeDialogAndChildren(d);
disposeModalDialog(name);
}
// If this operation somehow affected the visible dialog, show its parent if possible
while (!registeredModalDialogs.containsValue(parent) && parent != null) {
parent = parent.parent;
}
showDialog(parent);
}
// Close/hide the current dialog
// If the disposeOnClose flag is true then the dialog will be removed
// completely. Some dialogs such as configuration will have this set
// to false to avoid having to be regenerated unnecessarily.
public void closeDialog() {
if (visibleDialog == null) {
return;
}
if (visibleDialog.disposeOnClose) {
removeDialogAndChildren(visibleDialog);
} else {
hideModalDialog(visibleDialog.name);
}
showDialog(visibleDialog.parent);
}
// Recursively remove a dialog and any children that were registered
// This is a depth-first operation out of necessity since the actual
// stoage structure is flat.
private void removeDialogAndChildren(Dialog d) {
if (d == null) {
return;
}
for (Dialog child : registeredModalDialogs.values()) {
if (child.parent == null || !child.parent.equals(d)) {
continue;
}
removeDialogAndChildren(child);
}
registeredModalDialogs.remove(d.name);
}
public void showDialog(String name) {
Dialog d = registeredModalDialogs.get(name);
if (d == null) {
return;
}
showDialog(d);
}
protected void showDialog(Dialog d) {
if (d == null) {
// Uh oh... why are we asked to show nothing?
System.err.println("WARNING: Asked to show a null modal dialog!");
return;
}
if (d.parent != null && d.parent.equals(visibleDialog)) {
// The new dialog is a child of the visible dialog, preserve the parent dialog
} else if (visibleDialog != null) {
// The new dialog has no child relationship with the current dialog.
// This means that the old dialog should be considered closed.
if (visibleDialog.disposeOnClose) {
removeDialogAndChildren(d);
}
}
List<String> ancestors = new ArrayList<String>();
for (Dialog parent = d.parent; parent != null; parent = parent.parent) {
ancestors.add(0, parent.name);
}
displayModalDialog(d.name, d.ui, ancestors);
visibleDialog = d;
}
abstract protected void displayModalDialog(String name, JPanel ui, List<String> ancestors);
abstract protected void disposeModalDialog(String name);
abstract protected void hideModalDialog(String name);
}