From 95d4d3ecf1b92f573231aaa70022cb6de3d9da25 Mon Sep 17 00:00:00 2001 From: hneemann Date: Sun, 3 Apr 2016 18:27:59 +0200 Subject: [PATCH] added a first trace window --- .../java/de/neemann/digital/core/Model.java | 12 ++- .../de/neemann/digital/core/ModelEvent.java | 3 +- .../java/de/neemann/digital/gui/Main.java | 13 ++- .../digital/gui/components/ProbeDialog.java | 4 +- .../gui/components/data/DataSample.java | 42 +++++++++ .../digital/gui/components/data/DataSet.java | 68 +++++++++++++++ .../gui/components/data/DataSetComponent.java | 87 +++++++++++++++++++ .../gui/components/data/DataSetDialog.java | 76 ++++++++++++++++ src/main/resources/lang/lang_de.properties | 2 + src/main/resources/lang/lang_en.properties | 2 + 10 files changed, 300 insertions(+), 9 deletions(-) create mode 100644 src/main/java/de/neemann/digital/gui/components/data/DataSample.java create mode 100644 src/main/java/de/neemann/digital/gui/components/data/DataSet.java create mode 100644 src/main/java/de/neemann/digital/gui/components/data/DataSetComponent.java create mode 100644 src/main/java/de/neemann/digital/gui/components/data/DataSetDialog.java diff --git a/src/main/java/de/neemann/digital/core/Model.java b/src/main/java/de/neemann/digital/core/Model.java index 3cf4bd8fd..5dbb0ad8d 100644 --- a/src/main/java/de/neemann/digital/core/Model.java +++ b/src/main/java/de/neemann/digital/core/Model.java @@ -55,6 +55,7 @@ public class Model { private ArrayList nodesToUpdateNext; private int version; private boolean isInitialized = false; + private boolean signalsOrdered = false; /** * Creates a new model @@ -83,9 +84,8 @@ public class Model { /** * Adds a node to the model - * * @param node the node - * @param type of the node + * @param type of the node * @return the node itself for chained calls */ public T add(T node) { @@ -324,6 +324,10 @@ public class Model { } public ArrayList getSignals() { + if (!signalsOrdered) { + Collections.sort(signals); + signalsOrdered = true; + } return signals; } @@ -335,6 +339,10 @@ public class Model { return roms; } + public void fireManualChangeEvent() { + fireEvent(ModelEvent.MANUALCHANGE); + } + public static class Signal implements Comparable { private final String name; diff --git a/src/main/java/de/neemann/digital/core/ModelEvent.java b/src/main/java/de/neemann/digital/core/ModelEvent.java index a75885c6f..acafce2c2 100644 --- a/src/main/java/de/neemann/digital/core/ModelEvent.java +++ b/src/main/java/de/neemann/digital/core/ModelEvent.java @@ -10,8 +10,9 @@ public class ModelEvent { public static final ModelEvent STARTED = new ModelEvent(Event.STARTED); public static final ModelEvent BREAK = new ModelEvent(Event.BREAK); public static final ModelEvent STOPPED = new ModelEvent(Event.STOPPED); + public static final ModelEvent MANUALCHANGE = new ModelEvent(Event.MANUALCHANGE); - public enum Event {STARTED, STOPPED, STEP, BREAK, MICROSTEP} + public enum Event {STARTED, STOPPED, STEP, BREAK, MANUALCHANGE, MICROSTEP} private final Event event; diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index b5dd42631..7873f53a0 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -14,6 +14,7 @@ import de.neemann.digital.draw.shapes.ShapeFactory; import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.ElementOrderer; import de.neemann.digital.gui.components.ProbeDialog; +import de.neemann.digital.gui.components.data.DataSetDialog; import de.neemann.digital.gui.components.listing.ROMListingDialog; import de.neemann.digital.gui.state.State; import de.neemann.digital.gui.state.StateManager; @@ -57,8 +58,8 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave { private final ElementLibrary library; private final JCheckBoxMenuItem runClock; private final JCheckBoxMenuItem showProbes; + private final JCheckBoxMenuItem showGraph; private final JCheckBoxMenuItem showListing; - private final JCheckBoxMenuItem traceEnable; private final LibrarySelector librarySelector; private final ShapeFactory shapeFactory; private final SavedListener savedListener; @@ -288,7 +289,8 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave { showListing.setToolTipText(Lang.get("menu_listing_tt")); showProbes = new JCheckBoxMenuItem(Lang.get("menu_probe")); showProbes.setToolTipText(Lang.get("menu_probe_tt")); - traceEnable = new JCheckBoxMenuItem(Lang.get("menu_trace")); + showGraph = new JCheckBoxMenuItem(Lang.get("menu_graph")); + showGraph.setToolTipText(Lang.get("menu_graph_tt")); runClock = new JCheckBoxMenuItem(Lang.get("menu_runClock")); runClock.setToolTipText(Lang.get("menu_runClock_tt")); @@ -298,8 +300,8 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave { run.add(runToBreak.createJMenuItem()); //run.add(speedTest.createJMenuItem()); run.add(showProbes); + run.add(showGraph); run.add(showListing); - //run.add(traceEnable); run.add(runClock); doStep.setEnabled(false); @@ -404,6 +406,10 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave { if (showProbes.isSelected()) new ProbeDialog(this, model, updateEvent).setVisible(true); + if (showGraph.isSelected()) + new DataSetDialog(this, model, updateEvent).setVisible(true); + + if (showListing.isSelected()) for (ROM rom : model.getRoms()) try { @@ -538,6 +544,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave { @Override public void hasChanged() { modelDescription.addNodeElementsTo(model.nodesToUpdate(), circuitComponent.getHighLighted()); + model.fireManualChangeEvent(); circuitComponent.repaint(); doStep.setEnabled(model.needsUpdate()); } diff --git a/src/main/java/de/neemann/digital/gui/components/ProbeDialog.java b/src/main/java/de/neemann/digital/gui/components/ProbeDialog.java index 0a7f174ef..8443c7f28 100644 --- a/src/main/java/de/neemann/digital/gui/components/ProbeDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/ProbeDialog.java @@ -13,7 +13,6 @@ import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; -import java.util.Collections; /** * @author hneemann @@ -29,7 +28,6 @@ public class ProbeDialog extends JDialog implements ModelStateObserver { this.type = type; ArrayList signals = model.getSignals(); - Collections.sort(signals); tableModel = new SignalTableModel(signals); JTable list = new JTable(tableModel); getContentPane().add(new JScrollPane(list), BorderLayout.CENTER); @@ -55,7 +53,7 @@ public class ProbeDialog extends JDialog implements ModelStateObserver { @Override public void handleEvent(ModelEvent event) { - if (event.getType() == type) { + if (event.getType() == type || event.getType() == ModelEvent.Event.MANUALCHANGE) { tableModel.fireChanged(); } } diff --git a/src/main/java/de/neemann/digital/gui/components/data/DataSample.java b/src/main/java/de/neemann/digital/gui/components/data/DataSample.java new file mode 100644 index 000000000..bedb4ae25 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/data/DataSample.java @@ -0,0 +1,42 @@ +package de.neemann.digital.gui.components.data; + +import de.neemann.digital.core.Model; + +import java.util.ArrayList; + +/** + * @author hneemann + */ +public class DataSample { + + private final int mainTime; + private final long[] values; + + public DataSample(int mainTime, int valueCount) { + this.mainTime = mainTime; + values = new long[valueCount]; + } + + public DataSample(DataSample sample) { + this(sample.mainTime, sample.values.length); + System.arraycopy(sample.values, 0, values, 0, values.length); + } + + public int getMainTime() { + return mainTime; + } + + public long getValue(int i) { + return values[i]; + } + + public void setValue(int i, long value) { + values[i] = value; + } + + public DataSample fillWith(ArrayList signals) { + for (int i = 0; i < signals.size(); i++) + values[i] = signals.get(i).getValue().getValueIgnoreBurn(); + return this; + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/data/DataSet.java b/src/main/java/de/neemann/digital/gui/components/data/DataSet.java new file mode 100644 index 000000000..2fefdc77a --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/data/DataSet.java @@ -0,0 +1,68 @@ +package de.neemann.digital.gui.components.data; + +import de.neemann.digital.core.Model; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * @author hneemann + */ +public class DataSet implements Iterable { + private static final int MAX_SAMPLES = 1000; + private final ArrayList signals; + private final ArrayList samples; + private DataSample min; + private DataSample max; + + public DataSet(ArrayList signals) { + this.signals = signals; + samples = new ArrayList<>(); + } + + public void add(DataSample sample) { + if (samples.size() < MAX_SAMPLES) { + samples.add(sample); + if (min == null) { + min = new DataSample(sample); + max = new DataSample(sample); + } else { + for (int i = 0; i < signals.size(); i++) { + if (sample.getValue(i) < min.getValue(i)) + min.setValue(i, sample.getValue(i)); + if (sample.getValue(i) > max.getValue(i)) + max.setValue(i, sample.getValue(i)); + } + } + } + } + + public int size() { + return samples.size(); + } + + public int signalSize() { + return signals.size(); + } + + @Override + public Iterator iterator() { + return samples.iterator(); + } + + public DataSample getMin() { + return min; + } + + public DataSample getMax() { + return max; + } + + public long getWidth(int i) { + return max.getValue(i) - min.getValue(i); + } + + public Model.Signal getSignal(int i) { + return signals.get(i); + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/data/DataSetComponent.java b/src/main/java/de/neemann/digital/gui/components/data/DataSetComponent.java new file mode 100644 index 000000000..016553eb8 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/data/DataSetComponent.java @@ -0,0 +1,87 @@ +package de.neemann.digital.gui.components.data; + +import javax.swing.*; +import java.awt.*; + +/** + * @author hneemann + */ +public class DataSetComponent extends JComponent { + private static final int BORDER = 10; + private static final int SIZE = 20; + private static final int SEP2 = 3; + private static final int SEP = SEP2 * 2; + private static final Stroke NORMAL = new BasicStroke(0); + private static final Stroke THICK = new BasicStroke(2); + private final DataSet dataSet; + private int textWidth; + + public DataSetComponent(DataSet dataSet) { + this.dataSet = dataSet; + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + g.setColor(Color.WHITE); + g.fillRect(0, 0, getWidth(), getHeight()); + + textWidth = 0; + for (int i = 0; i < dataSet.signalSize(); i++) { + String text = dataSet.getSignal(i).getName(); + int w = g.getFontMetrics().stringWidth(text); + if (w > textWidth) textWidth = w; + } + int x = textWidth + BORDER + SEP; + + int yOffs = SIZE / 2 + g.getFontMetrics().getHeight() / 2; + g.setColor(Color.BLACK); + int y = BORDER; + for (int i = 0; i < dataSet.signalSize(); i++) { + String text = dataSet.getSignal(i).getName(); + g2.setColor(Color.BLACK); + g.drawString(text, BORDER, y + yOffs); + g2.setColor(Color.LIGHT_GRAY); + g.drawLine(x, y - SEP2, x + SIZE * dataSet.size(), y - SEP2); + y += SIZE + SEP; + } + g.drawLine(x, y - SEP2, x + SIZE * dataSet.size(), y - SEP2); + + + int[] last_ry = new int[dataSet.signalSize()]; + boolean first = true; + for (DataSample s : dataSet) { + g2.setStroke(NORMAL); + g2.setColor(Color.LIGHT_GRAY); + g.drawLine(x, BORDER - SEP2, x, (SIZE + SEP) * dataSet.signalSize() + BORDER - SEP2); + g2.setStroke(THICK); + g2.setColor(Color.BLACK); + y = BORDER; + for (int i = 0; i < dataSet.signalSize(); i++) { + + long width = dataSet.getWidth(i); + if (width == 0) width = 1; + //int ry = (int) (SIZE-(SIZE*(s.getValue(i)-dataSet.getMin().getValue(i)))/ width); + int ry = (int) (SIZE - (SIZE * s.getValue(i)) / width); + g.drawLine(x, y + ry, x + SIZE, y + ry); + if (!first && ry != last_ry[i]) + g.drawLine(x, y + last_ry[i], x, y + ry); + + last_ry[i] = ry; + y += SIZE + SEP; + } + first = false; + x += SIZE; + } + g2.setStroke(NORMAL); + g2.setColor(Color.LIGHT_GRAY); + g.drawLine(x, BORDER - SEP2, x, (SIZE + SEP) * dataSet.signalSize() + BORDER - SEP2); + } + + @Override + public Dimension getPreferredSize() { + int count = dataSet.size(); + if (count < 10) count = 10; + return new Dimension(SIZE * count + BORDER * 2 + textWidth, (SIZE + SEP) * dataSet.signalSize() + BORDER * 2); + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/data/DataSetDialog.java b/src/main/java/de/neemann/digital/gui/components/data/DataSetDialog.java new file mode 100644 index 000000000..9a715d974 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/data/DataSetDialog.java @@ -0,0 +1,76 @@ +package de.neemann.digital.gui.components.data; + +import de.neemann.digital.core.Model; +import de.neemann.digital.core.ModelEvent; +import de.neemann.digital.core.ModelStateObserver; +import de.neemann.digital.lang.Lang; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; + +/** + * @author hneemann + */ +public class DataSetDialog extends JDialog implements ModelStateObserver { + private final ModelEvent.Event type; + private final ArrayList signals; + private final DataSetComponent dsc; + private DataSample manualSample; + private int maintime; + private DataSet dataSet; + + public DataSetDialog(Frame owner, Model model, ModelEvent.Event type) { + super(owner, Lang.get("win_measures"), false); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setAlwaysOnTop(true); + this.type = type; + + signals = model.getSignals(); + dataSet = new DataSet(signals); + + dsc = new DataSetComponent(dataSet); + JScrollPane scrollPane = new JScrollPane(dsc); + getContentPane().add(scrollPane); + + addWindowListener(new WindowAdapter() { + @Override + public void windowOpened(WindowEvent e) { + model.addObserver(DataSetDialog.this); + } + + @Override + public void windowClosed(WindowEvent e) { + model.removeObserver(DataSetDialog.this); + } + }); + + scrollPane.getViewport().setPreferredSize(dsc.getPreferredSize()); + + pack(); + setLocationRelativeTo(owner); + } + + + @Override + public void handleEvent(ModelEvent event) { + if (event.getType() == ModelEvent.Event.MANUALCHANGE) { + if (manualSample == null) + manualSample = new DataSample(maintime, signals.size()); + manualSample.fillWith(signals); + } + + if (event.getType() == type) { + if (manualSample != null) { + dataSet.add(manualSample); + manualSample = null; + maintime++; + } + dataSet.add(new DataSample(maintime, signals.size()).fillWith(signals)); + maintime++; + } + dsc.repaint(); + } +} diff --git a/src/main/resources/lang/lang_de.properties b/src/main/resources/lang/lang_de.properties index 8b5ec56ad..f9260a134 100644 --- a/src/main/resources/lang/lang_de.properties +++ b/src/main/resources/lang/lang_de.properties @@ -144,6 +144,8 @@ menu_probe=Zeige Messwerte menu_probe_tt=Zeigt die Messwerte in einem eigenen Fenster an. menu_listing=Zeige Listing menu_listing_tt=Zeigt ein ROM-Listing mit der aktuellen Adresse markiert in eine eigenen Fenster an. +menu_graph=Messwerte grafisch darstellen +menu_graph_tt=Zeigt eine Grafik mit dem Messwerten \u00FCber der Zeit. menu_about=\u00DCber Digital diff --git a/src/main/resources/lang/lang_en.properties b/src/main/resources/lang/lang_en.properties index bbe8882a0..ed6ae0248 100644 --- a/src/main/resources/lang/lang_en.properties +++ b/src/main/resources/lang/lang_en.properties @@ -144,6 +144,8 @@ menu_probe=Show Probe Values menu_probe_tt=Shows values of probes in a separate window menu_listing=Show Listing menu_listing_tt=Shows a ROM listing with actual position marked in a separate window +menu_graph=Show Graph +menu_graph_tt=Shows a graph containing the measurement values win_saveChanges=Save Changes?