first working tutorial

This commit is contained in:
hneemann 2019-07-16 23:11:36 +02:00
parent 8b5fe67016
commit 376a7259be
12 changed files with 375 additions and 7 deletions

View File

@ -452,7 +452,7 @@ public final class Keys {
* enables the grid
*/
public static final Key<Boolean> SETTINGS_GRID
= new Key<>("grid", false);
= new Key<>("grid", true);
/**
* enables the wire bits view
@ -786,6 +786,14 @@ public final class Keys {
* Circuit is generic
*/
public static final Key<Boolean> IS_GENERIC =
new Key<Boolean>("isGeneric", false).setSecondary();
new Key<>("isGeneric", false).setSecondary();
/**
* Enables the tutorial
*/
public static final Key<Boolean> SETTINGS_SHOW_TUTORIAL =
new Key<>("showTutorial", true).setSecondary();
}

View File

@ -43,6 +43,7 @@ import de.neemann.digital.gui.components.testing.TestAllDialog;
import de.neemann.digital.gui.components.testing.ValueTableDialog;
import de.neemann.digital.gui.components.tree.LibraryTreeModel;
import de.neemann.digital.gui.components.tree.SelectTree;
import de.neemann.digital.gui.tutorial.InitialTutorial;
import de.neemann.digital.gui.release.CheckForNewRelease;
import de.neemann.digital.gui.remote.DigitalHandler;
import de.neemann.digital.gui.remote.RemoteException;
@ -1837,6 +1838,9 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
}
main.setVisible(true);
if (Settings.getInstance().getAttributes().get(Keys.SETTINGS_SHOW_TUTORIAL))
new InitialTutorial(main).setVisible(true);
CheckForNewRelease.showReleaseDialog(main);
});
}

View File

