2017-05-26 10:26:56 +02:00

1422 lines
57 KiB
Java

package de.neemann.digital.gui;
import de.neemann.digital.analyse.AnalyseException;
import de.neemann.digital.analyse.ModelAnalyser;
import de.neemann.digital.analyse.TruthTable;
import de.neemann.digital.analyse.expression.format.FormatToExpression;
import de.neemann.digital.core.*;
import de.neemann.digital.core.element.ElementAttributes;
import de.neemann.digital.core.element.Key;
import de.neemann.digital.core.element.Keys;
import de.neemann.digital.core.io.In;
import de.neemann.digital.core.io.Out;
import de.neemann.digital.core.memory.ROM;
import de.neemann.digital.core.wiring.Clock;
import de.neemann.digital.draw.elements.*;
import de.neemann.digital.draw.gif.GifExporter;
import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.library.CustomElement;
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.draw.model.RealTimeClock;
import de.neemann.digital.draw.shapes.Drawable;
import de.neemann.digital.draw.shapes.ShapeFactory;
import de.neemann.digital.gui.components.*;
import de.neemann.digital.gui.components.data.DataSetDialog;
import de.neemann.digital.gui.components.expression.ExpressionDialog;
import de.neemann.digital.gui.components.table.TableDialog;
import de.neemann.digital.gui.components.testing.TestResultDialog;
import de.neemann.digital.gui.components.tree.LibraryTreeModel;
import de.neemann.digital.gui.components.tree.SelectTree;
import de.neemann.digital.gui.remote.DigitalHandler;
import de.neemann.digital.gui.remote.RemoteException;
import de.neemann.digital.gui.remote.RemoteSever;
import de.neemann.digital.gui.state.State;
import de.neemann.digital.gui.state.StateManager;
import de.neemann.digital.gui.sync.LockSync;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import de.neemann.digital.testing.TestCaseElement;
import de.neemann.digital.testing.TestingDataException;
import de.neemann.gui.*;
import de.neemann.gui.language.Language;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import static javax.swing.JOptionPane.showInputDialog;
/**
* The main frame of the Digital Simulator
*
* @author hneemann
*/
public final class Main extends JFrame implements ClosingWindowListener.ConfirmSave, ErrorStopper, FileHistory.OpenInterface, DigitalRemoteInterface, StatusInterface {
private static final ArrayList<Key> ATTR_LIST = new ArrayList<>();
private static boolean experimental;
private static File lastExportDirectory;
/**
* @return true if experimental features are enabled
*/
public static boolean enableExperimental() {
return experimental;
}
static {
ATTR_LIST.add(Keys.SHOW_DATA_TABLE);
ATTR_LIST.add(Keys.SHOW_DATA_GRAPH);
ATTR_LIST.add(Keys.SHOW_DATA_GRAPH_MICRO);
}
private static final String MESSAGE = Lang.get("message");
private static final Icon ICON_RUN = IconCreator.create("media-playback-start.png");
private static final Icon ICON_MICRO = IconCreator.create("media-playback-start-2.png");
private static final Icon ICON_TEST = IconCreator.create("media-playback-start-T.png");
private static final Icon ICON_STEP = IconCreator.create("media-seek-forward.png");
private static final Icon ICON_STOP = IconCreator.create("media-playback-stop.png");
private static final Icon ICON_NEW = IconCreator.create("document-new.png");
private static final Icon ICON_NEW_SUB = IconCreator.create("document-new-sub.png");
private static final Icon ICON_OPEN = IconCreator.create("document-open.png");
private static final Icon ICON_OPEN_WIN = IconCreator.create("document-open-new.png");
private static final Icon ICON_SAVE = IconCreator.create("document-save.png");
private static final Icon ICON_SAVE_AS = IconCreator.create("document-save-as.png");
private static final Icon ICON_FAST = IconCreator.create("media-skip-forward.png");
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");
private static final Icon ICON_HELP = IconCreator.create("help.png");
private final CircuitComponent circuitComponent;
private final ToolTipAction save;
private final ElementLibrary library;
private final ShapeFactory shapeFactory;
private final JLabel statusLabel;
private final StateManager stateManager = new StateManager();
private final ElementAttributes settings = new ElementAttributes();
private final ScheduledThreadPoolExecutor timerExecutor = new ScheduledThreadPoolExecutor(1);
private final WindowPosManager windowPosManager = new WindowPosManager();
private final InsertHistory insertHistory;
private final boolean keepPrefMainFile;
private ToolTipAction doStep;
private ToolTipAction runToBreakAction;
private File baseFilename;
private File filename;
private FileHistory fileHistory;
private Sync modelSync;
private Model model;
private ModelCreator modelCreator;
private boolean realTimeClockRunning;
private State stoppedState;
private RunModelState runModelState;
private State runModelMicroState;
private JComponent componentOnPane;
private LibraryTreeModel treeModel;
/**
* Creates a new instance
*
* @param builder the builder
*/
private Main(MainBuilder builder) {
super(Lang.get("digital"));
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setIconImages(IconCreator.createImages("icon32.png", "icon64.png", "icon128.png"));
keepPrefMainFile = builder.keepPrefMainFile;
if (builder.library != null) library = builder.library;
else library = new ElementLibrary();
shapeFactory = new ShapeFactory(library, Settings.getInstance().get(Keys.SETTINGS_IEEE_SHAPES));
fileHistory = new FileHistory(this);
baseFilename = builder.baseFileName;
circuitComponent = new CircuitComponent(this, library, shapeFactory);
if (builder.circuit != null) {
SwingUtilities.invokeLater(() -> circuitComponent.setCircuit(builder.circuit));
setFilename(builder.fileToOpen, false);
} else {
if (builder.fileToOpen != null) {
SwingUtilities.invokeLater(() -> loadFile(builder.fileToOpen, builder.library == null, false));
} else {
File name = fileHistory.getMostRecent();
if (name != null) {
SwingUtilities.invokeLater(() -> loadFile(name, true, false));
}
}
}
library.addListener(circuitComponent);
getContentPane().add(circuitComponent);
componentOnPane = circuitComponent;
statusLabel = new JLabel(" ");
getContentPane().add(statusLabel, BorderLayout.SOUTH);
setupStates();
JMenuBar menuBar = new JMenuBar();
JToolBar toolBar = new JToolBar();
save = createFileMenu(menuBar, toolBar, builder.allowAllFileActions);
toolBar.addSeparator();
createViewMenu(menuBar, toolBar);
toolBar.addSeparator();
createEditMenu(menuBar);
toolBar.add(circuitComponent.getUndoAction().createJButtonNoText());
toolBar.add(circuitComponent.getRedoAction().createJButtonNoText());
toolBar.add(circuitComponent.getDeleteAction().createJButtonNoText());
toolBar.addSeparator();
createStartMenu(menuBar, toolBar);
createAnalyseMenu(menuBar);
toolBar.addSeparator();
insertHistory = new InsertHistory(toolBar, library);
library.addListener(insertHistory);
final LibrarySelector librarySelector = new LibrarySelector(library, shapeFactory);
library.addListener(librarySelector);
menuBar.add(librarySelector.buildMenu(insertHistory, circuitComponent));
addWindowListener(new ClosingWindowListener(this, this));
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
clearModelDescription(); // stop model timer if running
timerExecutor.shutdown();
library.removeListener(librarySelector);
library.removeListener(insertHistory);
library.removeListener(circuitComponent);
if (treeModel != null)
library.removeListener(treeModel);
windowPosManager.closeAll();
}
});
getContentPane().add(toolBar, BorderLayout.NORTH);
setJMenuBar(menuBar);
JMenu help = InfoDialog.getInstance().addToFrame(this, MESSAGE);
help.add(new ToolTipAction(Lang.get("menu_help_elements")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
try {
new ElementHelpDialog(Main.this, library, shapeFactory).setVisible(true);
} catch (NodeException | PinException e) {
new ErrorMessage(Lang.get("msg_creatingHelp")).addCause(e).show(Main.this);
}
}
}.setToolTip(Lang.get("menu_help_elements_tt")).createJMenuItem());
setPreferredSize(Screen.getInstance().scale(new Dimension(1024, 768)));
pack();
setLocationRelativeTo(builder.parent);
}
private void createViewMenu(JMenuBar menuBar, JToolBar toolBar) {
ToolTipAction maximize = new ToolTipAction(Lang.get("menu_maximize"), ICON_EXPAND) {
@Override
public void actionPerformed(ActionEvent e) {
circuitComponent.fitCircuit();
}
};
ToolTipAction zoomIn = new ToolTipAction(Lang.get("menu_zoomIn"), ICON_ZOOM_IN) {
@Override
public void actionPerformed(ActionEvent e) {
circuitComponent.scaleCircuit(1.25);
}
};
ToolTipAction zoomOut = new ToolTipAction(Lang.get("menu_zoomOut"), ICON_ZOOM_OUT) {
@Override
public void actionPerformed(ActionEvent e) {
circuitComponent.scaleCircuit(0.8);
}
};
ToolTipAction viewHelp = new ToolTipAction(Lang.get("menu_viewHelp"), ICON_HELP) {
@Override
public void actionPerformed(ActionEvent e) {
final Circuit circuit = circuitComponent.getCircuit();
final String name = Lang.get("msg_actualCircuit");
File file = filename;
if (file == null)
file = new File(name);
try {
ElementLibrary.ElementTypeDescriptionCustom description =
new ElementLibrary.ElementTypeDescriptionCustom(file,
attributes -> new CustomElement(circuit, library, filename),
circuit.getAttributes(), circuit.getInputNames());
description.setShortName(name);
description.setDescription(circuit.getAttributes().get(Keys.DESCRIPTION));
new ElementHelpDialog(Main.this, description, circuit.getAttributes()).setVisible(true);
} catch (PinException | NodeException e1) {
new ErrorMessage(Lang.get("msg_creatingHelp")).addCause(e1).show(Main.this);
}
}
}.setToolTip(Lang.get("menu_viewHelp_tt"));
JCheckBoxMenuItem treeCheckBox = new JCheckBoxMenuItem(Lang.get("menu_treeSelect"));
treeCheckBox.setToolTipText(Lang.get("menu_treeSelect_tt"));
treeCheckBox.addActionListener(actionEvent -> {
getContentPane().remove(componentOnPane);
if (treeCheckBox.isSelected()) {
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
treeModel = new LibraryTreeModel(library);
split.setLeftComponent(new JScrollPane(new SelectTree(treeModel, circuitComponent, shapeFactory, insertHistory)));
split.setRightComponent(circuitComponent);
getContentPane().add(split);
componentOnPane = split;
} else {
if (treeModel != null) {
library.removeListener(treeModel);
treeModel = null;
}
getContentPane().add(circuitComponent);
componentOnPane = circuitComponent;
}
revalidate();
});
if (Settings.getInstance().get(Keys.SETTINGS_DEFAULT_TREESELECT))
treeCheckBox.doClick();
toolBar.add(viewHelp.createJButtonNoText());
toolBar.add(zoomIn.createJButtonNoText());
toolBar.add(zoomOut.createJButtonNoText());
toolBar.add(maximize.createJButtonNoText());
JMenu view = new JMenu(Lang.get("menu_view"));
menuBar.add(view);
view.add(maximize.createJMenuItem());
view.add(zoomOut.createJMenuItem());
view.add(zoomIn.createJMenuItem());
view.addSeparator();
view.add(treeCheckBox);
view.addSeparator();
view.add(viewHelp.createJMenuItem());
}
/**
* Creates the file menu and adds it to menu and toolbar
*
* @param menuBar the menuBar
* @param toolBar the toolBar
* @return the save action
*/
private ToolTipAction createFileMenu(JMenuBar menuBar, JToolBar toolBar, boolean allowAll) {
ToolTipAction newFile = new ToolTipAction(Lang.get("menu_new"), ICON_NEW) {
@Override
public void actionPerformed(ActionEvent e) {
if (ClosingWindowListener.checkForSave(Main.this, Main.this)) {
setFilename(null, true);
circuitComponent.setCircuit(new Circuit());
windowPosManager.closeAll();
try {
library.setRootFilePath(null);
} catch (IOException e1) {
// can not happen, no folder is scanned
}
}
}
}.setToolTip(Lang.get("menu_new_tt")).setActive(allowAll);
ToolTipAction newSubFile = new ToolTipAction(Lang.get("menu_newSub"), ICON_NEW_SUB) {
@Override
public void actionPerformed(ActionEvent e) {
new MainBuilder()
.setParent(Main.this)
.setLibrary(library)
.setCircuit(new Circuit())
.setBaseFileName(getBaseFileName())
.keepPrefMainFile()
.build()
.setVisible(true);
}
}.setToolTip(Lang.get("menu_newSub_tt"));
ToolTipAction open = new ToolTipAction(Lang.get("menu_open"), ICON_OPEN) {
@Override
public void actionPerformed(ActionEvent e) {
if (ClosingWindowListener.checkForSave(Main.this, Main.this)) {
JFileChooser fc = getJFileChooser(baseFilename);
if (fc.showOpenDialog(Main.this) == JFileChooser.APPROVE_OPTION) {
loadFile(fc.getSelectedFile(), true, true);
}
}
}
}.setActive(allowAll);
ToolTipAction openWin = new ToolTipAction(Lang.get("menu_openWin"), ICON_OPEN_WIN) {
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fc = getJFileChooser(baseFilename);
if (fc.showOpenDialog(Main.this) == JFileChooser.APPROVE_OPTION) {
new MainBuilder()
.setParent(Main.this)
.setFileToOpen(fc.getSelectedFile())
.build()
.setVisible(true);
}
}
}.setToolTip(Lang.get("menu_openWin_tt")).setActive(allowAll);
JMenu openRecent = new JMenu(Lang.get("menu_openRecent"));
fileHistory.setMenu(openRecent);
openRecent.setEnabled(allowAll);
ToolTipAction saveAs = new ToolTipAction(Lang.get("menu_saveAs"), ICON_SAVE_AS) {
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fc = getJFileChooser(baseFilename);
final SaveAsHelper saveAsHelper = new SaveAsHelper(Main.this, fc, "dig");
saveAsHelper.checkOverwrite(
file -> {
if (library.isFileAccessible(file))
saveFile(file, !keepPrefMainFile);
else {
Object[] options = {Lang.get("btn_saveAnyway"), Lang.get("btn_newName"), Lang.get("cancel")};
int res = JOptionPane.showOptionDialog(Main.this,
Lang.get("msg_fileNotAccessible"),
Lang.get("msg_warning"),
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
switch (res) {
case 0:
saveFile(file, true);
break;
case 1:
saveAsHelper.retryFileSelect();
}
}
}
);
}
}.setActive(allowAll);
ToolTipAction save = new ToolTipAction(Lang.get("menu_save"), ICON_SAVE) {
@Override
public void actionPerformed(ActionEvent e) {
if (filename == null)
saveAs.actionPerformed(e);
else
saveFile(filename, false);
}
};
JMenu export = new JMenu(Lang.get("menu_export"));
export.add(new ExportAction(Lang.get("menu_exportSVG"), "svg", GraphicSVGIndex::new));
export.add(new ExportAction(Lang.get("menu_exportSVGLaTex"), "svg", GraphicSVGLaTeX::new));
export.add(new ExportAction(Lang.get("menu_exportPNGSmall"), "png", (out, min, max) -> GraphicsImage.create(out, min, max, "PNG", 1)));
export.add(new ExportAction(Lang.get("menu_exportPNGLarge"), "png", (out, min, max) -> GraphicsImage.create(out, min, max, "PNG", 2)));
if (enableExperimental())
export.add(new ExportGifAction(Lang.get("menu_exportAnimatedGIF")));
JMenu file = new JMenu(Lang.get("menu_file"));
menuBar.add(file);
file.add(newFile.createJMenuItem());
file.add(newSubFile.createJMenuItem());
file.add(openRecent);
file.add(open.createJMenuItem());
file.add(openWin.createJMenuItem());
file.add(save.createJMenuItem());
file.add(saveAs.createJMenuItem());
file.add(export);
toolBar.add(newFile.createJButtonNoText());
toolBar.add(open.createJButtonNoText());
toolBar.add(save.createJButtonNoText());
return save;
}
private File getBaseFileName() {
if (filename != null) return filename;
return baseFilename;
}
/**
* Creates the edit menu
*
* @param menuBar the menu bar
*/
private void createEditMenu(JMenuBar menuBar) {
JMenu edit = new JMenu(Lang.get("menu_edit"));
menuBar.add(edit);
ToolTipAction orderInputs = new ToolTipAction(Lang.get("menu_orderInputs")) {
@Override
public void actionPerformed(ActionEvent e) {
ElementOrder o = new ElementOrder(circuitComponent,
element -> element.equalsDescription(In.DESCRIPTION)
|| element.equalsDescription(Clock.DESCRIPTION));
new ElementOrderer<>(Main.this, Lang.get("menu_orderInputs"), o).showDialog();
}
}.setToolTip(Lang.get("menu_orderInputs_tt"));
ToolTipAction orderOutputs = new ToolTipAction(Lang.get("menu_orderOutputs")) {
@Override
public void actionPerformed(ActionEvent e) {
ElementOrder o = new ElementOrder(circuitComponent,
element -> element.equalsDescription(Out.DESCRIPTION)
|| element.equalsDescription(Out.LEDDESCRIPTION));
new ElementOrderer<>(Main.this, Lang.get("menu_orderOutputs"), o).showDialog();
}
}.setToolTip(Lang.get("menu_orderOutputs_tt"));
ToolTipAction orderMeasurements = new ToolTipAction(Lang.get("menu_orderMeasurements")) {
@Override
public void actionPerformed(ActionEvent e) {
orderMeasurements();
}
}.setToolTip(Lang.get("menu_orderMeasurements_tt"));
ToolTipAction editAttributes = new ToolTipAction(Lang.get("menu_editAttributes")) {
@Override
public void actionPerformed(ActionEvent e) {
circuitComponent.getCircuit().editAttributes(Main.this);
}
}.setToolTip(Lang.get("menu_editAttributes_tt"));
ToolTipAction editSettings = new ToolTipAction(Lang.get("menu_editSettings")) {
@Override
public void actionPerformed(ActionEvent e) {
final Language oldLang = Settings.getInstance().get(Keys.SETTINGS_LANGUAGE);
final boolean oldIeeeShapes = Settings.getInstance().get(Keys.SETTINGS_IEEE_SHAPES);
if (new AttributeDialog(Main.this, Settings.SETTINGS_KEYS, Settings.getInstance().getAttributes()).showDialog()) {
FormatToExpression.setDefaultFormat(Settings.getInstance().get(Keys.SETTINGS_EXPRESSION_FORMAT));
final Language newLang = Settings.getInstance().getAttributes().get(Keys.SETTINGS_LANGUAGE);
final boolean newIeeeShapes = Settings.getInstance().get(Keys.SETTINGS_IEEE_SHAPES);
if (!newLang.equals(oldLang) || (oldIeeeShapes != newIeeeShapes)) {
Lang.setLanguage(newLang);
JOptionPane.showMessageDialog(Main.this, Lang.get("msg_restartNeeded"));
}
}
}
}.setToolTip(Lang.get("menu_editSettings_tt"));
ToolTipAction actualToDefault = new ToolTipAction(Lang.get("menu_actualToDefault")) {
@Override
public void actionPerformed(ActionEvent e) {
circuitComponent.getCircuit().actualToDefault();
stoppedState.enter();
}
}.setToolTip(Lang.get("menu_actualToDefault_tt"));
ToolTipAction restoreAllFuses = new ToolTipAction(Lang.get("menu_restoreAllFuses")) {
@Override
public void actionPerformed(ActionEvent e) {
circuitComponent.getCircuit().restoreAllFuses(library);
stoppedState.enter();
}
}.setToolTip(Lang.get("menu_restoreAllFuses_tt"));
ToolTipAction insertAsNew = new ToolTipAction(Lang.get("menu_insertAsNew")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
try {
Object data = clipboard.getData(DataFlavor.stringFlavor);
if (data instanceof String) {
ArrayList<Movable> elements = CircuitTransferable.createList(data, shapeFactory, new Vector(0, 0));
Circuit circuit = new Circuit();
for (Movable m : elements) {
if (m instanceof Wire)
circuit.add((Wire) m);
if (m instanceof VisualElement)
circuit.add((VisualElement) m);
}
new MainBuilder()
.setParent(Main.this)
.setLibrary(library)
.setCircuit(circuit)
.setBaseFileName(getBaseFileName())
.keepPrefMainFile()
.build()
.setVisible(true);
}
} catch (Exception e1) {
e1.printStackTrace();
SwingUtilities.invokeLater(new ErrorMessage(Lang.get("msg_clipboardContainsNoImportableData")).setComponent(Main.this));
}
}
}.setToolTip(Lang.get("menu_insertAsNew_tt"));
edit.add(editAttributes.createJMenuItem());
edit.add(actualToDefault.createJMenuItem());
edit.add(restoreAllFuses.createJMenuItem());
edit.addSeparator();
edit.add(orderInputs.createJMenuItem());
edit.add(orderOutputs.createJMenuItem());
edit.add(orderMeasurements.createJMenuItem());
edit.add(createSpecialEditMenu());
edit.addSeparator();
JMenuItem copyItem = new JMenuItem(circuitComponent.getCopyAction());
copyItem.setAccelerator(KeyStroke.getKeyStroke('C', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
edit.add(copyItem);
JMenuItem pasteItem = new JMenuItem(circuitComponent.getPasteAction());
pasteItem.setAccelerator(KeyStroke.getKeyStroke('V', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
edit.add(pasteItem);
JMenuItem rotateItem = new JMenuItem(circuitComponent.getRotateAction());
rotateItem.setAccelerator(KeyStroke.getKeyStroke('R'));
edit.add(rotateItem);
edit.add(insertAsNew.createJMenuItem());
edit.addSeparator();
edit.add(editSettings.createJMenuItem());
}
private JMenu createSpecialEditMenu() {
JMenu special = new JMenu(Lang.get("menu_special"));
special.add(new ToolTipAction(Lang.get("menu_addPrefix")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (!circuitComponent.isLocked()) {
String prefix = showInputDialog(Lang.get("menu_addPrefix"));
if (prefix != null && prefix.length() > 0) {
boolean modified = false;
for (Drawable d : circuitComponent.getHighLighted()) {
if (d instanceof VisualElement) {
VisualElement v = (VisualElement) d;
if (v.equalsDescription(In.DESCRIPTION) || v.equalsDescription(Out.DESCRIPTION)) {
ElementAttributes attr = v.getElementAttributes();
String l = prefix + attr.getLabel();
attr.set(Keys.LABEL, l);
modified = true;
}
}
}
if (modified)
circuitComponent.hasChanged();
}
}
}
}.setToolTip(Lang.get("menu_addPrefix_tt")).createJMenuItem());
special.add(new ToolTipAction(Lang.get("menu_removePrefix")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (!circuitComponent.isLocked()) {
boolean modified = false;
for (Drawable d : circuitComponent.getHighLighted()) {
if (d instanceof VisualElement) {
VisualElement v = (VisualElement) d;
if (v.equalsDescription(In.DESCRIPTION) || v.equalsDescription(Out.DESCRIPTION)) {
ElementAttributes attr = v.getElementAttributes();
String l = attr.getLabel();
if (l.length() > 1) {
attr.set(Keys.LABEL, l.substring(1));
modified = true;
}
}
}
}
if (modified)
circuitComponent.hasChanged();
}
}
}.setToolTip(Lang.get("menu_removePrefix_tt")).createJMenuItem());
special.add(new ToolTipAction(Lang.get("menu_numbering")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (!circuitComponent.isLocked())
new NumberingWizard(Main.this, circuitComponent).start();
}
}.setToolTip(Lang.get("menu_numbering_tt")).createJMenuItem());
special.add(new ToolTipAction(Lang.get("menu_removePinNumbers")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (!circuitComponent.isLocked()) {
boolean modified = false;
for (VisualElement v : circuitComponent.getCircuit().getElements()) {
if (v.equalsDescription(In.DESCRIPTION)
|| v.equalsDescription(Clock.DESCRIPTION)
|| v.equalsDescription(Out.DESCRIPTION)) {
ElementAttributes attr = v.getElementAttributes();
int p = attr.get(Keys.PINNUMBER);
if (p > 0) {
attr.set(Keys.PINNUMBER, 0);
modified = true;
}
}
}
if (modified)
circuitComponent.hasChanged();
}
}
}.setToolTip(Lang.get("menu_removePinNumbers_tt")).createJMenuItem());
return special;
}
/**
* Creates the start menu
*
* @param menuBar the menu bar
* @param toolBar the tool bar
*/
private void createStartMenu(JMenuBar menuBar, JToolBar toolBar) {
doStep = new ToolTipAction(Lang.get("menu_step"), ICON_STEP) {
@Override
public void actionPerformed(ActionEvent e) {
try {
model.doMicroStep(false);
circuitComponent.removeHighLighted();
modelCreator.addNodeElementsTo(model.nodesToUpdate(), circuitComponent.getHighLighted());
circuitComponent.hasChanged();
doStep.setEnabled(model.needsUpdate());
} catch (Exception e1) {
SwingUtilities.invokeLater(
new ErrorMessage(Lang.get("msg_errorCalculatingStep")).addCause(e1).setComponent(Main.this)
);
}
}
}.setToolTip(Lang.get("menu_step_tt"));
ToolTipAction runModelAction = runModelState.createToolTipAction(Lang.get("menu_run"), ICON_RUN)
.setToolTip(Lang.get("menu_run_tt"));
ToolTipAction runModelMicroAction = runModelMicroState.createToolTipAction(Lang.get("menu_micro"), ICON_MICRO)
.setToolTip(Lang.get("menu_micro_tt"));
runToBreakAction = new ToolTipAction(Lang.get("menu_fast"), ICON_FAST) {
@Override
public void actionPerformed(ActionEvent e) {
try {
int i = model.runToBreak();
circuitComponent.hasChanged();
statusLabel.setText(Lang.get("stat_clocks", i));
} catch (NodeException e1) {
stoppedState.enter();
new ErrorMessage(Lang.get("msg_fastRunError")).addCause(e1).show(Main.this);
}
}
}.setToolTip(Lang.get("menu_fast_tt")).setActive(false);
ToolTipAction stoppedStateAction = stoppedState.createToolTipAction(Lang.get("menu_element"), ICON_STOP).setToolTip(Lang.get("menu_element_tt"));
ToolTipAction runTests = new ToolTipAction(Lang.get("menu_runTests"), ICON_TEST) {
@Override
public void actionPerformed(ActionEvent e) {
startTests();
}
}.setToolTip(Lang.get("menu_runTests_tt"));
ToolTipAction speedTest = new ToolTipAction(Lang.get("menu_speedTest")) {
@Override
public void actionPerformed(ActionEvent e) {
try {
Model model = new ModelCreator(circuitComponent.getCircuit(), library).createModel(false);
SpeedTest speedTest = new SpeedTest(model);
String frequency = Integer.toString(speedTest.calculate() / 1000);
circuitComponent.getCircuit().clearState();
JOptionPane.showMessageDialog(Main.this, Lang.get("msg_frequency_N", frequency));
} catch (Exception e1) {
new ErrorMessage(Lang.get("msg_speedTestError")).addCause(e1).show();
}
}
}.setToolTip(Lang.get("menu_speedTest_tt"));
ToolTipAction editRunAttributes = new ToolTipAction(Lang.get("menu_editRunAttributes")) {
@Override
public void actionPerformed(ActionEvent e) {
new AttributeDialog(Main.this, ATTR_LIST, settings).showDialog();
}
}.setToolTip(Lang.get("menu_editRunAttributes_tt"));
JMenu run = new JMenu(Lang.get("menu_sim"));
menuBar.add(run);
run.add(editRunAttributes.createJMenuItem());
run.addSeparator();
run.add(runModelAction.createJMenuItem());
run.add(runModelMicroAction.createJMenuItem());
run.add(doStep.createJMenuItem());
run.add(runToBreakAction.createJMenuItem());
run.add(stoppedStateAction.createJMenuItem());
run.add(runTests.createJMenuItem());
run.addSeparator();
run.add(speedTest.createJMenuItem());
doStep.setEnabled(false);
toolBar.add(runModelState.setIndicator(runModelAction.createJButtonNoText()));
toolBar.add(runToBreakAction.createJButtonNoText());
toolBar.add(stoppedStateAction.createJButtonNoText());
toolBar.addSeparator();
toolBar.add(runModelMicroState.setIndicator(runModelMicroAction.createJButtonNoText()));
toolBar.add(doStep.createJButtonNoText());
toolBar.addSeparator();
toolBar.add(runTests.createJButtonNoText());
}
/**
* starts the tests
*/
public void startTests() {
try {
ArrayList<TestResultDialog.TestSet> tsl = new ArrayList<>();
for (VisualElement el : circuitComponent.getCircuit().getElements())
if (el.equalsDescription(TestCaseElement.TESTCASEDESCRIPTION))
tsl.add(new TestResultDialog.TestSet(
el.getElementAttributes().get(TestCaseElement.TESTDATA),
el.getElementAttributes().getCleanLabel()));
if (tsl.isEmpty())
throw new TestingDataException(Lang.get("err_noTestData"));
windowPosManager.register("testResult", new TestResultDialog(Main.this, tsl, circuitComponent.getCircuit(), library)).setVisible(true);
stoppedState.enter();
} catch (NodeException | ElementNotFoundException | PinException | TestingDataException | RuntimeException e1) {
showErrorAndStopModel(Lang.get("msg_runningTestError"), e1);
}
}
/**
* Creates the analyse menu
*
* @param menuBar the menu bar
*/
private void createAnalyseMenu(JMenuBar menuBar) {
JMenu analyse = new JMenu(Lang.get("menu_analyse"));
menuBar.add(analyse);
analyse.add(new ToolTipAction(Lang.get("menu_analyse")) {
@Override
public void actionPerformed(ActionEvent e) {
try {
Model model = new ModelCreator(circuitComponent.getCircuit(), library).createModel(false);
new TableDialog(Main.this, new ModelAnalyser(model).analyse(), library, shapeFactory, getBaseFileName())
.setVisible(true);
stoppedState.enter();
} catch (PinException | NodeException | AnalyseException | ElementNotFoundException | RuntimeException e1) {
showErrorAndStopModel(Lang.get("msg_analyseErr"), e1);
}
}
}
.setToolTip(Lang.get("menu_analyse_tt"))
.createJMenuItem());
analyse.add(new ToolTipAction(Lang.get("menu_synthesise")) {
@Override
public void actionPerformed(ActionEvent e) {
TruthTable tt = new TruthTable(3).addResult();
new TableDialog(Main.this, tt, library, shapeFactory, getBaseFileName()).setVisible(true);
stoppedState.enter();
}
}
.setToolTip(Lang.get("menu_synthesise_tt"))
.createJMenuItem());
analyse.add(new ToolTipAction(Lang.get("menu_expression")) {
@Override
public void actionPerformed(ActionEvent e) {
new ExpressionDialog(Main.this, library, shapeFactory, getBaseFileName()).setVisible(true);
}
}
.setToolTip(Lang.get("menu_expression_tt"))
.createJMenuItem());
}
private void orderMeasurements() {
try {
Model m = new ModelCreator(circuitComponent.getCircuit(), library).createModel(false);
stoppedState.enter();
ArrayList<String> names = new ArrayList<>();
for (Signal s : m.getSignals())
names.add(s.getName());
new OrderMerger<String, String>(circuitComponent.getCircuit().getMeasurementOrdering()).order(names);
ElementOrderer.ListOrder<String> o = new ElementOrderer.ListOrder<>(names);
if (new ElementOrderer<>(Main.this, Lang.get("menu_orderMeasurements"), o)
.addOkButton()
.showDialog()) {
circuitComponent.getCircuit().setMeasurementOrdering(names);
}
} catch (NodeException | PinException | ElementNotFoundException | RuntimeException e) {
showErrorAndStopModel(Lang.get("msg_errorCreatingModel"), e);
}
}
private void setupStates() {
stoppedState = stateManager.register(new State() {
@Override
public void enter() {
super.enter();
clearModelDescription();
circuitComponent.setModeAndReset(false, NoSync.INST);
doStep.setEnabled(false);
stoppedState.getAction().setEnabled(false);
runToBreakAction.setEnabled(false);
}
});
runModelState = stateManager.register(new RunModelState());
runModelMicroState = stateManager.register(new State() {
@Override
public void enter() {
super.enter();
stoppedState.getAction().setEnabled(true);
if (createAndStartModel(false, ModelEvent.MICROSTEP, null))
circuitComponent.setManualChangeObserver(new MicroStepObserver(model));
}
});
}
private void clearModelDescription() {
circuitComponent.removeHighLighted();
if (model != null)
model.close();
modelCreator = null;
model = null;
}
private boolean createAndStartModel(boolean globalRunClock, ModelEvent updateEvent, ModelModifier modelModifier) {
try {
circuitComponent.removeHighLighted();
modelCreator = new ModelCreator(circuitComponent.getCircuit(), library);
if (model != null) {
model.close();
circuitComponent.getCircuit().clearState();
}
model = modelCreator.createModel(true);
model.setWindowPosManager(windowPosManager);
statusLabel.setText(Lang.get("msg_N_nodes", model.size()));
realTimeClockRunning = false;
modelSync = null;
if (globalRunClock)
for (Clock c : model.getClocks())
if (c.getFrequency() > 0) {
if (modelSync == null)
modelSync = new LockSync();
model.addObserver(new RealTimeClock(model, c, timerExecutor, this, modelSync, this));
realTimeClockRunning = true;
}
if (modelSync == null)
modelSync = NoSync.INST;
circuitComponent.setModeAndReset(true, modelSync);
if (realTimeClockRunning) {
// if clock is running, enable automatic update of gui
GuiModelObserver gmo = new GuiModelObserver(circuitComponent, updateEvent);
modelCreator.connectToGui(gmo);
model.addObserver(gmo);
} else
// all repainting is initiated by user actions!
modelCreator.connectToGui(null);
doStep.setEnabled(false);
runToBreakAction.setEnabled(!realTimeClockRunning && model.isFastRunModel());
List<String> ordering = circuitComponent.getCircuit().getMeasurementOrdering();
if (settings.get(Keys.SHOW_DATA_TABLE))
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);
if (settings.get(Keys.SHOW_DATA_GRAPH_MICRO))
windowPosManager.register("dataSetMicro", new DataSetDialog(this, model, true, ordering, modelSync)).setVisible(true);
if (modelModifier != null)
modelModifier.preInit(model);
model.init();
return true;
} catch (NodeException | PinException | RuntimeException | ElementNotFoundException e) {
showErrorAndStopModel(Lang.get("msg_errorCreatingModel"), e);
}
return false;
}
@Override
public void showErrorAndStopModel(String message, Exception cause) {
SwingUtilities.invokeLater(() -> {
if (cause instanceof NodeException) {
NodeException e = (NodeException) cause;
circuitComponent.addHighLightedWires(e.getValues());
if (modelCreator != null)
modelCreator.addNodeElementsTo(e.getNodes(), circuitComponent.getHighLighted());
} else if (cause instanceof PinException) {
PinException e = (PinException) cause;
circuitComponent.addHighLighted(e.getVisualElement());
if (e.getNet() != null)
circuitComponent.addHighLighted(e.getNet().getWires());
} else if (cause instanceof BurnException) {
BurnException e = (BurnException) cause;
circuitComponent.addHighLightedWires(e.getValues());
}
circuitComponent.hasChanged();
new ErrorMessage(message).addCause(cause).show(Main.this);
stoppedState.enter();
});
}
/**
* stops the model
*/
public void stopModel() {
stoppedState.enter();
}
private static JFileChooser getJFileChooser(File filename) {
File folder = null;
if (filename != null)
folder = filename.getParentFile();
JFileChooser fileChooser = new MyFileChooser(folder);
fileChooser.setFileFilter(new FileNameExtensionFilter("Circuit", "dig"));
return fileChooser;
}
@Override
public boolean isStateChanged() {
return circuitComponent.getCircuit().isModified();
}
@Override
public void saveChanges() {
save.actionPerformed(null);
}
@Override
public void open(File file) {
if (ClosingWindowListener.checkForSave(Main.this, Main.this)) {
loadFile(file, true, true);
}
}
private void loadFile(File filename, boolean setLibraryRoot, boolean toPref) {
try {
setFilename(filename, toPref);
if (setLibraryRoot) library.setRootFilePath(filename.getParentFile());
Circuit circuit = Circuit.loadCircuit(filename, shapeFactory);
circuitComponent.setCircuit(circuit);
stoppedState.enter();
windowPosManager.closeAll();
statusLabel.setText(" ");
} catch (Exception e) {
circuitComponent.setCircuit(new Circuit());
setFilename(null, false);
new ErrorMessage(Lang.get("msg_errorReadingFile")).addCause(e).show(this);
}
}
private void saveFile(File filename, boolean toPrefs) {
try {
circuitComponent.getCircuit().save(filename);
stoppedState.enter();
setFilename(filename, toPrefs);
library.invalidateElement(filename);
if (library.getRootFilePath() == null)
library.setRootFilePath(filename.getParentFile());
} catch (IOException e) {
new ErrorMessage(Lang.get("msg_errorWritingFile")).addCause(e).show();
}
}
private void setFilename(File filename, boolean toPrefs) {
this.filename = filename;
if (filename != null) {
this.baseFilename = filename;
if (toPrefs)
fileHistory.add(filename);
setTitle(filename + " - " + Lang.get("digital"));
} else {
setTitle(Lang.get("digital"));
}
}
/**
* @return the window position manager
*/
public WindowPosManager getWindowPosManager() {
return windowPosManager;
}
@Override
public void setStatus(String message) {
SwingUtilities.invokeLater(() -> statusLabel.setText(message));
}
private class FullStepObserver implements Observer {
private final Model model;
FullStepObserver(Model model) {
this.model = model;
}
@Override
public void hasChanged() {
try {
modelSync.accessNEx(() -> {
model.fireManualChangeEvent();
model.doStep();
});
circuitComponent.hasChanged();
} catch (NodeException | RuntimeException e) {
showErrorAndStopModel(Lang.get("msg_errorCalculatingStep"), e);
}
}
}
private class MicroStepObserver implements Observer {
private final Model model;
MicroStepObserver(Model model) {
this.model = model;
}
@Override
public void hasChanged() {
modelCreator.addNodeElementsTo(model.nodesToUpdate(), circuitComponent.getHighLighted());
model.fireManualChangeEvent();
circuitComponent.hasChanged();
doStep.setEnabled(model.needsUpdate());
}
}
private class ExportAction extends ToolTipAction {
private final String name;
private final String suffix;
private final ExportFactory exportFactory;
ExportAction(String name, String suffix, ExportFactory exportFactory) {
super(name);
this.name = name;
this.suffix = suffix;
this.exportFactory = exportFactory;
}
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new MyFileChooser();
if (filename != null)
fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, suffix));
if (lastExportDirectory != null)
fc.setCurrentDirectory(lastExportDirectory);
fc.addChoosableFileFilter(new FileNameExtensionFilter(name, suffix));
new SaveAsHelper(Main.this, fc, suffix).checkOverwrite(
file -> {
lastExportDirectory = file.getParentFile();
try (OutputStream out = new FileOutputStream(file)) {
new Export(circuitComponent.getCircuit(), exportFactory).export(out);
}
}
);
}
}
private class ExportGifAction extends ToolTipAction {
private final String name;
ExportGifAction(String name) {
super(name);
this.name = name;
}
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new MyFileChooser();
if (filename != null)
fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, "gif"));
if (lastExportDirectory != null)
fc.setCurrentDirectory(lastExportDirectory);
fc.addChoosableFileFilter(new FileNameExtensionFilter(name, "gif"));
new SaveAsHelper(Main.this, fc, "gif").checkOverwrite(
file -> {
lastExportDirectory = file.getParentFile();
GifExporter gifExporter = new GifExporter(Main.this, circuitComponent.getCircuit(), 500, file);
setDebug(false);
windowPosManager.closeAll();
runModelState.enter(false, gifExporter);
circuitComponent.hasChanged();
}
);
}
}
private class RunModelState extends State {
@Override
public void enter() {
enter(true, null);
}
void enter(boolean runRealTime, ModelModifier modelModifier) {
super.enter();
stoppedState.getAction().setEnabled(true);
if (createAndStartModel(runRealTime, ModelEvent.STEP, modelModifier))
circuitComponent.setManualChangeObserver(new FullStepObserver(model));
}
}
//***********************
// remote interface start
//***********************
private static class AddressPicker {
private long address;
private void getProgramROMAddress(Model model) {
List<ROM> roms = model.findNode(ROM.class, ROM::isProgramMemory);
if (roms.size() == 1)
address = roms.get(0).getRomAddress();
else
address = -1;
}
String getAddressString() {
if (address < 0)
return null;
else
return Long.toHexString(address);
}
}
private void setDebug(boolean debug) {
settings.set(Keys.SHOW_DATA_TABLE, debug);
}
@Override
public void start(File romHex) throws RemoteException {
SwingUtilities.invokeLater(() -> {
setDebug(false);
windowPosManager.closeAll();
runModelState.enter(true, new RomLoader(romHex));
circuitComponent.hasChanged();
});
}
@Override
public void debug(File romHex) throws RemoteException {
SwingUtilities.invokeLater(() -> {
setDebug(true);
runModelState.enter(false, new RomLoader(romHex));
circuitComponent.hasChanged();
});
}
@Override
public String doSingleStep() throws RemoteException {
if (model != null && !realTimeClockRunning) {
try {
AddressPicker addressPicker = new AddressPicker();
SwingUtilities.invokeAndWait(() -> {
ArrayList<Clock> cl = model.getClocks();
if (cl.size() == 1) {
ObservableValue clkVal = cl.get(0).getClockOutput();
clkVal.setBool(!clkVal.getBool());
try {
model.doStep();
if (clkVal.getBool()) {
clkVal.setBool(!clkVal.getBool());
model.doStep();
}
circuitComponent.hasChanged();
addressPicker.getProgramROMAddress(model);
} catch (NodeException | RuntimeException e) {
showErrorAndStopModel(Lang.get("err_remoteExecution"), e);
}
}
});
return addressPicker.getAddressString();
} catch (InterruptedException | InvocationTargetException e) {
throw new RemoteException("error performing a single step " + e.getMessage());
}
}
return null;
}
@Override
public String runToBreak() throws RemoteException {
try {
AddressPicker addressPicker = new AddressPicker();
SwingUtilities.invokeAndWait(() -> {
if (model != null && model.isFastRunModel() && !realTimeClockRunning)
runToBreakAction.actionPerformed(null);
addressPicker.getProgramROMAddress(model);
});
return addressPicker.getAddressString();
} catch (InterruptedException | InvocationTargetException e) {
throw new RemoteException("error performing a run to break " + e.getMessage());
}
}
@Override
public void stop() {
SwingUtilities.invokeLater(() -> {
stoppedState.enter();
circuitComponent.hasChanged();
});
}
//**********************
// remote interface end
//**********************
/**
* Starts the main app
*
* @param args the arguments
*/
public static void main(String[] args) {
ToolTipManager.sharedInstance().setDismissDelay(10000);
URL.setURLStreamHandlerFactory(ElementHelpDialog.createURLStreamHandlerFactory());
FormatToExpression.setDefaultFormat(Settings.getInstance().get(Keys.SETTINGS_EXPRESSION_FORMAT));
MainBuilder builder = new MainBuilder();
for (String s : args) {
if (s.equals("experimental")) experimental = true;
if (s.toLowerCase().endsWith(".dig")) builder.setFileToOpen(new File(s));
}
SwingUtilities.invokeLater(() -> {
Main main = builder.build();
try {
new RemoteSever(new DigitalHandler(main)).start(41114);
} catch (IOException e) {
SwingUtilities.invokeLater(() -> main.statusLabel.setText(Lang.get("err_portIsInUse")));
}
main.setVisible(true);
});
}
/**
* Builder to create a Main-Window
*/
public static class MainBuilder {
private File fileToOpen;
private Component parent;
private ElementLibrary library;
private Circuit circuit;
private boolean allowAllFileActions = true;
private File baseFileName;
private boolean keepPrefMainFile;
/**
* @param fileToOpen the file to open
* @return this for chained calls
*/
public MainBuilder setFileToOpen(File fileToOpen) {
this.fileToOpen = fileToOpen;
this.baseFileName = fileToOpen;
return this;
}
/**
* @param baseFileName filename used as base for save and load operations
* @return this for chained calls
*/
public MainBuilder setBaseFileName(File baseFileName) {
this.baseFileName = baseFileName;
return this;
}
/**
* @param parent the parent component
* @return this for chained calls
*/
public MainBuilder setParent(Component parent) {
this.parent = parent;
return this;
}
/**
* @param library the library to use
* @return this for chained calls
*/
public MainBuilder setLibrary(ElementLibrary library) {
this.library = library;
return this;
}
/**
* @param circuit the circuit to show
* @return this for chained calls
*/
public MainBuilder setCircuit(Circuit circuit) {
this.circuit = circuit;
return this;
}
/**
* If called, most file actions are denied
*
* @return this for chained calls
*/
public MainBuilder denyMostFileActions() {
this.allowAllFileActions = false;
return this;
}
/**
* Keeps the main file defined in the preferences
*
* @return this for chained calls
*/
public MainBuilder keepPrefMainFile() {
this.keepPrefMainFile = true;
return this;
}
/**
* Creates a new Main instance
*
* @return a new Main instance
*/
private Main build() {
return new Main(this);
}
/**
* Opens the frame using SwingUtilities.invokeLater
*/
public void openLater() {
SwingUtilities.invokeLater(() -> build().setVisible(true));
}
}
}