diff --git a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java index 62daa7fc4..0fd687612 100644 --- a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java +++ b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java @@ -189,7 +189,7 @@ public class ElementLibrary implements Iterable * @param rootLibraryPath the path * @throws IOException IOException */ - public void setFilePath(File rootLibraryPath) throws IOException { + public void setRootFilePath(File rootLibraryPath) throws IOException { if (rootLibraryPath == null) { if (this.rootLibraryPath != null) { this.rootLibraryPath = null; @@ -201,6 +201,31 @@ public class ElementLibrary implements Iterable } } + /** + * @return the actual root file path + */ + public File getRootFilePath() { + return rootLibraryPath; + } + + /** + * Checks if the given file is accessible from the actual library. + * + * @param file the file to check + * @return true if given file is importable + */ + public boolean isFileAccessible(File file) { + if (rootLibraryPath == null) return true; + + try { + String root = rootLibraryPath.getCanonicalPath(); + String path = file.getParentFile().getCanonicalPath(); + return path.startsWith(root); + } catch (IOException e) { + return false; + } + } + /** * Returns the node or null if node not present. * @@ -358,14 +383,18 @@ public class ElementLibrary implements Iterable } /** - * Removes an element from the library + * Removes an element from the library to enforce a reload * * @param name the elements name */ - public void invalidateElement(File name) { + public void invalidateElement(File name) throws IOException { LibraryNode n = map.get(name.getName()); if (n != null) n.invalidate(); + else { + if (rootLibraryPath != null && isFileAccessible(name)) + rescanFolder(); + } } /** diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index 76600b507..abbbedd5c 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -90,6 +90,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E 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"); @@ -109,7 +110,6 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E private final ScheduledThreadPoolExecutor timerExecuter = new ScheduledThreadPoolExecutor(1); private final WindowPosManager windowPosManager = new WindowPosManager(); private final InsertHistory insertHistory; - private final boolean isNestedFile; private ToolTipAction doStep; private ToolTipAction runToBreakAction; @@ -153,41 +153,41 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E * @param parent the parent component * @param circuit circuit to show */ - public Main(Component parent, Circuit circuit) { - this(parent, null, null, circuit); + public Main(Component parent, ElementLibrary parentsLibrary, Circuit circuit) { + this(parent, null, parentsLibrary, circuit); } /** * Creates a new instance * - * @param parent the parent component - * @param fileToOpen a file to open - * @param parentsLibrary the parents library. If not null means opening a nested circuit - * @param circuit circuit to show + * @param parent the parent component + * @param nestedFileToOpen a file to open + * @param parentsLibrary the parents library. If not null means opening a nested circuit + * @param circuit circuit to show */ - private Main(Component parent, File fileToOpen, ElementLibrary parentsLibrary, Circuit circuit) { + private Main(Component parent, File nestedFileToOpen, ElementLibrary parentsLibrary, Circuit circuit) { super(Lang.get("digital")); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); setIconImages(IconCreator.createImages("icon32.png", "icon64.png", "icon128.png")); - isNestedFile = parentsLibrary != null; - if (isNestedFile) this.library = parentsLibrary; + if (parentsLibrary != null) library = parentsLibrary; else library = new ElementLibrary(); + shapeFactory = new ShapeFactory(library, Settings.getInstance().get(Keys.SETTINGS_IEEE_SHAPES)); fileHistory = new FileHistory(this); + circuitComponent = new CircuitComponent(this, library, shapeFactory); if (circuit != null) { - circuitComponent = new CircuitComponent(this, library, shapeFactory); SwingUtilities.invokeLater(() -> circuitComponent.setCircuit(circuit)); + setFilename(nestedFileToOpen, false); } else { - circuitComponent = new CircuitComponent(this, library, shapeFactory); - if (fileToOpen != null) { - SwingUtilities.invokeLater(() -> loadFile(fileToOpen, false)); + if (nestedFileToOpen != null) { + SwingUtilities.invokeLater(() -> loadFile(nestedFileToOpen, false)); } else { File name = fileHistory.getMostRecent(); if (name != null) { - SwingUtilities.invokeLater(() -> loadFile(name, false)); + SwingUtilities.invokeLater(() -> loadFile(name, true)); } } } @@ -205,7 +205,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E JMenuBar menuBar = new JMenuBar(); JToolBar toolBar = new JToolBar(); - save = createFileMenu(menuBar, toolBar); + save = createFileMenu(menuBar, toolBar, nestedFileToOpen == null); toolBar.addSeparator(); createViewMenu(menuBar, toolBar); @@ -350,7 +350,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E * @param toolBar the toolBar * @return the save action */ - private ToolTipAction createFileMenu(JMenuBar menuBar, JToolBar toolBar) { + 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) { @@ -358,9 +358,21 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E setFilename(null, true); circuitComponent.setCircuit(new Circuit()); windowPosManager.closeAll(); + try { + library.setRootFilePath(null); + } catch (IOException e1) { + // can not happen, no folder is scanned + } } } - }.setActive(!isNestedFile); + }.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 Main(Main.this, library, new Circuit()).setVisible(true); + } + }.setToolTip(Lang.get("menu_newSub_tt")); ToolTipAction open = new ToolTipAction(Lang.get("menu_open"), ICON_OPEN) { @Override @@ -372,7 +384,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E } } } - }.setActive(!isNestedFile); + }.setActive(allowAll); ToolTipAction openWin = new ToolTipAction(Lang.get("menu_openWin"), ICON_OPEN_WIN) { @Override @@ -384,21 +396,46 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E m.setVisible(true); } } - }.setToolTip(Lang.get("menu_openWin_tt")).setActive(!isNestedFile); + }.setToolTip(Lang.get("menu_openWin_tt")).setActive(allowAll); JMenu openRecent = new JMenu(Lang.get("menu_openRecent")); fileHistory.setMenu(openRecent); - openRecent.setEnabled(!isNestedFile); + openRecent.setEnabled(allowAll); ToolTipAction saveas = new ToolTipAction(Lang.get("menu_saveAs"), ICON_SAVE_AS) { @Override public void actionPerformed(ActionEvent e) { JFileChooser fc = getJFileChooser(lastFilename); - if (fc.showSaveDialog(Main.this) == JFileChooser.APPROVE_OPTION) { - saveFile(fc.getSelectedFile()); - } + if (lastFilename == null && library.getRootFilePath() != null) + fc.setCurrentDirectory(library.getRootFilePath()); + + boolean repeat; + do { + repeat = false; + if (fc.showSaveDialog(Main.this) == JFileChooser.APPROVE_OPTION) { + final File selectedFile = fc.getSelectedFile(); + if (library.isFileAccessible(selectedFile)) + saveFile(selectedFile, false); + 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(selectedFile, true); + break; + case 1: + repeat = true; + break; + } + } + } + } while (repeat); } - }.setActive(!isNestedFile); + }.setActive(allowAll); ToolTipAction save = new ToolTipAction(Lang.get("menu_save"), ICON_SAVE) { @Override @@ -406,7 +443,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E if (filename == null) saveas.actionPerformed(e); else - saveFile(filename); + saveFile(filename, false); } }; @@ -418,12 +455,13 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E JMenu file = new JMenu(Lang.get("menu_file")); menuBar.add(file); - file.add(newFile); + file.add(newFile.createJMenuItem()); + file.add(newSubFile.createJMenuItem()); file.add(openRecent); - file.add(open); - file.add(openWin); - file.add(save); - file.add(saveas); + file.add(open.createJMenuItem()); + file.add(openWin.createJMenuItem()); + file.add(save.createJMenuItem()); + file.add(saveas.createJMenuItem()); file.add(export); toolBar.add(newFile.createJButtonNoText()); @@ -525,7 +563,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E circuit.add((VisualElement) m); } - Main m = new Main(Main.this, circuit); + Main m = new Main(Main.this, library, circuit); m.lastFilename = filename; m.setLocationRelativeTo(Main.this); m.setVisible(true); @@ -694,7 +732,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E public void actionPerformed(ActionEvent e) { try { Model model = new ModelCreator(circuitComponent.getCircuit(), library).createModel(false); - new TableDialog(Main.this, new ModelAnalyser(model).analyse(), shapeFactory, filename) + new TableDialog(Main.this, new ModelAnalyser(model).analyse(), library, shapeFactory, filename) .setVisible(true); stoppedState.enter(); } catch (PinException | NodeException | AnalyseException | ElementNotFoundException | RuntimeException e1) { @@ -709,7 +747,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E @Override public void actionPerformed(ActionEvent e) { TruthTable tt = new TruthTable(3).addResult(); - new TableDialog(Main.this, tt, shapeFactory, null).setVisible(true); + new TableDialog(Main.this, tt, library, shapeFactory, null).setVisible(true); stoppedState.enter(); } } @@ -719,7 +757,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E analyse.add(new ToolTipAction(Lang.get("menu_expression")) { @Override public void actionPerformed(ActionEvent e) { - new ExpressionDialog(Main.this, shapeFactory).setVisible(true); + new ExpressionDialog(Main.this, library, shapeFactory).setVisible(true); } } .setToolTip(Lang.get("menu_expression_tt")) @@ -899,9 +937,10 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E } } - private void loadFile(File filename, boolean toPrefs) { + private void loadFile(File filename, boolean setLibraryRoot) { try { - setFilename(filename, toPrefs); + setFilename(filename, setLibraryRoot); + if (setLibraryRoot) library.setRootFilePath(filename.getParentFile()); Circuit circ = Circuit.loadCircuit(filename, shapeFactory); circuitComponent.setCircuit(circ); stoppedState.enter(); @@ -914,12 +953,12 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E } } - private void saveFile(File filename) { + private void saveFile(File filename, boolean toPrefs) { filename = checkSuffix(filename, "dig"); try { circuitComponent.getCircuit().save(filename); stoppedState.enter(); - setFilename(filename, !isNestedFile); + setFilename(filename, toPrefs); library.invalidateElement(filename); } catch (IOException e) { new ErrorMessage(Lang.get("msg_errorWritingFile")).addCause(e).show(); @@ -927,20 +966,14 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E } private void setFilename(File filename, boolean toPrefs) { - try { - this.filename = filename; - if (filename != null) { - this.lastFilename = filename; - if (!isNestedFile) library.setFilePath(filename.getParentFile()); - if (toPrefs) - fileHistory.add(filename); - setTitle(filename + " - " + Lang.get("digital")); - } else { - if (!isNestedFile) library.setFilePath(null); - setTitle(Lang.get("digital")); - } - } catch (IOException e) { - new ErrorMessage(Lang.get("msg_errorReadingFile")).addCause(e).show(this); + this.filename = filename; + if (filename != null) { + this.lastFilename = filename; + if (toPrefs) + fileHistory.add(filename); + setTitle(filename + " - " + Lang.get("digital")); + } else { + setTitle(Lang.get("digital")); } } diff --git a/src/main/java/de/neemann/digital/gui/components/expression/ExpressionDialog.java b/src/main/java/de/neemann/digital/gui/components/expression/ExpressionDialog.java index 17112f4b8..cb5ce33d6 100644 --- a/src/main/java/de/neemann/digital/gui/components/expression/ExpressionDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/expression/ExpressionDialog.java @@ -5,6 +5,7 @@ import de.neemann.digital.analyse.expression.format.FormatToExpression; import de.neemann.digital.analyse.parser.Parser; import de.neemann.digital.builder.circuit.CircuitBuilder; import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.library.ElementLibrary; import de.neemann.digital.draw.shapes.ShapeFactory; import de.neemann.digital.gui.Main; import de.neemann.digital.gui.components.table.ShowStringDialog; @@ -31,7 +32,7 @@ public class ExpressionDialog extends JDialog { * @param parent the parent * @param shapeFactory the shapeFactory used for new circuits */ - public ExpressionDialog(JFrame parent, ShapeFactory shapeFactory) { + public ExpressionDialog(Main parent, ElementLibrary library, ShapeFactory shapeFactory) { super(parent, Lang.get("expression"), false); JTextField text = new JTextField("(C ∨ B) ∧ (A ∨ C) ∧ (B ∨ !C) * (C + !A)", 40); @@ -64,7 +65,7 @@ public class ExpressionDialog extends JDialog { for (Expression exp : expList) circuitBuilder.addCombinatorial(FormatToExpression.defaultFormat(exp), exp); Circuit circuit = circuitBuilder.createCircuit(); - SwingUtilities.invokeLater(() -> new Main(null, circuit).setVisible(true)); + SwingUtilities.invokeLater(() -> new Main(parent, library, circuit).setVisible(true)); } catch (Exception ex) { new ErrorMessage().addCause(ex).show(ExpressionDialog.this); } diff --git a/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java b/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java index 2834cda33..1bcadb9e7 100644 --- a/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java @@ -32,6 +32,7 @@ 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.draw.elements.Circuit; +import de.neemann.digital.draw.library.ElementLibrary; import de.neemann.digital.draw.shapes.ShapeFactory; import de.neemann.digital.gui.Main; import de.neemann.digital.gui.components.AttributeDialog; @@ -74,6 +75,7 @@ public class TableDialog extends JDialog { private final JLabel label; private final JTable table; private final Font font; + private final ElementLibrary library; private final ShapeFactory shapeFactory; private JCheckBoxMenuItem createJK; private File filename; @@ -89,8 +91,9 @@ public class TableDialog extends JDialog { * @param parent the parent frame * @param truthTable the table to show */ - public TableDialog(JFrame parent, TruthTable truthTable, ShapeFactory shapeFactory, File filename) { + public TableDialog(JFrame parent, TruthTable truthTable, ElementLibrary library, ShapeFactory shapeFactory, File filename) { super(parent, Lang.get("win_table")); + this.library = library; this.shapeFactory = shapeFactory; this.filename = filename; setDefaultCloseOperation(DISPOSE_ON_CLOSE); @@ -519,7 +522,7 @@ public class TableDialog extends JDialog { CircuitBuilder circuitBuilder = new CircuitBuilder(shapeFactory, useJKff); new BuilderExpressionCreator(circuitBuilder, modifier).setUseJKOptimizer(useJKff).create(lastGeneratedExpressions); Circuit circuit = circuitBuilder.createCircuit(); - SwingUtilities.invokeLater(() -> new Main(null, circuit).setVisible(true)); + SwingUtilities.invokeLater(() -> new Main(null, library, circuit).setVisible(true)); } catch (ExpressionException | FormatterException | RuntimeException e) { new ErrorMessage(Lang.get("msg_errorDuringCalculation")).addCause(e).show(); } diff --git a/src/main/resources/document-new-sub.png b/src/main/resources/document-new-sub.png new file mode 100644 index 000000000..edb606816 Binary files /dev/null and b/src/main/resources/document-new-sub.png differ diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 2fc18526d..73ce86986 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -36,6 +36,9 @@ Setzt alle Werte auf 0 zurück! Übergänge Alle möglichen Übergänge werden als Testfälle ergänzt. Dient zur Erzeugung von Testfällen für den Simulator selbst. + Neuer Name + Trotzdem speichern + Warnung Abbrechen Digital Ausdruck @@ -592,6 +595,9 @@ Es sind nur {1} Variablen erlaubt, es wurden jedoch {2} gefunden. Gatterschrittmodus Startet die Schaltung im Gatterschrittmodus Neu + Aktuelle Schaltung löschen. + Neue Teilschaltung + Öffnet ein neues Fenster um darin eine neue eingebettete Teilschaltung zu erstellen. Öffnen Zuletzt verwendet Öffnen in neuem Fenster @@ -730,6 +736,7 @@ Die Icons stammen aus dem Tango Desktop Project. Meldung vom externen Fitter Starten des externen Fitters Aktuelle Schaltung + Dieser Dateiename ist nicht aus dem aktuellen Projekt importierbar! Ok diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 77402834e..21754a0a0 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -36,6 +36,9 @@ All values are set to zero! Transitions All possible transitions are added as test cases. Is used to create test cases to test the simulator itself. + New Name + Save anyway + Warning Cancel Digital Expression @@ -579,6 +582,9 @@ allowed are {1} variables but {2} are found. Single gate stepping Runs the circuit in single gate step mode New + Clear actual circuit. + New nested Circuit + Opens a new window to create a new nested circuit. Open Open Recent Open in New Window @@ -717,6 +723,7 @@ The icons are taken from the Tango Desktop Project. Message from the external fitter Execution of external fitter Actual Circuit + The selected file name is not importable from the actual project! Ok diff --git a/src/test/java/de/neemann/digital/integration/TestNesting.java b/src/test/java/de/neemann/digital/integration/TestNesting.java index 8e5a0f69c..d74af0422 100644 --- a/src/test/java/de/neemann/digital/integration/TestNesting.java +++ b/src/test/java/de/neemann/digital/integration/TestNesting.java @@ -64,7 +64,7 @@ public class TestNesting extends TestCase { private TestExecuter createTestExecuterForNesting(String file) throws IOException, NodeException, PinException, ElementNotFoundException { ElementLibrary library = new ElementLibrary(); - library.setFilePath(new File(Resources.getRoot(), "dig")); + library.setRootFilePath(new File(Resources.getRoot(), "dig")); return TestExecuter.createFromFile(file, library); } diff --git a/src/test/java/de/neemann/digital/integration/ToBreakRunner.java b/src/test/java/de/neemann/digital/integration/ToBreakRunner.java index 425a87822..e6ce4aca6 100644 --- a/src/test/java/de/neemann/digital/integration/ToBreakRunner.java +++ b/src/test/java/de/neemann/digital/integration/ToBreakRunner.java @@ -70,7 +70,7 @@ public class ToBreakRunner { private ToBreakRunner(File filename, boolean doInit) throws IOException, PinException, NodeException, ElementNotFoundException { library = new ElementLibrary(); - library.setFilePath(filename.getParentFile()); + library.setRootFilePath(filename.getParentFile()); ShapeFactory shapeFactory = new ShapeFactory(library); circuit = Circuit.loadCircuit(filename, shapeFactory);