@ -61,6 +61,7 @@ public final class Settings implements AttributeListener {
intList.add(Keys.SETTINGS_TOOLCHAIN_CONFIG);
intList.add(Keys.SETTINGS_FONT_SCALING);
intList.add(Keys.SETTINGS_MAC_MOUSE);
intList.add(Keys.SETTINGS_SHOW_TUTORIAL);
settingsKeys = Collections.unmodifiableList(intList);

View File

@ -133,6 +133,7 @@ public class CircuitComponent extends JComponent implements ChangedListener, Lib
private Mouse mouse = Mouse.getMouse();
private Circuit shallowCopy;
private CircuitScrollPanel circuitScrollPanel;
private ModificationListener modificationListener;
/**
@ -429,6 +430,8 @@ public class CircuitComponent extends JComponent implements ChangedListener, Lib
try {
if (modification != null) {
undoManager.apply(modification);
if (modificationListener != null)
modificationListener.modified(modification);
if (circuitScrollPanel != null)
circuitScrollPanel.sizeChanged();
}
@ -620,6 +623,16 @@ public class CircuitComponent extends JComponent implements ChangedListener, Lib
getCircuit().clearState();
}
requestFocusInWindow();
if (modificationListener != null)
modificationListener.modified(null);
}
/**
* @return true if circuit is running
*/
public boolean isRunning() {
return activeMouseController == mouseRun;
}
/**
@ -2314,6 +2327,15 @@ public class CircuitComponent extends JComponent implements ChangedListener, Lib
new MouseControllerWizard(wizardNotification).activate();
}
/**
* Sets the modification listener.
*
* @param modificationListener is called every time the circuit is modified
*/
public void setModificationListener(ModificationListener modificationListener) {
this.modificationListener = modificationListener;
}
/**
* Deactivate a wizard
*/
@ -2366,4 +2388,15 @@ public class CircuitComponent extends JComponent implements ChangedListener, Lib
void closed();
}
/**
* Listener to get notified if the circuit has changed
*/
public interface ModificationListener {
/**
* Called if the circuit was modified
*
* @param modification the modification
*/
void modified(Modification<Circuit> modification);
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright (c) 2019 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.gui.tutorial;
import de.neemann.digital.core.NodeException;
import de.neemann.digital.core.basic.XOr;
import de.neemann.digital.core.element.ElementTypeDescription;
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.draw.elements.Circuit;
import de.neemann.digital.draw.elements.PinException;
import de.neemann.digital.draw.elements.VisualElement;
import de.neemann.digital.draw.library.ElementNotFoundException;
import de.neemann.digital.draw.model.ModelCreator;
import de.neemann.digital.gui.Main;
import de.neemann.digital.gui.Settings;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.modification.ModifyInsertWire;
import de.neemann.digital.lang.Lang;
import de.neemann.digital.undo.Modification;
import de.neemann.digital.undo.Modifications;
import de.neemann.gui.LineBreaker;
import de.neemann.gui.Screen;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
/**
* The tutorial dialog.
*/
public class InitialTutorial extends JDialog implements CircuitComponent.ModificationListener {
private static final ArrayList<Step> STEPS = new ArrayList<>();
static {
STEPS.add(new Step("tutorial1", (cc, mod, t) -> contains(cc, In.DESCRIPTION)));
STEPS.add(new Step("tutorial2", (cc, mod, t) -> contains(cc, In.DESCRIPTION, In.DESCRIPTION)));
STEPS.add(new Step("tutorial3", (cc, mod, t) -> contains(cc, In.DESCRIPTION, In.DESCRIPTION, XOr.DESCRIPTION)));
STEPS.add(new Step("tutorial4", (cc, mod, t) -> contains(cc, In.DESCRIPTION, In.DESCRIPTION, XOr.DESCRIPTION, Out.DESCRIPTION)));
STEPS.add(new Step("tutorial5", (cc, mod, t) -> contains(mod, ModifyInsertWire.class)));
STEPS.add(new Step("tutorial6", (cc, mod, t) -> isWorking(cc)));
STEPS.add(new Step("tutorial7", (cc, mod, t) -> cc.isRunning()));
STEPS.add(new Step("tutorial8", (cc, mod, t) -> !cc.isRunning()));
STEPS.add(new Step("tutorial9", (cc, mod, t) -> isIONamed(cc, 1, t)));
STEPS.add(new Step("tutorial10", (cc, mod, t) -> isIONamed(cc, 3, t)));
}
private static boolean isIONamed(CircuitComponent cc, int expected, InitialTutorial t) {
HashSet<String> set = new HashSet<>();
int num = 0;
for (VisualElement ve : cc.getCircuit().getElements()) {
if (ve.equalsDescription(In.DESCRIPTION) || ve.equalsDescription(Out.DESCRIPTION)) {
String l = ve.getElementAttributes().getLabel();
if (!l.isEmpty()) {
if (set.contains(l)) {
t.setTextByID("tutorialUniqueIdents");
return false;
}
set.add(l);
num++;
}
}
}
return num == expected;
}
private static boolean isWorking(CircuitComponent cc) {
try {
new ModelCreator(cc.getCircuit(), cc.getLibrary()).createModel(false);
return true;
} catch (PinException | NodeException | ElementNotFoundException e) {
return false;
}
}
private static boolean contains(Modification<Circuit> mod, Class<? extends Modification> modifyClass) {
if (mod == null)
return false;
if (mod.getClass() == modifyClass)
return true;
if (mod instanceof Modifications) {
Modifications m = (Modifications) mod;
for (Object i : m.getModifications())
if (i.getClass() == modifyClass)
return true;
}
return false;
}
private static boolean contains(CircuitComponent cc, ElementTypeDescription... descriptions) {
ArrayList<VisualElement> el = new ArrayList<>(cc.getCircuit().getElements());
if (el.size() != descriptions.length)
return false;
for (ElementTypeDescription d : descriptions) {
Iterator<VisualElement> it = el.iterator();
while (it.hasNext()) {
if (it.next().equalsDescription(d)) {
it.remove();
break;
}
}
}
return el.isEmpty();
}
private final JTextPane text;
private final CircuitComponent circuitComponent;
private int stepIndex;
/**
* Creates the tutorial dialog.
* @param main the main class
*/
public InitialTutorial(Main main) {
super(main, Lang.get("tutorial"), false);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setAlwaysOnTop(true);
circuitComponent = main.getCircuitComponent();
circuitComponent.setModificationListener(this);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent windowEvent) {
circuitComponent.setModificationListener(null);
}
});
text = new JTextPane();
text.setEditable(false);
text.setFont(Screen.getInstance().getFont(1.2f));
text.setPreferredSize(new Dimension(300, 400));
getContentPane().add(new JScrollPane(text));
pack();
final Point ml = main.getLocation();
setLocation(ml.x - getWidth(), ml.y);
stepIndex = -1;
incIndex();
}
private void incIndex() {
stepIndex++;
if (stepIndex == STEPS.size()) {
Settings.getInstance().getAttributes().set(Keys.SETTINGS_SHOW_TUTORIAL, false);
dispose();
} else {
setTextByID(STEPS.get(stepIndex).getId());
}
}
private void setTextByID(String id) {
final String s = Lang.get(id);
text.setText(new LineBreaker(1000).breakLines(s));
}
@Override
public void modified(Modification<Circuit> modification) {
if (STEPS.get(stepIndex).getChecker().accomplished(circuitComponent, modification, this))
incIndex();
}
private static final class Step {
private final String id;
private final Checker checker;
private Step(String id, Checker checker) {
this.id = id;
this.checker = checker;
}
public String getId() {
return id;
}
public Checker getChecker() {
return checker;
}
}
private interface Checker {
boolean accomplished(CircuitComponent circuitComponent, Modification<Circuit> modification, InitialTutorial t);
}
}

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) 2019 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
/**
* Classes used to show the initial tutorial
*/
package de.neemann.digital.gui.tutorial;

