diff --git a/.gitignore b/.gitignore
index d943149..faa5c7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
*.cu
*.routes
*.trains
+*.history
/backup
/bin/
/Debug/
diff --git a/pom.xml b/pom.xml
index c9dced1..3398015 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0de.srsoftwareweb4rail
- 1.4.17
+ 1.4.18Web4RailjarJava Model Railway Control
@@ -38,7 +38,7 @@
de.srsoftwaretools
- 1.1.12
+ 1.1.15compile
diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation
index d0f422a..0a42987 100644
--- a/resources/translations/Application.de.translation
+++ b/resources/translations/Application.de.translation
@@ -7,6 +7,8 @@ Actions (On) : Aktionen (Ein)
Actions (Off) : Aktionen (Aus)
Actions and contacts : Aktionen und Kontakte
Action type : Aktions-Typ
+Actions in case conditions are fulfilled : Aktionen, wenn Bedingungen erfüllt sind
+Actions in case conditions are not fulfilled : Aktionen, wenn Bedingungen nicht erfüllt sind
Actions will only fire, if all conditions are fullfilled. : Aktionen werden nur ausgeführt, wenn alle Bedingungen erfüllt sind.
ActivateRoute : Route aktivieren
add : hinzufügen
@@ -16,6 +18,7 @@ add action : Aktion hinzufügen
Add action to action list : Aktion zur Liste hinzufügen
add car : Waggon hinzufügen
Add condition : Bedingung hinzufügen
+Add entry : Eintrag hinzufügen
add locomotive : Lok hinzufügen
add new aspect : neues Signalbild hinzufügen
add new car : neuen Waggon anlegen
@@ -28,6 +31,7 @@ Address : Adresse
Add tag "{}" to train : Markierung "{}" zu Zug hinzufügen
Add tile : Kachel hinzufügen
Add {} to destinations of train : {} zu den Zielen des Zugs hinzufügen
+Allow editing JSON of action lists : Bearbeiten von Action-Lists per JSON-Editor erlauben
analyze : analysieren
Analyze : analysieren
Analyze may overwrite these routes! : Durch die Analyse können diese Fahrstraßen überschrieben werden!
@@ -109,6 +113,7 @@ Current location\: {} : Aufenthaltsort: {}
Current orientation : aktuelle Fahrtrichtung
Current velocity\: {} {} : Aktuelle Geschwindigkeit: {} {}
custom fields : benutzerdefinierte Felder
+Date/Time : Datum/Zeit
Decoder address : Decoder-Adresse
decouple : Abkuppeln
decoupler : decoupler
@@ -130,6 +135,7 @@ disable {} : {} deaktivieren
disabled routes : deaktivierte Fahrstraßen
DisableEnableBlock : Block (de)aktivieren
Display "{}" on {}. : „{}“ auf {} anzeigen.
+Do you know, what you are doing? : Weißt du, was du da tust?
driven distance : zurückgelegte Strecke
Drop : Verwerfen
Drop brake times : Bremszeiten löschen
@@ -138,10 +144,12 @@ due after : fällig ab
1) Duration between 5 {} steps during brake process. : 1) Zeit zwischen 5 {}-Schritten beim Bremsvorgang.
EAST : Osten
edit : bearbeiten
+edit JSON : JSON bearbeiten
Editable properties : veränderliche Eigenschaften
editable train properties : veränderliche Zug-Eigenschaften
Edit json : JSON bearbeiten
Effect : Effekt
+else\: : falls nicht:
Emergency : Notfall
empty train : leerer Zug
enable : aktivieren
@@ -150,8 +158,9 @@ Engage {} : {} aktivieren
EngageDecoupler : Entkuppler aktivieren
Enter new name for plan : Neuen Namen für den Plan eingeben
executed : ausgeführt
+executed "{}" after {} : "{}" nach {} ausgeführt
extended address : erweiterte Adresse
-export : exportieren
+Event : Ereignis
Faster ({} {}) : {} {} schneller
Final speed after breaking, before halting : Endgeschwindigkeit nach Bremsvorgang, vor dem Anhalten
FinishRoute : Route abschließen
@@ -167,12 +176,12 @@ Function : Funktion
Hardware settings : Hardware-Einstellungen
Height : Höhe
Help : Hilfe
+History : Logbuch
Hold : an lassen
(id\: {}, length\: {}) : (Id: {}, Länge: {})
if ({}) : falls ({})
If car of train\: inspect car number : Falls Fahrzeug aus Zug: Untersuche Fahrzeug Nummer
-If checked, tiles behind the train are freed according to the length of the train and the tiles. If it is unchecked, tiles will not get free before route is finished.
-Falls aktiviert, wird die Strecke anhand von Zug- und Kachel-Länge hinter dem Zug freigegeben, Falls deaktiviert wird die Strecke hinter dem Zug erst bei Abschluss der Route freigegeben.
+If checked, tiles behind the train are freed according to the length of the train and the tiles. If it is unchecked, tiles will not get free before route is finished. : Falls aktiviert, wird die Strecke anhand von Zug- und Kachel-Länge hinter dem Zug freigegeben, Falls deaktiviert wird die Strecke hinter dem Zug erst bei Abschluss der Route freigegeben.
internal contacts : interne Kontakte
Interval : Intervall
inverted : invertiert
@@ -215,6 +224,7 @@ Minimum delay : minimale Verzögerung
Minimum and maximum times (in Miliseconds) trains with the respective tag have to wait in this block. : Minamle und maximale Block-Haltezeit (in Millisekunden) für Züge mit der entsprchender Markierung.
minimum starting voltage vmin : Mindestanfahrspannung vmin
Move tiles : Kacheln verschieben
+move up : nach oben schieben
name : Name
new car : neuer Waggon
new contact : neuer Kontakt
@@ -366,6 +376,7 @@ Text to display on clients : Text, welcher auf den Clients angezeigt werden soll
Text to show on display : Text, welcher in der Anzeige dargestellt werden soll
Tile(s) : Kachel(n)
Tile(s) moved. : Kachel(n) verschoben.
+Timeout : maximale Wartezeit5
Toggle : umschalten
toggle {} : {} umschalten
Toggle power : Stom umschalten
@@ -429,4 +440,4 @@ Was not able to set all turnouts! : Konnte nicht alle Weichen stellen!
WEST : Westen
Width : Breite
{} within last {} blocks of train : {} ist in den letzten {} Blöcken des Zugs
-Your plan currently has {} routes. : Ihr Plan hat im Moment {} Fahrstraßen.
\ No newline at end of file
+Your plan currently has {} routes. : Ihr Plan hat im Moment {} Fahrstraßen.
diff --git a/src/main/java/de/srsoftware/web4rail/Application.java b/src/main/java/de/srsoftware/web4rail/Application.java
index 27e7364..54e6562 100644
--- a/src/main/java/de/srsoftware/web4rail/Application.java
+++ b/src/main/java/de/srsoftware/web4rail/Application.java
@@ -125,6 +125,8 @@ public class Application extends BaseClass{
return Condition.action(params,plan);
case REALM_CU:
return plan.controlUnit().process(params);
+ case REALM_HISTORY:
+ return History.action(params);
case REALM_LOCO:
return Locomotive.action(params,plan);
case REALM_MAINTENANCE:
diff --git a/src/main/java/de/srsoftware/web4rail/BaseClass.java b/src/main/java/de/srsoftware/web4rail/BaseClass.java
index 7d6071c..cc6fd9e 100644
--- a/src/main/java/de/srsoftware/web4rail/BaseClass.java
+++ b/src/main/java/de/srsoftware/web4rail/BaseClass.java
@@ -18,8 +18,9 @@ import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import de.srsoftware.tools.translations.Translation;
import de.srsoftware.tools.Tag;
+import de.srsoftware.tools.translations.Translation;
+import de.srsoftware.web4rail.History.LogEntry;
import de.srsoftware.web4rail.Plan.Direction;
import de.srsoftware.web4rail.actions.Action;
import de.srsoftware.web4rail.conditions.Condition;
@@ -54,7 +55,7 @@ public abstract class BaseClass implements Constants{
public static final Logger LOG = LoggerFactory.getLogger(BaseClass.class);
private static final String CUSTOM_FIELDS = "custom_Fields";
private static final String NEW_CUSTOM_FIELD_NAME = "new_custom_field_name";
- protected HashMap customFieldValues = new HashMap();
+ protected HashMap customFieldValues = new HashMap();
private BaseClass parent;
public static class Context {
@@ -308,6 +309,12 @@ public abstract class BaseClass implements Constants{
}
}
+
+ public Window addLogEntry(String text) {
+ History.assign(new History.LogEntry(text),this);
+ return properties();
+ }
+
public Button button(String text,Map additionalProps) {
return new Button(text,props(additionalProps));
}
@@ -320,6 +327,25 @@ public abstract class BaseClass implements Constants{
LOG.debug(tx, fills);
return false;
}
+
+ public static String distance(long l) {
+ String unit = Plan.lengthUnit;
+ if (DEFAULT_LENGTH_UNIT.equals(unit)) {
+ if (l > 1_000_000) {
+ l/=1_000_000;
+ unit = t("km");
+ } else
+ if (l > 1_000) {
+ l/=1_000;
+ unit = t("m");
+ } else
+ if (l > 10) {
+ l/=10;
+ unit = t("cm");
+ }
+ }
+ return l+NBSP+unit;
+ }
public Form form(String id,List> elements) {
Form form = new Form(id);
@@ -503,6 +529,21 @@ public abstract class BaseClass implements Constants{
new Button(t("Apply"),customForm).addTo(customForm).addTo(customFields);
customFields.addTo(win);
+ Fieldset history = new Fieldset(t("History"));
+
+ Form form = new Form("add-history-entry");
+ new Input(REALM, REALM_HISTORY).hideIn(form);
+ new Input(ACTION, ACTION_ADD).hideIn(form);
+ new Input(ID,id()).hideIn(form);
+ new TextArea(NOTES).addTo(form);
+ new Button(t("Add entry"), form).addTo(form);
+ form.addTo(history);
+
+ table = new Table();
+ table.addHead(t("Date/Time"),t("Event"));
+ for (LogEntry entry : History.getFor(this)) table.addRow(new SimpleDateFormat("YYYY-dd-MM HH:mm").format(entry.date()),entry.getText());
+ table.addTo(history).addTo(win);
+
return win;
}
diff --git a/src/main/java/de/srsoftware/web4rail/Constants.java b/src/main/java/de/srsoftware/web4rail/Constants.java
index 6a6ebf0..0a2a296 100644
--- a/src/main/java/de/srsoftware/web4rail/Constants.java
+++ b/src/main/java/de/srsoftware/web4rail/Constants.java
@@ -46,6 +46,7 @@ public interface Constants {
public static final String REALM_CONDITION = "condition";
public static final String REALM_CONTACT = "contact";
public static final String REALM_CU = "cu";
+ public static final String REALM_HISTORY = "history";
public static final String REALM_LOCO = "loco";
public static final String REALM_MAINTENANCE = "maintenance";
public static final String REALM_ROUTE = "route";
diff --git a/src/main/java/de/srsoftware/web4rail/History.java b/src/main/java/de/srsoftware/web4rail/History.java
new file mode 100644
index 0000000..6bbfbb0
--- /dev/null
+++ b/src/main/java/de/srsoftware/web4rail/History.java
@@ -0,0 +1,102 @@
+package de.srsoftware.web4rail;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Vector;
+
+import org.json.JSONObject;
+
+import de.srsoftware.web4rail.BaseClass.Id;
+
+public class History {
+
+ private static HashMap> log = new HashMap<>();
+
+ static class LogEntry extends BaseClass {
+ private long timestamp;
+ private String text;
+
+ public LogEntry(String text) {
+ this.text = text;
+ timestamp = new Date().getTime();
+ }
+
+ public Date date() {
+ return new Date(timestamp);
+ }
+
+ public long getTime() {
+ return timestamp;
+ }
+
+ public String getText() {
+ return text;
+ }
+ };
+
+ public static LogEntry assign(LogEntry logEntry, BaseClass object) {
+ Id id = object.id();
+ Vector list = log.get(id);
+ if (list == null) log.put(id, list = new Vector<>());
+ list.insertElementAt(logEntry,0);
+ return logEntry;
+ }
+
+ public static Vector getFor(BaseClass object){
+ Vector list = log.get(object.id());
+ return list != null ? list : new Vector<>();
+ }
+
+ public static Object action(HashMap params) {
+
+ switch (params.get(Constants.ACTION)) {
+ case Constants.ACTION_ADD:
+ BaseClass object = BaseClass.get(Id.from(params));
+ return object != null ? object.addLogEntry(params.get(Constants.NOTES)) : BaseClass.t("Unknown object!");
+
+ }
+
+ return BaseClass.t("Unknown action: {}",params.get(Constants.ACTION));
+ }
+
+ public static void save(String filename) {
+ try {
+ FileWriter file = new FileWriter(filename, Constants.UTF8);
+ JSONObject json = new JSONObject();
+ log.entrySet().forEach(entry -> {
+ JSONObject list = new JSONObject();
+ entry.getValue().forEach(le -> list.put(le.timestamp+"", le.getText()));
+ json.put(entry.getKey().toString(), list);
+ });
+ json.write(file);
+ file.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void load(String filename) throws IOException {
+ BufferedReader file = new BufferedReader(new FileReader(filename, Constants.UTF8));
+ JSONObject json = new JSONObject(file.readLine());
+ file.close();
+
+ for (String id : json.keySet()) {
+ JSONObject o = json.getJSONObject(id);
+ Vector entries = new Vector<>();
+
+ for (String time : o.keySet()) {
+ LogEntry le = new LogEntry(o.getString(time));
+ le.timestamp = Long.parseLong(time);
+ entries.add(le);
+ }
+ Collections.sort(entries, (a,b) -> Long.compare(b.timestamp, a.timestamp));
+ log.put(new Id(id), entries);
+ }
+
+ }
+}
diff --git a/src/main/java/de/srsoftware/web4rail/MaintnanceTask.java b/src/main/java/de/srsoftware/web4rail/MaintnanceTask.java
index 2a6d2a8..f7f2d47 100644
--- a/src/main/java/de/srsoftware/web4rail/MaintnanceTask.java
+++ b/src/main/java/de/srsoftware/web4rail/MaintnanceTask.java
@@ -84,6 +84,7 @@ public class MaintnanceTask extends BaseClass{
Block block = train.currentBlock();
if (isSet(block)) plan.place(block);
}
+ car.addLogEntry(t("executed \"{}\" after {}",name,BaseClass.distance(lastExecutionDist)));
return car.properties();
}
return t("parent is not a car!");
diff --git a/src/main/java/de/srsoftware/web4rail/Plan.java b/src/main/java/de/srsoftware/web4rail/Plan.java
index bb2da0a..d8d0e55 100644
--- a/src/main/java/de/srsoftware/web4rail/Plan.java
+++ b/src/main/java/de/srsoftware/web4rail/Plan.java
@@ -157,12 +157,14 @@ public class Plan extends BaseClass{
private static final String FREE_BEHIND_TRAIN = "free_behind_train";
private static final String RENAME = "rename";
private static final String SPEED_STEP = "speed_step";
+ private static final String ALLOW_JSON_EDIT = "allow_json_edit";
private String name = DEFAULT_NAME;
private ControlUnit controlUnit = new ControlUnit(this); // the control unit, to which the plan is connected
private Contact learningContact;
private Configuration appConfig;
private LinkedList listeners = new LinkedList<>();
+ public static boolean allowJsonEdit = false;
/**
* creates a new plan, starts to send heart beats
@@ -563,6 +565,9 @@ public class Plan extends BaseClass{
} catch (Exception e) {
LOG.warn("Was not able to establish connection to control unit!");
}
+
+ History.load(name+".history");
+
LoadCallback.fire();
}
@@ -714,6 +719,7 @@ public class Plan extends BaseClass{
formInputs.add(t("Speed step"),new Input(SPEED_STEP, Train.defaultSpeedStep).attr("title", t("Speeds are always increadsed/decreased by this value")));
formInputs.add(t("Lower speed limit"),new Input(FINAL_SPEED, Train.defaultEndSpeed).attr("title", t("Final speed after breaking, before halting")));
formInputs.add(t("Free tiles behind train"),new Checkbox(FREE_BEHIND_TRAIN, t("If checked, tiles behind the train are freed according to the length of the train and the tiles. If it is unchecked, tiles will not get free before route is finished."), Route.freeBehindTrain));
+ formInputs.add(t("Allow editing JSON of action lists"),new Checkbox(ALLOW_JSON_EDIT, t("Do you know, what you are doing?"), allowJsonEdit ));
postForm.add(relayProperties());
postForm.add(routeProperties());
@@ -859,6 +865,8 @@ public class Plan extends BaseClass{
file.write(json().toString());
file.close();
+ History.save(name+".history");
+
return t("Plan saved as \"{}\".",name);
}
@@ -1026,7 +1034,8 @@ public class Plan extends BaseClass{
if (params.containsKey(LENGTH_UNIT)) lengthUnit = params.get(LENGTH_UNIT);
if (params.containsKey(SPEED_UNIT)) speedUnit = params.get(SPEED_UNIT);
if (params.containsKey(SPEED_STEP)) Train.defaultSpeedStep = Integer.parseInt(params.get(SPEED_STEP));
- if (params.containsKey(FINAL_SPEED)) Train.defaultEndSpeed = Integer.parseInt(params.get(FINAL_SPEED));
+ if (params.containsKey(FINAL_SPEED)) Train.defaultEndSpeed = Integer.parseInt(params.get(FINAL_SPEED));
+ allowJsonEdit = "on".equalsIgnoreCase(params.get(ALLOW_JSON_EDIT));
Route.freeBehindTrain = "on".equalsIgnoreCase(params.get(FREE_BEHIND_TRAIN));
return t("Plan updated.");
diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java
index e787c25..7ae9e58 100644
--- a/src/main/java/de/srsoftware/web4rail/Route.java
+++ b/src/main/java/de/srsoftware/web4rail/Route.java
@@ -348,7 +348,7 @@ public class Route extends BaseClass {
setupActions = new ActionList(this);
triggeredActions.put(ROUTE_SETUP, setupActions);
}
- setupActions.list().addTo(setup).addTo(list);
+ setupActions.listAt(setup).addTo(list);
Tag start = new Tag("li").content(t("Start actions")+COL);
ActionList startActions = triggeredActions.get(ROUTE_START);
@@ -356,7 +356,7 @@ public class Route extends BaseClass {
startActions = new ActionList(this);
triggeredActions.put(ROUTE_START, startActions);
}
- startActions.list().addTo(start).addTo(list);
+ startActions.listAt(start).addTo(list);
for (Contact c : contacts) {
Tag item = c.link("span", c).addTo(new Tag("li")).content(NBSP);
@@ -365,7 +365,7 @@ public class Route extends BaseClass {
actions = new ActionList(this);
triggeredActions.put(c.trigger(), actions);
}
- actions.list().addTo(item).addTo(list);
+ actions.listAt(item).addTo(list);
}
list.addTo(win);
return win;
diff --git a/src/main/java/de/srsoftware/web4rail/actions/Action.java b/src/main/java/de/srsoftware/web4rail/actions/Action.java
index 605e6eb..6cac058 100644
--- a/src/main/java/de/srsoftware/web4rail/actions/Action.java
+++ b/src/main/java/de/srsoftware/web4rail/actions/Action.java
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import de.srsoftware.tools.Tag;
import de.srsoftware.web4rail.BaseClass;
+import de.srsoftware.web4rail.LoadCallback;
import de.srsoftware.web4rail.tags.Button;
import de.srsoftware.web4rail.tags.Fieldset;
import de.srsoftware.web4rail.tags.Form;
@@ -110,6 +111,7 @@ public abstract class Action extends BaseClass {
((ActionList)this).clear();
}
load(json);
+ LoadCallback.fire();
return context().properties();
}
Window win = new Window("json-import-export-"+id(), t("JSON code of {}",this));
diff --git a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java
index d662cda..631bd03 100644
--- a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java
+++ b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java
@@ -123,23 +123,22 @@ public class ActionList extends Action implements Iterable{
return json;
}
- public Tag list() {
- Tag span = new Tag("span");
- button(t("add action"), Map.of(ACTION, ACTION_ADD)).addTo(span);
- button(t("export"), Map.of(ACTION, ACTION_SAVE)).addTo(span);
+ public T listAt(T parent) {
+ button(parent.is("fieldset") ? t("add action") : "+", Map.of(ACTION, ACTION_ADD)).title(t("add action")).addTo(parent);
+ if (plan.allowJsonEdit) button(t("edit JSON"), Map.of(ACTION, ACTION_SAVE)).addTo(parent);
if (!isEmpty()) {
Tag list = new Tag("ol");
for (Action action : actions) {
Tag item = action.link("span",action).addTo(new Tag("li")).content(NBSP);
- action.button("-", Map.of(ACTION,ACTION_DROP)).addTo(item);
- action.button("↑", Map.of(ACTION,ACTION_MOVE)).addTo(item);
- if (action instanceof ActionList) ((ActionList) action).list().addTo(item);
+ action.button("↑", Map.of(ACTION,ACTION_MOVE)).title(t("move up")).addTo(item);
+ action.button("-", Map.of(ACTION,ACTION_DROP)).title(t("delete")).addTo(item);
+ if (action instanceof ActionList) ((ActionList) action).listAt(item);
item.addTo(list);
}
- list.addTo(span);
+ list.addTo(parent);
}
- return span;
+ return (T)parent;
}
public Action load(JSONObject json) {
@@ -248,9 +247,7 @@ public class ActionList extends Action implements Iterable{
@Override
protected Window properties(List