diff --git a/src/main/java/de/neemann/digital/data/DataPlotter.java b/src/main/java/de/neemann/digital/data/DataPlotter.java index e77357e09..a4504f06f 100644 --- a/src/main/java/de/neemann/digital/data/DataPlotter.java +++ b/src/main/java/de/neemann/digital/data/DataPlotter.java @@ -5,6 +5,8 @@ import de.neemann.digital.draw.graphics.Orientation; import de.neemann.digital.draw.graphics.Style; import de.neemann.digital.draw.graphics.Vector; import de.neemann.digital.draw.shapes.Drawable; +import de.neemann.digital.gui.sync.NoSync; +import de.neemann.digital.gui.sync.Sync; /** * The dataSet stores the collected DataSamples. @@ -16,6 +18,7 @@ public class DataPlotter implements Drawable { private final ValueTable data; private final int maxTextLength; private double size = SIZE; + private Sync modelSync = NoSync.INST; /** * Creates a new instance @@ -44,7 +47,7 @@ public class DataPlotter implements Drawable { * @param width width of the frame */ public void fitInside(int width) { - size = ((double) (width - getTextBorder())) / data.getRows(); + modelSync.access(() -> size = ((double) (width - getTextBorder())) / data.getRows()); } /** @@ -63,43 +66,45 @@ public class DataPlotter implements Drawable { @Override public void drawTo(Graphic g, Style highLight) { - int x = getTextBorder(); + modelSync.access(() -> { + int x = getTextBorder(); - int yOffs = SIZE / 2; - int y = BORDER; - int signals = data.getColumns(); - for (int i = 0; i < signals; i++) { - String text = data.getColumnName(i); - g.drawText(new Vector(x - 2, y + yOffs), new Vector(x + 1, y + yOffs), text, Orientation.RIGHTCENTER, Style.NORMAL); - g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH); - y += SIZE + SEP; - } - g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH); - - - int[] lastRy = new int[signals]; - boolean first = true; - double pos = 0; - for (Value[] s : data) { - int xx = (int) (pos + x); - g.drawLine(new Vector(xx, BORDER - SEP2), new Vector(xx, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH); - y = BORDER; + int yOffs = SIZE / 2; + int y = BORDER; + int signals = data.getColumns(); for (int i = 0; i < signals; i++) { - - long width = data.getMax(i); - if (width == 0) width = 1; - int ry = (int) (SIZE - (SIZE * s[i].getValue()) / width); - g.drawLine(new Vector(xx, y + ry), new Vector((int) (xx + size), y + ry), Style.NORMAL); - if (!first && ry != lastRy[i]) - g.drawLine(new Vector(xx, y + lastRy[i]), new Vector(xx, y + ry), Style.NORMAL); - - lastRy[i] = ry; + String text = data.getColumnName(i); + g.drawText(new Vector(x - 2, y + yOffs), new Vector(x + 1, y + yOffs), text, Orientation.RIGHTCENTER, Style.NORMAL); + g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH); y += SIZE + SEP; } - first = false; - pos += size; - } - g.drawLine(new Vector(x, BORDER - SEP2), new Vector(x, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH); + g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH); + + + int[] lastRy = new int[signals]; + boolean first = true; + double pos = 0; + for (Value[] s : data) { + int xx = (int) (pos + x); + g.drawLine(new Vector(xx, BORDER - SEP2), new Vector(xx, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH); + y = BORDER; + for (int i = 0; i < signals; i++) { + + long width = data.getMax(i); + if (width == 0) width = 1; + int ry = (int) (SIZE - (SIZE * s[i].getValue()) / width); + g.drawLine(new Vector(xx, y + ry), new Vector((int) (xx + size), y + ry), Style.NORMAL); + if (!first && ry != lastRy[i]) + g.drawLine(new Vector(xx, y + lastRy[i]), new Vector(xx, y + ry), Style.NORMAL); + + lastRy[i] = ry; + y += SIZE + SEP; + } + first = false; + pos += size; + } + g.drawLine(new Vector(x, BORDER - SEP2), new Vector(x, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH); + }); } private int getTextBorder() { @@ -117,7 +122,24 @@ public class DataPlotter implements Drawable { * @return the current width of the graphical representation */ public int getCurrentGraphicWidth() { - return getTextBorder() + (int) (data.getRows() * size); + return modelSync.access(new Runnable() { + private int r; + + @Override + public void run() { + r = DataPlotter.this.getTextBorder() + (int) (data.getRows() * size); + } + }).r; } + /** + * Sets lock to access the data + * + * @param modelSync the lock + * @return this for chained calls + */ + public DataPlotter setModelSync(Sync modelSync) { + this.modelSync = modelSync; + return this; + } } diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index cfe7422f8..47171f9ee 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -1003,9 +1003,9 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS windowPosManager.register("probe", new ProbeDialog(this, model, updateEvent, ordering, modelSync)).setVisible(true); if (settings.get(Keys.SHOW_DATA_GRAPH)) - windowPosManager.register("dataSet", new DataSetDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering, modelSync)).setVisible(true); + windowPosManager.register("dataSet", DataSetDialog.createLiveDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering, modelSync)).setVisible(true); if (settings.get(Keys.SHOW_DATA_GRAPH_MICRO)) - windowPosManager.register("dataSetMicro", new DataSetDialog(this, model, true, ordering, modelSync)).setVisible(true); + windowPosManager.register("dataSetMicro", DataSetDialog.createLiveDialog(this, model, true, ordering, modelSync)).setVisible(true); if (modelModifier != null) modelModifier.preInit(model); 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 index 62e019950..d17724eb2 100644 --- a/src/main/java/de/neemann/digital/gui/components/data/DataSetComponent.java +++ b/src/main/java/de/neemann/digital/gui/components/data/DataSetComponent.java @@ -3,6 +3,7 @@ package de.neemann.digital.gui.components.data; import de.neemann.digital.data.DataPlotter; import de.neemann.digital.data.ValueTable; import de.neemann.digital.draw.graphics.GraphicSwing; +import de.neemann.digital.gui.sync.Sync; import javax.swing.*; import java.awt.*; @@ -15,15 +16,20 @@ import java.awt.*; */ public class DataSetComponent extends JComponent { private final DataPlotter plotter; + /** + * The data stored in the plotter needs to be seen as part of the model. + * So a lock is necessary to access the data. + */ private JScrollPane scrollPane; /** * Creates a new dataSet * - * @param dataSet the dataSet to paint + * @param dataSet the dataSet to paint + * @param modelSync lock to access the model */ - public DataSetComponent(ValueTable dataSet) { - plotter = new DataPlotter(dataSet); + public DataSetComponent(ValueTable dataSet, Sync modelSync) { + plotter = new DataPlotter(dataSet).setModelSync(modelSync); addMouseWheelListener(e -> { double f = Math.pow(0.9, e.getWheelRotation()); scale(f, e.getX()); @@ -55,8 +61,8 @@ public class DataSetComponent extends JComponent { public void scale(double f, int xPos) { revalidate(); repaint(); - f=plotter.scale(f); - + f = plotter.scale(f); + // keep relative mouse position int x = (int) (xPos * f) - (xPos - (int) scrollPane.getViewport().getViewRect().getX()); if (x < 0) x = 0; scrollPane.getViewport().setViewPosition(new Point(x, 0)); 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 index 682c01634..74ede1456 100644 --- a/src/main/java/de/neemann/digital/gui/components/data/DataSetDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/data/DataSetDialog.java @@ -7,6 +7,7 @@ import de.neemann.digital.core.Signal; import de.neemann.digital.data.ValueTable; import de.neemann.digital.gui.SaveAsHelper; import de.neemann.digital.gui.components.OrderMerger; +import de.neemann.digital.gui.sync.NoSync; import de.neemann.digital.gui.sync.Sync; import de.neemann.digital.lang.Lang; import de.neemann.gui.IconCreator; @@ -32,27 +33,29 @@ public class DataSetDialog extends JDialog implements ModelStateObserver { private final DataSetComponent dsc; private final JScrollPane scrollPane; private final Sync modelSync; - private ValueTable logData; private DataSetObserver dataSetObserver; private static final Icon ICON_EXPAND = IconCreator.create("View-zoom-fit.png"); private static final Icon ICON_ZOOM_IN = IconCreator.create("View-zoom-in.png"); private static final Icon ICON_ZOOM_OUT = IconCreator.create("View-zoom-out.png"); + /** - * Creates a new instance + * Creates a instance prepared for "live logging" * * @param owner the parent frame - * @param model the model used to collect the data - * @param microStep true the event type which triggers a new DataSample - * @param ordering the ordering of the measurement values - * @param modelSync used to access the running model + * @param model the model + * @param microStep stepping mode + * @param ordering the ordering to use + * @param modelSync the lock to access the model + * @return the created instance */ - public DataSetDialog(Frame owner, Model model, boolean microStep, List ordering, Sync modelSync) { - super(owner, createTitle(microStep), false); - this.modelSync = modelSync; - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setAlwaysOnTop(true); + public static DataSetDialog createLiveDialog(Frame owner, Model model, boolean microStep, List ordering, Sync modelSync) { + String title; + if (microStep) + title = Lang.get("win_measures_microstep"); + else + title = Lang.get("win_measures_fullstep"); ArrayList signals = model.getSignalsCopy(); new OrderMerger(ordering) { @@ -62,11 +65,40 @@ public class DataSetDialog extends JDialog implements ModelStateObserver { } }.order(signals); + DataSetObserver dataSetObserver = new DataSetObserver(microStep, signals, MAX_SAMPLE_SIZE); + ValueTable logData = dataSetObserver.getLogData(); - dataSetObserver = new DataSetObserver(microStep, signals, MAX_SAMPLE_SIZE); - logData = dataSetObserver.getLogData(); + return new DataSetDialog(owner, title, model, logData, dataSetObserver, modelSync); + } - dsc = new DataSetComponent(logData); + /** + * Creates a new instance + * + * @param owner the parent frame + * @param title the frame title + * @param logData the data to visualize + */ + public DataSetDialog(Frame owner, String title, ValueTable logData) { + this(owner, title, null, logData, null, NoSync.INST); + } + + /** + * Creates a new instance + * + * @param owner the parent frame + * @param title the frame title + * @param model the model used to collect the data + * @param logData the data to visualize + * @param modelSync used to access the running model + */ + private DataSetDialog(Frame owner, String title, Model model, ValueTable logData, DataSetObserver dataSetObserver, Sync modelSync) { + super(owner, title, false); + this.dataSetObserver = dataSetObserver; + this.modelSync = modelSync; + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setAlwaysOnTop(true); + + dsc = new DataSetComponent(logData, modelSync); scrollPane = new JScrollPane(dsc); getContentPane().add(scrollPane); dsc.setScrollPane(scrollPane); @@ -100,17 +132,18 @@ public class DataSetDialog extends JDialog implements ModelStateObserver { getContentPane().add(toolBar, BorderLayout.NORTH); pack(); - addWindowListener(new WindowAdapter() { - @Override - public void windowOpened(WindowEvent e) { - modelSync.access(() -> model.addObserver(DataSetDialog.this)); - } + if (model != null) + addWindowListener(new WindowAdapter() { + @Override + public void windowOpened(WindowEvent e) { + modelSync.access(() -> model.addObserver(DataSetDialog.this)); + } - @Override - public void windowClosed(WindowEvent e) { - modelSync.access(() -> model.removeObserver(DataSetDialog.this)); - } - }); + @Override + public void windowClosed(WindowEvent e) { + modelSync.access(() -> model.removeObserver(DataSetDialog.this)); + } + }); scrollPane.getViewport().setPreferredSize(dsc.getPreferredSize()); @@ -123,7 +156,7 @@ public class DataSetDialog extends JDialog implements ModelStateObserver { JFileChooser fileChooser = new MyFileChooser(); fileChooser.setFileFilter(new FileNameExtensionFilter("Comma Separated Values", "csv")); new SaveAsHelper(DataSetDialog.this, fileChooser, "csv") - .checkOverwrite(file -> logData.saveCSV(file)); + .checkOverwrite(logData::saveCSV); } }.setToolTip(Lang.get("menu_saveData_tt")).createJMenuItem()); @@ -138,14 +171,6 @@ public class DataSetDialog extends JDialog implements ModelStateObserver { setLocationRelativeTo(owner); } - private static String createTitle(boolean microStep) { - if (microStep) - return Lang.get("win_measures_microstep"); - else - return Lang.get("win_measures_fullstep"); - } - - @Override public void handleEvent(ModelEvent event) { modelSync.access(() -> { diff --git a/src/main/java/de/neemann/digital/gui/components/testing/TestResultDialog.java b/src/main/java/de/neemann/digital/gui/components/testing/TestResultDialog.java index 3bdf23f37..a6852e7f0 100644 --- a/src/main/java/de/neemann/digital/gui/components/testing/TestResultDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/testing/TestResultDialog.java @@ -2,6 +2,7 @@ package de.neemann.digital.gui.components.testing; import de.neemann.digital.core.Model; import de.neemann.digital.core.NodeException; +import de.neemann.digital.data.ValueTable; import de.neemann.digital.data.ValueTableModel; import de.neemann.digital.data.Value; import de.neemann.digital.draw.elements.Circuit; @@ -9,15 +10,18 @@ import de.neemann.digital.draw.elements.PinException; import de.neemann.digital.draw.library.ElementLibrary; import de.neemann.digital.draw.library.ElementNotFoundException; import de.neemann.digital.draw.model.ModelCreator; +import de.neemann.digital.gui.components.data.DataSetDialog; import de.neemann.digital.lang.Lang; import de.neemann.digital.testing.*; import de.neemann.gui.ErrorMessage; import de.neemann.gui.IconCreator; import de.neemann.gui.LineBreaker; +import de.neemann.gui.ToolTipAction; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; +import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Collections; @@ -32,6 +36,8 @@ public class TestResultDialog extends JDialog { private static final Icon ICON_FAILED = IconCreator.create("testFailed.png"); private static final Icon ICON_PASSED = IconCreator.create("testPassed.png"); + private final ArrayList resultTableData; + /** * Creates a new result dialog. * @@ -50,6 +56,8 @@ public class TestResultDialog extends JDialog { Collections.sort(tsl); + resultTableData = new ArrayList<>(); + JTabbedPane tp = new JTabbedPane(); int i = 0; int errorTabIndex = -1; @@ -65,8 +73,9 @@ public class TestResultDialog extends JDialog { table.setDefaultRenderer(Value.class, new ValueRenderer()); table.setDefaultRenderer(Integer.class, new NumberRenderer()); final Font font = table.getFont(); - table.getColumnModel().getColumn(0).setMaxWidth(font.getSize()*4); + table.getColumnModel().getColumn(0).setMaxWidth(font.getSize() * 4); table.setRowHeight(font.getSize() * 6 / 5); + resultTableData.add(testExecuter.getResult()); String tabName; Icon tabIcon; @@ -89,6 +98,21 @@ public class TestResultDialog extends JDialog { if (errorTabIndex >= 0) tp.setSelectedIndex(errorTabIndex); + + JMenuBar bar = new JMenuBar(); + JMenu view = new JMenu(Lang.get("menu_view")); + ToolTipAction asGraph = new ToolTipAction(Lang.get("menu_showDataAsGraph")) { + @Override + public void actionPerformed(ActionEvent actionEvent) { + int tab = tp.getSelectedIndex(); + if (tab < 0) tab = 0; + new DataSetDialog(owner, Lang.get("win_testdata_N", tp.getTitleAt(tab)), resultTableData.get(tab)).setVisible(true); + } + }.setToolTip(Lang.get("menu_showDataAsGraph_tt")); + view.add(asGraph.createJMenuItem()); + bar.add(view); + setJMenuBar(bar); + getContentPane().add(tp); pack(); setLocationRelativeTo(owner); diff --git a/src/main/java/de/neemann/digital/gui/sync/LockSync.java b/src/main/java/de/neemann/digital/gui/sync/LockSync.java index b624d6c18..250532977 100644 --- a/src/main/java/de/neemann/digital/gui/sync/LockSync.java +++ b/src/main/java/de/neemann/digital/gui/sync/LockSync.java @@ -20,20 +20,22 @@ public class LockSync implements Sync { } @Override - public void access(Runnable run) { + public A access(A run) { lock.lock(); try { run.run(); + return run; } finally { lock.unlock(); } } @Override - public void accessNEx(Sync.ModelRun run) throws NodeException { + public A accessNEx(A run) throws NodeException { lock.lock(); try { run.run(); + return run; } finally { lock.unlock(); } diff --git a/src/main/java/de/neemann/digital/gui/sync/NoSync.java b/src/main/java/de/neemann/digital/gui/sync/NoSync.java index 545d17bde..75dc9c88d 100644 --- a/src/main/java/de/neemann/digital/gui/sync/NoSync.java +++ b/src/main/java/de/neemann/digital/gui/sync/NoSync.java @@ -18,12 +18,14 @@ public final class NoSync implements Sync { } @Override - public void access(Runnable run) { + public A access(A run) { run.run(); + return run; } @Override - public void accessNEx(Sync.ModelRun run) throws NodeException { + public A accessNEx(A run) throws NodeException { run.run(); + return run; } } diff --git a/src/main/java/de/neemann/digital/gui/sync/Sync.java b/src/main/java/de/neemann/digital/gui/sync/Sync.java index f87051b8e..a3de3a2ac 100644 --- a/src/main/java/de/neemann/digital/gui/sync/Sync.java +++ b/src/main/java/de/neemann/digital/gui/sync/Sync.java @@ -13,16 +13,20 @@ public interface Sync { * Calls the given runnable * * @param run the runnable to execute + * @param the type oth the runnable + * @return the given runnable. Used for chained calls */ - void access(Runnable run); + A access(A run); /** * Same as access, but catches an exception * * @param run the runnable to execute + * @param the type oth the runnable + * @return the given runnable. Used for chained calls * @throws NodeException NodeException */ - void accessNEx(ModelRun run) throws NodeException; + A accessNEx(A run) throws NodeException; /** * Like runnable but throws an exception diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index bc20cc3f4..f7cc6c6d1 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -863,7 +863,7 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Letzte Aktion rückgängig machen Wiederherstellen Letzte rückgängig gemachte Aktion wieder herstellen. - Als Graph anzeigen. + Zeige Graph Zeigt die Daten als Graph an. Digital @@ -947,6 +947,7 @@ Die Icons stammen aus dem Tango Desktop Project. Eintrag nach unten schieben Eintrag nach oben schieben Alle möglichen Lösungen + Testdaten {0} Beenden bestätigen! Messwerte Messwerte im Vollschrittmodus diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index cc46607ea..edf409d0e 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -851,6 +851,8 @@ The names of the variables may not be unique. Revert last modification Redo Apply last reverted modification again. + Show graph + Show the data as a Graph. Digital @@ -941,6 +943,7 @@ The icons are taken from the Tango Desktop Project. Table Export Select + Testdata {0} Help