View File

@ -27,6 +27,13 @@ public final class Modifications<A extends Copyable<A>> implements Modification<
m.modify(a);
}
/**
* @return The contained modifications
*/
public ArrayList<Modification<A>> getModifications() {
return modifications;
}
@Override
public String toString() {
return name;

View File

@ -82,21 +82,27 @@ public class LineBreaker {
StringBuilder word = new StringBuilder();
pos = indent;
boolean lastLineBreak=false;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
switch (c) {
case '\n':
if (preserveLineBreaks) {
if (preserveLineBreaks || lastLineBreak) {
addWord(word);
lineBreak();
break;
} else {
addWord(word);
lastLineBreak = true;
}
break;
case '\r':
case ' ':
addWord(word);
lastLineBreak = false;
break;
default:
word.append(c);
lastLineBreak = false;
}
}
addWord(word);

View File

@ -1348,6 +1348,9 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
<string name="key_isGeneric">Schaltung ist generisch</string>
<string name="key_isGeneric_tt">Erlaubt die Erzeugung von generischen Schaltungen.</string>
<string name="key_showTutorial">Tutorial beim Start anzeigen</string>
<string name="key_showTutorial_tt">Aktiviert das Tutorial.</string>
<string name="mod_insertWire">Leitung eingefügt.</string>
<string name="mod_insertCopied">Aus Zwischenablage eingefügt.</string>
<string name="mod_setKey_N0_in_element_N1">Wert ''{0}'' in Element ''{1}'' verändert.</string>
@ -1949,4 +1952,57 @@ Daher steht auch das Signal 'D_out' zur Verfügung, um in diesem Fall den Wert z
Gibt es aus einem Zustand keinen unbedingten Übergang, bleibt der Automat in diesem Zustand, wenn keine
andere Übergangsbedingung erfüllt ist.
</body></html>]]></string>
<string name="tutorial">Tutorial</string>
<string name="tutorial1">Im Folgenden werden Sie mit einem kurzen Tutorial zur ersten
Schaltung geführt:
Fügen Sie einen Eingang in die Schaltung ein.
Sie finden den Eingang im Menu Bauteile▸IO.</string>
<string name="tutorial2">Fügen Sie nun einen zweiten Eingang in die Schaltung ein.
Sie können auch auf den Eingang in der Toolbar klicken.
Setzen Sie den zweiten Eingang am besten mit zwei Gitterabständen unter den ersten Eingang.
Sie können die Schaltung verschieben, wenn Sie die rechte Maustaste gedrückt halten.
Durch klicken auf die Eingänge können Sie diese verschieben.
</string>
<string name="tutorial3">Als nächstes soll ein "Exklusiv Oder" Gatter eingefügt werden.
Sie finden dieses Gatter im Menu Bauteile▸Logisch.
Setzen Sie dieses Bauteil mit zwei Gitterabständen rechts neben die Eingänge.
</string>
<string name="tutorial4">Als letztes Bauteil soll noch ein Ausgang eingefügt werden.
Setzen Sie diesen mit ebenfalls zwei Gitterabständen rechts neben das "Exklusiv Oder" Gatter.
</string>
<string name="tutorial5">Um die Schaltung zu vervollständigen, sind Verbindungsleitungen zu ziehen.
Klicken Sie auf den roten Punkt am ersten Eingang und verbinden Sie diesen mit einem Eingang
des "Exklusiv Oder" Gatters, indem Sie danach auf einen blauen Punkt des "Exklusiv Oder"
Gatters klicken.
</string>
<string name="tutorial6">Verbinden Sie die roten Punkte der Eingänge mit den blauen Punkten des
"Exklusiv Oder" Gatters und den roten Punkt des "Exklusiv Oder" Gatters mit dem blauen Punkte
des Ausgangs.
Durch Klicken können Sie die Leitung anheften. Rechts-Klick bricht das Zeichnen der Leitung ab.
</string>
<string name="tutorial7">Damit ist Ihre erste Schaltung funktionsfähig.
Um die Simulation zu starten, können Sie auf den Play-Knopf in der Toolbar klicken.
</string>
<string name="tutorial8">
Die Simulation ist jetzt aktiv.
Jetzt können Sie die Eingänge umschalten indem Sie darauf klicken.
Um die Simulation zu beenden, klicken Sie auf den Stop-Knopf in der Toolbar.
</string>
<string name="tutorial9">
Der Vollständigkeit halber sollen die Ein- und Ausgänge benannt werden.
Durch Rechts-Klick auf einen Ausgang öffnet sich ein Dialog.
Hier kann der Ausgang mit einer Bezeichnung versehen werden.
</string>
<string name="tutorial10">
Benennen Sie alle Ein- und Ausgänge.
</string>
<string name="tutorialUniqueIdents">
Die Ein- und Ausgänge sollten eindeutig benannt sein.
</string>
</resources>

