Commit f1112308 authored by Stefan Schuhbaeck's avatar Stefan Schuhbaeck
Browse files

add TopographyChecker logic and gui components.

parent 9f2c7595
Pipeline #68847 passed with stages
in 66 minutes and 22 seconds
......@@ -293,9 +293,19 @@ TopographyCreator.btnConvexHull.label=Convex Hull
TopographyCreator.btnSimplePolygon.label=Simple Polygon
TopographyCreator.btnCircle.label=Circle
TopographyCreator.btnRectangle.label=Rectangle
TopographyCreator.btnChecker.tooltip=Show Topography Checker Messages
select_shape_tooltip=Select Shape
# TopographyChecker
TopographyChecker.type.error=Error
TopographyChecker.type.warning=Warning
TopographyChecker.source.targetIdNotFound=The following target ids where not found in the scenario.
TopographyChecker.source.noTargetIdSet=No Target Ids set for Source.
TopographyChecker.source.noTargetIdAndNoSpawn=No Target Ids set for Source with SpawnNumber 0. This might be an error.
TopographyChecker.source.idNotUnique=Multiple Sources have the same ID.
# tab titles Topography creator / postvis
Tab.Simulation.title=Simulation
Tab.Model.title=Model
......@@ -330,8 +340,12 @@ ActionSeeDiscardChanges.popup.title=Unsaved changes
SaveDespiteJsonErrors.title=JSON-Errors.
SaveDespiteJsonErrors.text=There are errors in the JSON of the currently open scenario. Do you want to correct those?
SaveDespiteTopographyCheckerErrors.title=Topography-Errors.
SaveDespiteTopographyCheckerErrors.text=There are errors in the Topography of the currently open scenario. Do you want to correct those?
RunScenarioJsonErrors.title=Resolve JSON errors
RunScenarioJsonErrors.text=The simulation can't be started with erroneous JSON.
RunScenarioTopographyCheckerErrors.title=Resolve TopographyChecker errors
RunScenarioTopographyCheckerErrors.text=The simulation can't be started with erroneous Topography setup.
RunScenarioNotReadyToRun.text=One or more of the selected scenarios isn't ready to run.
ActionEditScenarioDescription.menu.title=Edit description
......@@ -294,6 +294,15 @@ TopographyCreator.btnConvexHull.label=Convexe H\u00fclle
TopographyCreator.btnSimplePolygon.label=Einfaches Polygon
TopographyCreator.btnCircle.label=Kreis
TopographyCreator.btnRectangle.label=Rechteck
TopographyCreator.btnChecker.tooltip=Topography Linter Nachrichten
# TopographyChecker
TopographyChecker.type.error=Fehler
TopographyChecker.type.warning=Warnung
TopographyChecker.source.targetIdNotFound=Die folgenden Ziel-IDs wurden nicht im Szenario gefunden.
TopographyChecker.source.noTargetIdSet=In der Quelle wurden keine Ziel Ids vergeben.
TopographyChecker.source.noTargetIdAndNoSpawn=In der Quelle wurden keine Ziel Ids vergeben, aber die Spawn Anzahl ist bei 0.
TopographyChecker.source.idNotUnique=Quellen haben keine eindeutige ID.
# tab titles Topography creator / postvis
Tab.Simulation.title=Simulation
......@@ -329,8 +338,12 @@ ActionSeeDiscardChanges.popup.title=Ungespeicherte \u00c4nderungen
SaveDespiteJsonErrors.title=Fehlerhaftes JSON.
SaveDespiteJsonErrors.text=Im ge\u00f6ffneten Szenario bestehen Fehler im JSON. Wollen Sie diese korrigieren?
SaveDespiteTopographyCheckerErrors.title=Fehlerhafte Topography.
SaveDespiteTopographyCheckerErrors.text=Im ge\u00f6ffneten Szenario bestehen Fehler in der Topography. Wollen Sie diese korrigieren?
RunScenarioJsonErrors.title=JSON Fehler beheben
RunScenarioJsonErrors.text=Die Simulation kann nicht mit fehlerhaftem JSON gestartet werden.
RunScenarioTopographyCheckerErrors.title=TopographyChecker Fehler beheben
RunScenarioTopographyCheckerErrors.text=Die Simulation kann nicht mit einer fehlerhaften Topography gestartet werden.
RunScenarioNotReadyToRun.text=Eines oder mehrere der ausgew\u00e4hlten Szenario sind derzeit nicht lauff\u00e4hig.
ActionEditScenarioDescription.menu.title=Beschreibung bearbeiten
package org.vadere.gui.components.view;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.vadere.simulator.entrypoints.ReflectionAttributeModifier;
import org.vadere.gui.components.model.IDefaultModel;
import org.vadere.gui.projectview.view.JsonValidIndicator;
import org.vadere.gui.projectview.view.ProjectView;
import org.vadere.gui.projectview.view.ScenarioPanel;
import org.vadere.gui.topographycreator.model.AgentWrapper;
import org.vadere.gui.topographycreator.model.TopographyCreatorModel;
import org.vadere.simulator.util.TopographyChecker;
import org.vadere.state.attributes.Attributes;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.ScenarioElement;
import org.vadere.state.util.StateJsonConverter;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* The ScenarioElementView display's a ScenarioElement in JSON-Format.
......@@ -48,26 +42,26 @@ public class ScenarioElementView extends JPanel implements ISelectScenarioElemen
private JsonValidIndicator jsonValidIndicator;
public ScenarioElementView(final IDefaultModel defaultModel) {
this(defaultModel, null);
this(defaultModel,null, null);
}
public ScenarioElementView(final IDefaultModel defaultModel, final Component topComponent) {
public ScenarioElementView(final IDefaultModel defaultModel, final JsonValidIndicator jsonValidIndicator, final Component topComponent) {
this.panelModel = defaultModel;
this.panelModel.addSelectScenarioElementListener(this);
this.jsonValidIndicator = jsonValidIndicator;
CellConstraints cc = new CellConstraints();
JScrollPane scrollPane = new JScrollPane();
scrollPane.setPreferredSize(new Dimension(1, Toolkit.getDefaultToolkit().getScreenSize().height));
if (topComponent != null) {
if (topComponent != null && this.jsonValidIndicator !=null) {
setLayout(new FormLayout("default:grow", "pref, default"));
JPanel jsonMeta = new JPanel(); // name of the scenario element and indicator of
// valid/invalid
jsonMeta.setLayout(new BoxLayout(jsonMeta, BoxLayout.Y_AXIS));
jsonValidIndicator = new JsonValidIndicator();
jsonMeta.add(jsonValidIndicator);
jsonValidIndicator.hide();
jsonMeta.add(this.jsonValidIndicator);
this.jsonValidIndicator.hide();
jsonMeta.add(topComponent);
add(jsonMeta, cc.xy(1, 1));
......
......@@ -19,7 +19,6 @@ import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.prefs.Preferences;
......@@ -390,6 +389,15 @@ public class ProjectViewModel {
return false;
}
JEditorPane errorPanel = ScenarioPanel.getActiveTopographyErrorMsg();
if (errorPanel != null) {
VDialogManager.showMessageDialogWithBodyAndTextEditorPane(
Messages.getString("RunScenarioTopographyCheckerErrors.title"),
Messages.getString("RunScenarioTopographyCheckerErrors.text"),
errorPanel, JOptionPane.ERROR_MESSAGE);
return false;
}
return true;
}
......
......@@ -55,6 +55,7 @@ public class ScenarioPanel extends JPanel implements IProjectChangeListener, Pro
private ProjectViewModel model;
private static String activeJsonParsingErrorMsg = null;
private static JEditorPane activeTopographyErrorMsg = null;
ScenarioPanel(JLabel scenarioName, ProjectViewModel model) {
......@@ -324,6 +325,14 @@ public class ScenarioPanel extends JPanel implements IProjectChangeListener, Pro
postVisualizationView.loadOutputFile(trajectoryFile, scenarioRM);
}
public static void setActiveTopographyErrorMsg(JEditorPane msg){
activeTopographyErrorMsg = msg;
}
public static JEditorPane getActiveTopographyErrorMsg(){
return activeTopographyErrorMsg;
}
public static void setActiveJsonParsingErrorMsg(String msg) {
activeJsonParsingErrorMsg = msg;
}
......
......@@ -50,6 +50,14 @@ public class VDialogManager {
title, buttonOptions);
}
public static int showConfirmDialogWithBodyAndEditorPane(String title, String body, JEditorPane editorPane,
int buttonOptions) {
return JOptionPane.showConfirmDialog(
ProjectView.getMainWindow(),
getPanelWithBodyAndTextEditorPane(body, editorPane),
title, buttonOptions);
}
public static void showMessageDialogWithBodyAndTextArea(String title, String body, String textAreaContent,
int messageType) {
......@@ -61,6 +69,16 @@ public class VDialogManager {
title, messageType);
}
public static void showMessageDialogWithBodyAndTextEditorPane(String title, String body, JEditorPane jEditorPane,
int messageType) {
JOptionPane.showMessageDialog(
ProjectView.getMainWindow(),
getPanelWithBodyAndTextEditorPane(
"<html>" + body + "<br><br></html>",
jEditorPane),
title, messageType);
}
public static JPanel getPanelWithBodyAndTextArea(String body, String textAreaContent) {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
......@@ -75,6 +93,19 @@ public class VDialogManager {
return panel;
}
public static JPanel getPanelWithBodyAndTextEditorPane(String body, JEditorPane jEditorPane){
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JLabel label = new JLabel(body);
panel.add(label);
JScrollPane jsp = new JScrollPane(jEditorPane);
jsp.setPreferredSize(new Dimension(600, 300));
panel.add(jsp);
return panel;
}
public static void showMessageDialogWithTextArea(String title, String textAreaContent, int messageType) {
JScrollPane jsp = new JScrollPane(new JTextArea(textAreaContent)); // via http://stackoverflow.com/a/14011536
jsp.setPreferredSize(new Dimension(600, 300));
......@@ -93,6 +124,15 @@ public class VDialogManager {
if (ret == JOptionPane.YES_OPTION)
return false;
}
JEditorPane jEditorPane = ScenarioPanel.getActiveTopographyErrorMsg();
if (jEditorPane != null) {
int ret = VDialogManager.showConfirmDialogWithBodyAndEditorPane(
Messages.getString("SaveDespiteTopographyCheckerErrors.title"),
"<html>" + Messages.getString("SaveDespiteTopographyCheckerErrors.text") + "<br><br><html>",
jEditorPane, JOptionPane.YES_NO_OPTION);
if (ret == JOptionPane.YES_OPTION)
return false;
}
return true;
}
......
package org.vadere.gui.topographycreator.control;
import org.vadere.gui.components.utils.Messages;
import org.vadere.gui.components.utils.Resources;
import org.vadere.gui.projectview.view.JsonValidIndicator;
import org.vadere.gui.projectview.view.ScenarioPanel;
import org.vadere.gui.projectview.view.VDialogManager;
import org.vadere.gui.topographycreator.model.IDrawPanelModel;
import org.vadere.simulator.util.TopographyChecker;
import org.vadere.simulator.util.TopographyCheckerMessage;
import org.vadere.simulator.util.TopographyCheckerMessageType;
import org.vadere.state.scenario.ScenarioElement;
import java.awt.event.ActionEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.PriorityQueue;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
public class ActionTopographyCheckerMenu extends TopographyAction implements Observer {
private final static ImageIcon iconRed = new ImageIcon(Resources.class.getResource("/icons/light_red_icon.png"));
private final static ImageIcon iconYellow = new ImageIcon(Resources.class.getResource("/icons/light_yellow_icon.png"));
private final static ImageIcon iconGreen = new ImageIcon(Resources.class.getResource("/icons/light_green_icon.png"));
private final JsonValidIndicator jsonValidIndicator;
private PriorityQueue<TopographyCheckerMessage> errorMsg;
private PriorityQueue<TopographyCheckerMessage> warnMsg;
private MsgDocument msgDocument;
public ActionTopographyCheckerMenu(String name, IDrawPanelModel<?> panelModel, JsonValidIndicator jsonValidIndicator) {
super(name, iconYellow, panelModel);
this.jsonValidIndicator = jsonValidIndicator;
this.errorMsg = new PriorityQueue<>();
this.warnMsg = new PriorityQueue<>();
panelModel.addObserver(this);
}
/**
* Handel click on traffic light icon and show all TopographyChecker messages generated for the
* current state of the topography.
*/
@Override
public void actionPerformed(ActionEvent e) {
VDialogManager.showMessageDialogWithBodyAndTextEditorPane(
"Topography Checker",
"The following problems where found",
msgDocument,
JOptionPane.INFORMATION_MESSAGE
);
}
/**
* After each change of the Topography which yields a valid json representation check run the
* {@link TopographyChecker} and change the icon respectively. This function also creates the
* message document presented in various dialog windows.
*/
@Override
public void update(Observable o, Object arg) {
if (jsonValidIndicator.isValid()) {
TopographyChecker checker = new TopographyChecker(getScenarioPanelModel().getTopography());
errorMsg.clear();
warnMsg.clear();
ScenarioPanel.setActiveTopographyErrorMsg(null);
addMsg(checker.checkBuildingStep());
putValue(Action.SMALL_ICON, iconGreen);
if (warnMsg.size() > 0) {
putValue(Action.SMALL_ICON, iconYellow);
}
if (errorMsg.size() > 0) {
putValue(Action.SMALL_ICON, iconRed);
}
}
}
/**
* @return MsgDocument containing erros and warnings with links to the specific scenario element
*/
private MsgDocument checkerMessagesToDoc() {
StringBuilder sb = new StringBuilder();
MsgDocument doc = new MsgDocument();
if (errorMsg.size() > 0) {
sb.append("<h3> Errors </h3>").append("<br>");
errorMsg.forEach(m -> msgToDocString(sb, m, doc));
}
if (warnMsg.size() > 0) {
if (sb.length() > 0) {
sb.append("<br>");
}
sb.append("<h3> Warnings </h3>").append("<br>");
warnMsg.forEach(m -> msgToDocString(sb, m, doc));
}
if (errorMsg.size() == 0 && warnMsg.size() == 0) {
sb.append("No Problems found.");
}
doc.setText(sb.toString());
return doc;
}
private void msgToDocString(StringBuilder sb, TopographyCheckerMessage msg, MsgDocument doc) {
sb.append(Messages.getString(msg.getMsgType().getLocalTypeId())).append(": ");
doc.makeLink(msg.getElement(), sb);
sb.append("Reason: ").append(Messages.getString(msg.getReason().getLocalMessageId()));
if (!msg.getReasonModifier().isEmpty()) {
sb.append(" ").append(msg.getReasonModifier());
}
sb.append("<br>");
doc.setText(sb.toString());
}
private void addMsg(List<TopographyCheckerMessage> msg) {
msg.forEach(m -> {
if (m.getMsgType().equals(TopographyCheckerMessageType.WARN)) {
warnMsg.add(m);
} else {
errorMsg.add(m);
}
});
msgDocument = checkerMessagesToDoc();
if (errorMsg.size() > 0) {
ScenarioPanel.setActiveTopographyErrorMsg(msgDocument);
}
}
/**
* Simple {@link JEditorPane} wrapper which manages the links within the document to highlight
* the {@link ScenarioElement} producing the error / warning.
*/
class MsgDocument extends JEditorPane {
HashMap<String, ScenarioElement> linkMap;
int id;
MsgDocument() {
linkMap = new HashMap<>();
id = 0;
setContentType("text/html");
setEditable(false);
addHyperlinkListener(e -> {
System.out.println(e.getEventType().toString() + " " + e.getDescription());
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
ScenarioElement element = linkMap.getOrDefault(e.getDescription(), null);
if (element != null) {
getScenarioPanelModel().setSelectedElement(element);
}
}
});
}
void makeLink(ScenarioElement element, StringBuilder sb) {
linkMap.put("element/id/" + id, element);
sb.append("<a href='element/id/")
.append(id).append("'>")
.append(element.getClass().getSimpleName())
.append("(Id:").append(element.getId()).append(")")
.append("</a>");
id++;
}
}
}
......@@ -13,6 +13,7 @@ import org.vadere.gui.components.view.InfoPanel;
import org.vadere.gui.components.view.ScenarioElementView;
import org.vadere.gui.components.view.ScenarioToolBar;
import org.vadere.gui.projectview.control.ActionDeselect;
import org.vadere.gui.projectview.view.JsonValidIndicator;
import org.vadere.gui.topographycreator.control.ActionBasic;
import org.vadere.gui.topographycreator.control.ActionCloseDrawOptionPanel;
import org.vadere.gui.topographycreator.control.ActionCopyElement;
......@@ -28,6 +29,7 @@ import org.vadere.gui.topographycreator.control.ActionSelectCut;
import org.vadere.gui.topographycreator.control.ActionSelectSelectShape;
import org.vadere.gui.topographycreator.control.ActionSwitchCategory;
import org.vadere.gui.topographycreator.control.ActionSwitchSelectionMode;
import org.vadere.gui.topographycreator.control.ActionTopographyCheckerMenu;
import org.vadere.gui.topographycreator.control.ActionUndo;
import org.vadere.gui.topographycreator.control.ActionZoomIn;
import org.vadere.gui.topographycreator.control.ActionZoomOut;
......@@ -111,7 +113,8 @@ public class TopographyWindow extends JPanel {
infoPanel = new InfoPanel(panelModel);
selectedElementLabel = new JLabelObserver(JLabelObserver.DEFAULT_TEXT);
final ScenarioElementView textView = new ScenarioElementView(panelModel, selectedElementLabel);
JsonValidIndicator jsonValidIndicator = new JsonValidIndicator();
final ScenarioElementView textView = new ScenarioElementView(panelModel, jsonValidIndicator, selectedElementLabel);
final JPanel thisPanel = this;
......@@ -306,6 +309,12 @@ public class TopographyWindow extends JPanel {
new ImageIcon(Resources.class.getResource("/icons/topography_icon.png")),
panelModel, selectShape);
/* Topography checker*/
ActionTopographyCheckerMenu actionTopographyCheckerMenu =
new ActionTopographyCheckerMenu("TopographyChecker", panelModel, jsonValidIndicator);
/* create toolbar*/
addActionToToolbar(toolbar, selectShape, "select_shape_tooltip");
addActionToToolbar(
toolbar,
......@@ -341,6 +350,7 @@ public class TopographyWindow extends JPanel {
addActionToToolbar(toolbar, undoAction, "TopographyCreator.btnUndo.tooltip");
addActionToToolbar(toolbar, redoAction, "TopographyCreator.btnRedo.tooltip");
toolbar.add(Box.createHorizontalGlue());
addActionToToolbar(toolbar, actionTopographyCheckerMenu, "TopographyCreator.btnChecker.tooltip");
toolbar.add(infoButton);
infoButton.setToolTipText("About");
......
......@@ -4,17 +4,25 @@ import org.apache.commons.math3.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.vadere.state.scenario.Obstacle;
import org.vadere.state.scenario.Source;
import org.vadere.state.scenario.Target;
import org.vadere.state.scenario.Topography;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
public class TopographyChecker {
private final Topography topography;
private TopographyCheckerMessageBuilder msgBuilder;
public TopographyChecker(@NotNull final Topography topography) {
this.topography = topography;
this.msgBuilder = new TopographyCheckerMessageBuilder();
}
public List<Pair<Obstacle, Obstacle>> checkObstacleOverlap() {
......@@ -35,11 +43,72 @@ public class TopographyChecker {
return checkObstacleOverlap().size() > 0;
}
public List<Source> getSourceWithoutTarget() {
return topography.getSources().stream()
.filter(s -> s.getAttributes().getTargetIds().size() == 0)
.collect(Collectors.toList());
public List<TopographyCheckerMessage> checkBuildingStep(){
List<TopographyCheckerMessage> ret = new ArrayList<>();
ret.addAll(checkValidTargetsInSource());
ret.addAll(checkUniqueSourceId());
return ret;
}
public List<TopographyCheckerMessage> checkValidTargetsInSource() {
List<TopographyCheckerMessage> ret = new ArrayList<>();
Set<Integer> targetIds = topography.getTargets().stream()
.map(Target::getId)
.collect(Collectors.toSet());
for(Source s : topography.getSources()){
if (s.getAttributes().getTargetIds().size() == 0){
if (s.getAttributes().getSpawnNumber() == 0){
ret.add(msgBuilder
.warning()
.element(s)
.reason(TopographyCheckerReason.SOURCE_NO_TARGET_ID_NO_SPAWN)
.build());
} else {
ret.add(msgBuilder.error()
.element(s)
.reason(TopographyCheckerReason.SOURCE_NO_TARGET_ID_SET)
.build());
}
} else {
List<String> notFoundTargetIds = s.getAttributes().getTargetIds().stream()
.filter(tId -> !targetIds.contains(tId))
.map(tId -> Integer.toString(tId))
.collect(Collectors.toList());
if (notFoundTargetIds.size() > 0) {
StringBuilder sj = new StringBuilder();
sj.append("[");
notFoundTargetIds.forEach(i -> sj.append(i).append(", "));
sj.setLength(sj.length() - 2);
sj.append("]");
ret.add(msgBuilder.error()
.element(s)
.reason(TopographyCheckerReason.SOURCE_TARGET_ID_NOT_FOUND, sj.toString())
.build());
}
}
}
return ret;
}
public List<TopographyCheckerMessage> checkUniqueSourceId() {
List<TopographyCheckerMessage> ret = new ArrayList<>();
Set<Integer> sourceId = new HashSet<>();
for(Source s : topography.getSources()){
if (!sourceId.add(s.getId())){
ret.add(msgBuilder.warning()
.element(s)
.reason(TopographyCheckerReason.SOURCE_ID_NOT_UNIQUE)