/* */ package js.java.plugins.monitorWand; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.NoSuchElementException; import java.util.TreeMap; import java.util.TreeSet; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import js.java.stspluginlib.PluginClient; /** * * @author js */ public class connection extends PluginClient { /** * Behälter für die Züge und ihren Fahrplan */ private static class ZugDetailFahrplan { final public int zid; final public String name; public int verspaetung; public String gleis; public String plangleis; final public String von; final public String nach; public boolean sichtbar; public LinkedList plan = null; public ZugDetailFahrplan(ZugDetails reference) { zid = reference.zid; name = reference.name; verspaetung = reference.verspaetung; gleis = reference.gleis; plangleis = reference.plangleis; von = reference.von; nach = reference.nach; sichtbar = reference.sichtbar; } public String getFormattedAn() { try { return plan.getFirst().getFormattedAn(); } catch (NullPointerException e) { return ""; } catch (NoSuchElementException e) { return ""; } } public String getFormattedAb() { try { return plan.getFirst().getFormattedAb(); } catch (NullPointerException e) { return ""; } catch (NoSuchElementException e) { return ""; } } public long getAb() { try { return plan.getFirst().ab; } catch (NullPointerException e) { return 0; } catch (NoSuchElementException e) { return 0; } } public boolean update(ZugDetails other) { boolean ret = !(plangleis.equals(other.plangleis)); verspaetung = other.verspaetung; sichtbar = other.sichtbar; gleis = other.gleis; plangleis = other.plangleis; return ret; } } /** * das Menü für die Bahnsteigliste */ private final JMenu menu; /** * die Bahnsteigliste */ private final TreeMap bahnsteige = new TreeMap(); /** * Name des Stellwerks */ private String name = ""; /** * Listener für die Menu-Auswahl */ private ActionListener menuListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { repaint(); } }; /** * Timer-Listener, um alle 30 Sekunden die Zugdaten zu aktualisieren */ private ActionListener refreshListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { request_zugliste(); } }; /** * der Timer zum refreshListener */ private javax.swing.Timer refreshTimer = new javax.swing.Timer(1000 * 30, refreshListener); /** * Liste der Züge und ihrer Fahrpläne */ private HashMap zuglist = new HashMap(); connection(JMenu m) { /** * PluginClient verlangt 4 Parameter im Konstruktor: * - Name des Plugins (MonitorWand) * - Autor (JS) * - Version des Plugins (0.9) * - eine kurze Beschreibung des Plugins (Demo-Abfahrtwand) */ super("MonitorWand", "JS", "0.9", "Demo-Abfahrtwand"); this.menu = m; } /** * Wird von PluginClient aufgerufen, wenn die Verbindung zum Sim erfolgreich hergestellt * wurde. Zu dem Zeitpunkt wurde sich auch schon registriert. * Die Simzeit ist erst nach diesem Aufruf verfügbar! */ @Override protected void connected() { this.request_setdebug(true); // Debugmeldungen in der Sim-Console ein this.request_anlageninfo(); this.request_bahnsteigliste(); refreshTimer.setInitialDelay(1000); refreshTimer.start(); } protected void repaint() { // tut nichts } /** * Wird vom PluginClient aufgerufen, wenn die Verbindung beendet wurde. Die Ursache ist dabei egal. */ @Override protected void closed() { System.exit(100); } /** * Antwort vom * @param aid * @param name * @param build */ @Override protected void response_anlageninfo(int aid, String name, String build) { this.name = name; repaint(); } /** * Antwort vom * @param bl > * * Baut das Menü mit den Bahnsteigen (neu) auf */ @Override protected void response_bahnsteigliste(final HashMap> bl) { synchronized (bahnsteige) { bahnsteige.clear(); for (String s : bl.keySet()) { bahnsteige.put(s, null); } } javax.swing.SwingUtilities.invokeLater(new Runnable() { @Override public void run() { menu.removeAll(); synchronized (bahnsteige) { for (Map.Entry s : bahnsteige.entrySet()) { JCheckBoxMenuItem m = new JCheckBoxMenuItem(s.getKey()); m.setSelected(true); m.addActionListener(menuListener); menu.add(m); s.setValue(m); } } repaint(); } }); } /** * Antwort vom * @param zl * * speichert neue Züge in einer Liste, fragt für jeden Zug die Details ab */ @Override protected void response_zugliste(HashMap zl) { for (Integer zid : zl.keySet()) { if (!zuglist.containsKey(zid)) { zuglist.put(zid, null); // noch unbekannter ZID, neu aufnehmen } this.request_zugdetails(zid); // Details immer abfragen, Verspätungen, Gleisänderung ändern sich ständig } Iterator it = zuglist.keySet().iterator(); while (it.hasNext()) { int zid = it.next(); if (!zl.containsKey(zid)) { it.remove(); // aufräumen, ZIDs, die nicht mehr mitgeschickt wurden, gibt es nicht mehr } } } /** * Antwort vom * @param zid * @param details */ @Override protected void response_zugdetails(int zid, ZugDetails details) { ZugDetailFahrplan zd = zuglist.get(zid); if (zd == null) { // Zug völlig neue, noch keine Details da zd = new ZugDetailFahrplan(details); zuglist.put(zid, zd); this.request_zugfahrplan(zid); // Fahrplan abfragen } else { zd.update(details); this.request_zugfahrplan(zid); } repaint(); } /** * Antwort vom * @param zid * @param plan */ @Override protected void response_zugfahrplan(int zid, LinkedList plan) { System.out.println("Fahrplan für " + zid); ZugDetailFahrplan zd = zuglist.get(zid); if (zd != null) { zd.plan = plan; // Fahrplan so speichern } else { System.out.println("Fehler: Für " + zid + " fehlen die Details!"); } } /** * @return name des Stellwerks */ public String getName() { return name; } /** * enthält jeweils einen Zug für die Abfahrtwand */ public class Abfahrten implements Comparable { static private final String SPACER = " "; public final String name; /// Zugname public final String gleis; /// Gleisname (ggf. nach Gleisänderung) public final String ab; /// Abfahrtzeit (formatiert HH:MM) public final String nach; /// Zugziel (Ausfahrt) public final String infotext; /// ein Info-Text mit Verspätung, Gleisänderung, etc. private final long abLong; /// Abfahrtzeit in Millis, zum Sortieren Abfahrten(ZugDetailFahrplan z, ZugFahrplanZeile f) { String t = ""; if (z.verspaetung > 5) { t += "ca. " + ((z.verspaetung / 5) * 5) + " Min später" + SPACER; } name = z.name; gleis = cleverGleis(f.gleis); if (!f.plan.equals(f.gleis)) { t += "Gleisänderung von Gleis " + cleverGleis(f.plan) + SPACER; } else if (z.gleis != null && z.plangleis != null && f.plan.equals(z.plangleis) && !z.plangleis.equals(z.gleis)) { t += "Gleisänderung von Gleis " + cleverGleis(z.plangleis) + SPACER; } abLong = f.ab; ab = f.getFormattedAb(); nach = z.nach; if (f.flags.hasFlag('R')) { t += "Zug ändert Fahrtrichtung" + SPACER; } if (f.flags.hasFlag('F')) { int fzid = f.flags.dataOfFlag('F'); ZugDetailFahrplan fz = zuglist.get(fzid); if (fz == null || fz.nach.isEmpty()) { t += "Zug wird geteilt" + SPACER; } else { t += "Zug wird geteilt, vorderer Zugteil nach " + nach + ", hinterer Zugteil als " + fz.name + " nach " + fz.nach + SPACER; } } infotext = t; } /** * führende Buchstaben im Gleisnamen entfernen * @param g * @return */ private String cleverGleis(String g) { StringBuilder ret = new StringBuilder(g); while (ret.length() > 0 && !Character.isDigit(ret.charAt(0))) { ret.deleteCharAt(0); } return ret.toString(); } @Override public int compareTo(Object o) { Abfahrten oo = (Abfahrten) o; int r = (int) (abLong - oo.abLong); if (r == 0) { r = name.compareToIgnoreCase(oo.name); } if (r == 0) { r = gleis.compareToIgnoreCase(oo.gleis); } return r; } } /** * liefert eine sortierte Liste der noch ausstehenden Abfahrten für die * ausgewählten Bahnsteige * * Ablauf ist der simpel: Züge, deren Ist- oder Soll-Gleis im Menü ausgewählt ist, * werden in die Liste aufgenommen. Die Sortierung macht diese für uns automatisch. * Die Wand gibt später alle Züge der Reihe nach aus, bis die Wand voll ist. * Weggelassen werden endende Züge (E-Flag und K-Flag), denn die haben keine Abfahrt. * @return */ public TreeSet getAbfahrten() { TreeSet ret = new TreeSet(); for (ZugDetailFahrplan z : zuglist.values()) { if (z.gleis != null) { try { System.out.println("Zug: " + z.name + " von " + z.von + " ab " + z.getFormattedAb()); for (ZugFahrplanZeile g : z.plan) { if (!g.flags.hasFlag('E') && !g.flags.hasFlag('K') && !g.flags.hasFlag('D')) { JCheckBoxMenuItem cb = bahnsteige.get(g.gleis); boolean add = (cb != null && cb.isSelected()); cb = bahnsteige.get(g.plan); add |= (cb != null && cb.isSelected()); if (add) { System.out.println("zeigen"); ret.add(new Abfahrten(z, g)); } } else { System.out.println("Flag"); } } } catch (NullPointerException ex) { System.out.println("Unvollständig: " + z.name + "(" + z.zid + ")"); } } } return ret; } }