View File

@ -1334,6 +1334,9 @@
<string name="key_isGeneric">Circuit is generic</string>
<string name="key_isGeneric_tt">Allows to create a generic circuit.</string>
<string name="key_showTutorial">Show Tutorial at Startup</string>
<string name="key_showTutorial_tt">Enables the tutorial.</string>
<string name="mod_insertWire">Inserted wire.</string>
<string name="mod_insertCopied">Insert from clipboard.</string>
<string name="mod_setKey_N0_in_element_N1">Value ''{0}'' in component ''{1}'' modified.</string>
@ -1918,4 +1921,48 @@ Therefore, the signal 'D_out' is also available to check the value in this case.
transition condition is met.
</body></html>]]></string>
<string name="tutorial">Tutorial</string>
<string name="tutorial1">In the following you will be guided to the first circuit with a short tutorial:
Add an input into the circuit. You will find the input in the menu Components▸IO.</string>
<string name="tutorial2">Now add a second input to the circuit. You can also click on the input
in the toolbar.
It is best to place the second input with two grid spacings under the first input.
You can move the circuit by holding down the right mouse button.
By clicking on the inputs you can move them.</string>
<string name="tutorial3">Next, an "Exclusive Or" gate is to be inserted.
You can find this gate in the menu Components▸Logic.
Place this component with two grid spacings to the right of the inputs.</string>
<string name="tutorial4">The last component to be inserted is an output.
Set it with two grid spacings to the right of the "Exclusive Or" gate.
</string>
<string name="tutorial5">In order to complete the circuit, connecting wires must be drawn.
Click on the red dot at the first input and connect it to an input of the "Exclusive Or" gate,
by clicking on a blue dot of the "Exclusive Or" gate.
</string>
<string name="tutorial6">Connect the red dots of the inputs to the blue dots of the
"Exclusive Or" gate and the red dot of the "Exclusive Or" gate to the blue dot of the output.
You can pin the wire by clicking. Right-click cancels the drawing of the wire.
</string>
<string name="tutorial7">Your first circuit is now functional.
To start the simulation, you can click on the Play button in the toolbar.
</string>
<string name="tutorial8">
The simulation is now active. Now you can switch the inputs by clicking on them.
To stop the simulation, click on the Stop button in the toolbar.
</string>
<string name="tutorial9">
For completeness, the inputs and outputs should be labeled.
Right-click on an output to open a dialog. Here the output can be given a name.</string>
<string name="tutorial10">
Label all inputs and outputs.
</string>
<string name="tutorialUniqueIdents">
The inputs and outputs should be uniquely named.
</string>
</resources>

View File

@ -68,7 +68,7 @@ public class TestLang extends TestCase {
StringBuilder sb = new StringBuilder();
for (String key : map.keySet()) {
if (!keys.contains(key)) {
if (!(key.startsWith("key_") || key.startsWith("elem_"))) {
if (!(key.startsWith("key_") || key.startsWith("elem_") || key.startsWith("tutorial"))) {
if (sb.length() > 0)
sb.append(", ");
sb.append('"').append(key).append('"');

View File

@ -12,8 +12,8 @@ import junit.framework.TestCase;
public class LineBreakerTest extends TestCase {
public void testBreakLines() throws Exception {
assertEquals("this is a test string", new LineBreaker(60).breakLines("this \n\n is \n a test \n\r string"));
assertEquals("this is a test\nstring", new LineBreaker(14).breakLines("this \n\n is \n a test \n\r string"));
assertEquals("this\nis a test string", new LineBreaker(60).breakLines("this \n\n is \n a test \n\r string"));
assertEquals("this\nis a test\nstring", new LineBreaker(14).breakLines("this \n\n is \n a test \n\r string"));
assertEquals("This is a test string. This\n" +
"is a test string. This is a\n" +
"test string.", new LineBreaker(27).breakLines("This is a test string. This is a test string. This is a test string."));