Index: /applications/editors/josm/plugins/public_transport/build.xml
===================================================================
--- /applications/editors/josm/plugins/public_transport/build.xml	(revision 33764)
+++ /applications/editors/josm/plugins/public_transport/build.xml	(revision 33765)
@@ -8,5 +8,5 @@
 
     <property name="plugin.author" value="Roland M. Olbricht"/>
-    <property name="plugin.class" value="public_transport.PublicTransportPlugin"/>
+    <property name="plugin.class" value="org.openstreetmap.josm.plugins.public_transport.PublicTransportPlugin"/>
     <property name="plugin.description" value="This plugin simplifies the mapping and editing of public transport routes."/>
     <!-- <property name="plugin.icon" value=""/> -->
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/AStarAlgorithm.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/AStarAlgorithm.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/AStarAlgorithm.java	(revision 33765)
@@ -0,0 +1,121 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+
+public abstract class AStarAlgorithm {
+    // The following abstract functions and subclasses must be overridden by a class using
+    // AStarAlgorithm.
+
+    public abstract static class Vertex implements Comparable<Vertex> {
+        @Override
+        public abstract int compareTo(Vertex v);
+    }
+
+    public abstract static class Edge {
+        public abstract Vertex getBegin();
+
+        public abstract Vertex getEnd();
+
+        public abstract double getLength();
+    }
+
+    public abstract Vector<Edge> getNeighbors(Vertex vertex);
+
+    public abstract double estimateDistance(Vertex vertex);
+
+    // end of interface to override -------------------------------------------
+
+    public AStarAlgorithm(Vertex begin, Vertex end) {
+        this.begin = begin;
+        this.end = end;
+        openList = new TreeMap<>();
+        closedList = new TreeSet<>();
+        pathTail = new TreeMap<>();
+    }
+
+    public Vertex determineCurrentStart() {
+        Vertex minVertex = null;
+        double minDist = 0;
+        Iterator<Map.Entry<Vertex, Double>> iter = openList.entrySet().iterator();
+        while (iter.hasNext()) {
+            Map.Entry<Vertex, Double> entry = iter.next();
+            double distance = entry.getValue().doubleValue() + estimateDistance(entry.getKey());
+            if (minVertex == null || distance < minDist) {
+                minDist = distance;
+                minVertex = entry.getKey();
+            }
+        }
+        if (minVertex != null) {
+            System.out.print(openList.get(minVertex).doubleValue());
+            System.out.print("\t");
+            System.out.println(minDist);
+        }
+
+        return minVertex;
+    }
+
+    Vector<Edge> shortestPath() {
+        // Travel through the network
+        Vertex currentStart = begin;
+        openList.put(currentStart, 0.0);
+        while (currentStart != null && !currentStart.equals(end)) {
+            double startDistance = openList.get(currentStart).doubleValue();
+
+            // Mark currentStart as visited.
+            openList.remove(currentStart);
+            closedList.add(currentStart);
+
+            Iterator<Edge> neighbors = getNeighbors(currentStart).iterator();
+            while (neighbors.hasNext()) {
+                Edge edge = neighbors.next();
+
+                // Don't walk back.
+                if (closedList.contains(edge.getEnd()))
+                    continue;
+
+                // Update entry in openList
+                Double knownDistance = openList.get(edge.getEnd());
+                double distance = startDistance + edge.getLength();
+
+                if (knownDistance == null || distance < knownDistance.doubleValue()) {
+                    openList.put(edge.getEnd(), distance);
+                    pathTail.put(edge.getEnd(), edge);
+                }
+            }
+
+            currentStart = determineCurrentStart();
+        }
+
+        if (currentStart == null)
+            return null;
+
+        // Reconstruct the found path
+        Vector<Edge> backwards = new Vector<>();
+        Vertex currentEnd = end;
+        while (!currentEnd.equals(begin)) {
+            backwards.add(pathTail.get(currentEnd));
+            currentEnd = pathTail.get(currentEnd).getBegin();
+        }
+
+        Vector<Edge> result = new Vector<>();
+        for (int i = backwards.size() - 1; i >= 0; --i) {
+            result.add(backwards.elementAt(i));
+        }
+        return result;
+    }
+
+    protected Vertex begin;
+
+    protected Vertex end;
+
+    private TreeSet<Vertex> closedList;
+
+    private TreeMap<Vertex, Double> openList;
+
+    private TreeMap<Vertex, Edge> pathTail;
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/PublicTransportAStar.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/PublicTransportAStar.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/PublicTransportAStar.java	(revision 33765)
@@ -0,0 +1,143 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport;
+
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.MainApplication;
+
+public class PublicTransportAStar extends AStarAlgorithm {
+    public PublicTransportAStar(Node start, Node end) {
+        super(new NodeVertex(start), new NodeVertex(end));
+    }
+
+    public static class NodeVertex extends AStarAlgorithm.Vertex {
+        public NodeVertex(Node node) {
+            this.node = node;
+        }
+
+        @Override
+        public int compareTo(AStarAlgorithm.Vertex v) {
+            return this.node.compareTo(((NodeVertex) v).node);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if ((NodeVertex) o == null)
+                return false;
+            return node.equals(((NodeVertex) o).node);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(node);
+        }
+
+        public Node node;
+    }
+
+    public static class PartialWayEdge extends AStarAlgorithm.Edge {
+        public PartialWayEdge(Way way, int beginIndex, int endIndex) {
+            this.way = way;
+            this.beginIndex = beginIndex;
+            this.endIndex = endIndex;
+        }
+
+        @Override
+        public AStarAlgorithm.Vertex getBegin() {
+            return new NodeVertex(way.getNode(beginIndex));
+        }
+
+        @Override
+        public AStarAlgorithm.Vertex getEnd() {
+            return new NodeVertex(way.getNode(endIndex));
+        }
+
+        @Override
+        public double getLength() {
+            int min = beginIndex;
+            int max = endIndex;
+            if (endIndex < beginIndex) {
+                min = endIndex;
+                max = beginIndex;
+            }
+
+            double totalDistance = 0;
+            for (int i = min; i < max; ++i) {
+                totalDistance += way.getNode(i).getCoor().greatCircleDistance(way.getNode(i + 1).getCoor());
+            }
+            return totalDistance;
+        }
+
+        public Way way;
+
+        public int beginIndex;
+
+        public int endIndex;
+    }
+
+    @Override
+    public Vector<AStarAlgorithm.Edge> getNeighbors(AStarAlgorithm.Vertex vertex) {
+        if (waysPerNode == null) {
+            waysPerNode = new TreeMap<>();
+
+            Iterator<Way> iter = MainApplication.getLayerManager().getEditDataSet().getWays().iterator();
+            while (iter.hasNext()) {
+                Way way = iter.next();
+
+                // Only consider ways that are usable
+                if (!way.isUsable())
+                    continue;
+
+                // Further tests whether the way is eligible.
+
+                for (int i = 0; i < way.getNodesCount(); ++i) {
+                    if (waysPerNode.get(way.getNode(i)) == null)
+                        waysPerNode.put(way.getNode(i), new TreeSet<Way>());
+                    waysPerNode.get(way.getNode(i)).add(way);
+                }
+            }
+        }
+
+        NodeVertex nodeVertex = (NodeVertex) vertex;
+        System.out.println(nodeVertex.node.getUniqueId());
+
+        Vector<AStarAlgorithm.Edge> result = new Vector<>();
+        // Determine all ways in which nodeVertex.node is contained.
+        Iterator<Way> iter = waysPerNode.get(nodeVertex.node).iterator();
+        while (iter.hasNext()) {
+            Way way = iter.next();
+
+            // Only consider ways that are usable
+            if (!way.isUsable())
+                continue;
+
+            // Further tests whether the way is eligible.
+
+            for (int i = 0; i < way.getNodesCount(); ++i) {
+                if (way.getNode(i).equals(nodeVertex.node)) {
+                    if (i > 0)
+                        result.add(new PartialWayEdge(way, i, i - 1));
+                    if (i < way.getNodesCount() - 1)
+                        result.add(new PartialWayEdge(way, i, i + 1));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    TreeMap<Node, TreeSet<Way>> waysPerNode = null;
+
+    @Override
+    public double estimateDistance(AStarAlgorithm.Vertex vertex) {
+        NodeVertex nodeVertex = (NodeVertex) vertex;
+        return ((NodeVertex) super.end).node.getCoor()
+                .greatCircleDistance(nodeVertex.node.getCoor());
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/PublicTransportPlugin.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/PublicTransportPlugin.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/PublicTransportPlugin.java	(revision 33765)
@@ -0,0 +1,52 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.actions.RoutePatternAction;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+
+public class PublicTransportPlugin extends Plugin {
+
+    static JMenu jMenu;
+
+    public PublicTransportPlugin(PluginInformation info) {
+        super(info);
+        refreshMenu();
+    }
+
+    public static void refreshMenu() {
+        MainMenu menu = MainApplication.getMenu();
+
+        if (jMenu == null)
+            jMenu = menu.addMenu("Public Transport", tr("Public Transport"), KeyEvent.VK_COMMA,
+                    menu.getDefaultMenuPos(), "help");
+        else
+            jMenu.removeAll();
+
+        jMenu.addSeparator();
+        jMenu.add(new JMenuItem(new StopImporterAction()));
+        jMenu.add(new JMenuItem(new RoutePatternAction()));
+        jMenu.add(new JMenuItem(new GTFSImporterAction()));
+        setEnabledAll(true);
+    }
+
+    private static void setEnabledAll(boolean isEnabled) {
+        for (int i = 0; i < jMenu.getItemCount(); i++) {
+            JMenuItem item = jMenu.getItem(i);
+
+            if (item != null)
+                item.setEnabled(isEnabled);
+        }
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/TransText.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/TransText.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/TransText.java	(revision 33765)
@@ -0,0 +1,17 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class TransText {
+    public String text;
+
+    public TransText(String t) {
+        text = t;
+    }
+
+    @Override
+    public String toString() {
+        return text == null ? "" : tr(text);
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/GTFSImporterAction.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/GTFSImporterAction.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/GTFSImporterAction.java	(revision 33765)
@@ -0,0 +1,508 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.text.DecimalFormat;
+import java.text.Format;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.DefaultListModel;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JTable;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.commands.GTFSAddCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.GTFSCatchCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.GTFSDeleteCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.GTFSJoinCommand;
+import org.openstreetmap.josm.plugins.public_transport.dialogs.GTFSImporterDialog;
+import org.openstreetmap.josm.plugins.public_transport.models.GTFSStopTableModel;
+import org.openstreetmap.josm.plugins.public_transport.refs.TrackReference;
+import org.openstreetmap.josm.tools.Logging;
+
+public class GTFSImporterAction extends JosmAction {
+    private static GTFSImporterDialog dialog = null;
+
+    private static DefaultListModel<?> tracksListModel = null;
+
+    private static Vector<String> data = null;
+
+    private static TrackReference currentTrack = null;
+
+    private static GTFSStopTableModel gtfsStopTM = null;
+
+    public boolean inEvent = false;
+
+    /**
+     * Constructs a new {@code GTFSImporterAction}.
+     */
+    public GTFSImporterAction() {
+        super(tr("Create Stops from GTFS ..."), null, tr("Create Stops from a GTFS file"), null,
+                false);
+        putValue("toolbar", "publictransport/gtfsimporter");
+        MainApplication.getToolbar().register(this);
+    }
+
+    public GTFSStopTableModel getGTFSStopTableModel() {
+        return gtfsStopTM;
+    }
+
+    public GTFSImporterDialog getDialog() {
+        return dialog;
+    }
+
+    public DefaultListModel<?> getTracksListModel() {
+        if (tracksListModel == null)
+            tracksListModel = new DefaultListModel<>();
+        return tracksListModel;
+    }
+
+    public TrackReference getCurrentTrack() {
+        return currentTrack;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent event) {
+
+        if (dialog == null)
+            dialog = new GTFSImporterDialog(this);
+
+        dialog.setVisible(true);
+
+        if (tr("Create Stops from GTFS ...").equals(event.getActionCommand())) {
+            String curDir = Main.pref.get("lastDirectory");
+            if (curDir.isEmpty()) {
+                curDir = ".";
+            }
+            JFileChooser fc = new JFileChooser(new File(curDir));
+            fc.setDialogTitle(tr("Select GTFS file (stops.txt)"));
+            fc.setMultiSelectionEnabled(false);
+
+            int answer = fc.showOpenDialog(Main.parent);
+            if (answer != JFileChooser.APPROVE_OPTION)
+                return;
+
+            if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir))
+                Main.pref.put("lastDirectory", fc.getCurrentDirectory().getAbsolutePath());
+
+            importData(fc.getSelectedFile());
+
+            refreshData();
+
+/*    } else if ("stopImporter.settingsGPSTimeStart".equals(event.getActionCommand()))
+    {
+      if ((!inEvent) && (dialog.gpsTimeStartValid()) && (currentTrack != null))
+    Main.main.undoRedo.add(new TrackStoplistRelocateCommand(this));
+    }
+    else if ("stopImporter.settingsStopwatchStart".equals(event.getActionCommand()))
+    {
+      if ((!inEvent) && (dialog.stopwatchStartValid()) && (currentTrack != null))
+    Main.main.undoRedo.add(new TrackStoplistRelocateCommand(this));
+    }
+    else if ("stopImporter.settingsTimeWindow".equals(event.getActionCommand()))
+    {
+      if (currentTrack != null)
+    currentTrack.timeWindow = dialog.getTimeWindow();
+    }
+    else if ("stopImporter.settingsThreshold".equals(event.getActionCommand()))
+    {
+      if (currentTrack != null)
+    currentTrack.threshold = dialog.getThreshold();
+    }
+    else if ("stopImporter.settingsSuggestStops".equals(event.getActionCommand()))
+      Main.main.undoRedo.add(new TrackSuggestStopsCommand(this));
+    else if ("stopImporter.stoplistFind".equals(event.getActionCommand()))
+      findNodesInTable(dialog.getStoplistTable(), currentTrack.stoplistTM.getNodes());
+    else if ("stopImporter.stoplistShow".equals(event.getActionCommand()))
+      showNodesFromTable(dialog.getStoplistTable(), currentTrack.stoplistTM.getNodes());
+    else if ("stopImporter.stoplistMark".equals(event.getActionCommand()))
+      markNodesFromTable(dialog.getStoplistTable(), currentTrack.stoplistTM.getNodes());
+    else if ("stopImporter.stoplistDetach".equals(event.getActionCommand()))
+    {
+      Main.main.undoRedo.add(new TrackStoplistDetachCommand(this));
+      dialog.getStoplistTable().clearSelection();
+    */
+        } else if ("gtfsImporter.gtfsStopsAdd".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new GTFSAddCommand(this));
+        else if ("gtfsImporter.gtfsStopsDelete".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new GTFSDeleteCommand(this));
+        else if ("gtfsImporter.gtfsStopsCatch".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new GTFSCatchCommand(this));
+        else if ("gtfsImporter.gtfsStopsJoin".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new GTFSJoinCommand(this));
+        else if ("gtfsImporter.gtfsStopsFind".equals(event.getActionCommand()))
+            findNodesInTable(dialog.getGTFSStopTable(), gtfsStopTM.nodes);
+        else if ("gtfsImporter.gtfsStopsShow".equals(event.getActionCommand()))
+            showNodesFromTable(dialog.getGTFSStopTable(), gtfsStopTM.nodes);
+        else if ("gtfsImporter.gtfsStopsMark".equals(event.getActionCommand()))
+            markNodesFromTable(dialog.getGTFSStopTable(), gtfsStopTM.nodes);
+    }
+
+    private void importData(final File file) {
+        try (BufferedReader r = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
+            if (data == null)
+                data = new Vector<>();
+            else
+                data.clear();
+
+            while (r.ready()) {
+                data.add(r.readLine());
+            }
+        } catch (FileNotFoundException e) {
+            Logging.error(e);
+            JOptionPane.showMessageDialog(null, tr("File \"{0}\" does not exist", file.getName()));
+        } catch (IOException e) {
+            Logging.error(e);
+            JOptionPane.showMessageDialog(null, tr("IOException \"{0}\" occurred", e.toString()));
+        }
+    }
+
+    private void refreshData() {
+        if (data != null) {
+            Vector<Node> existingStops = new Vector<>();
+
+            DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+            if (ds == null) {
+                JOptionPane.showMessageDialog(null,
+                        tr("There exists no dataset. Try to download data from the server or open an OSM file."),
+                        tr("No data found"), JOptionPane.ERROR_MESSAGE);
+
+                return;
+            } else {
+                Iterator<Node> iter = ds.getNodes().iterator();
+                while (iter.hasNext()) {
+                    Node node = iter.next();
+                    if ("bus_stop".equals(node.get("highway")))
+                        existingStops.add(node);
+                }
+            }
+
+            Iterator<String> iter = data.iterator();
+            if (iter.hasNext())
+                gtfsStopTM = new GTFSStopTableModel(this, iter.next());
+            else {
+                JOptionPane.showMessageDialog(null, tr("The GTFS file was empty."),
+                        tr("No data found"), JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+
+            while (iter.hasNext()) {
+                String s = iter.next();
+                gtfsStopTM.addRow(s, existingStops);
+            }
+            dialog.setGTFSStopTableModel(gtfsStopTM);
+        } else {
+            JOptionPane.showMessageDialog(null, tr("The GTFS file was empty."), tr("No data found"),
+                    JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+//   public void tracksSelectionChanged(int selectedPos)
+//   {
+//     if (selectedPos >= 0)
+//     {
+//       currentTrack = ((TrackReference)tracksListModel.elementAt(selectedPos));
+//       dialog.setTrackValid(true);
+//
+//       //Prepare Settings
+//       dialog.setSettings
+//    (currentTrack.gpsSyncTime, currentTrack.stopwatchStart,
+//     currentTrack.timeWindow, currentTrack.threshold);
+//
+//       //Prepare Stoplist
+//       dialog.setStoplistTableModel
+//           (((TrackReference)tracksListModel.elementAt(selectedPos)).stoplistTM);
+//     }
+//     else
+//     {
+//       currentTrack = null;
+//       dialog.setTrackValid(false);
+//     }
+//   }
+
+    public static Node createNode(LatLon latLon, String id, String name) {
+        Node node = new Node(latLon);
+        node.put("highway", "bus_stop");
+        node.put("stop_id", id);
+        node.put("name", name);
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        if (ds == null) {
+            JOptionPane.showMessageDialog(null,
+                    tr("There exists no dataset."
+                            + " Try to download data from the server or open an OSM file."),
+                    tr("No data found"), JOptionPane.ERROR_MESSAGE);
+
+            return null;
+        }
+        ds.addPrimitive(node);
+        return node;
+    }
+
+    /**
+     * returns a collection of all selected lines or a collection of all lines otherwise
+     */
+    public static Vector<Integer> getConsideredLines(JTable table) {
+        int[] selectedLines = table.getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < table.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+        return consideredLines;
+    }
+
+    /** marks the table items whose nodes are marked on the map */
+    public static void findNodesInTable(JTable table, Vector<Node> nodes) {
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        if (ds == null)
+            return;
+
+        table.clearSelection();
+
+        for (int i = 0; i < table.getRowCount(); ++i) {
+            if ((nodes.elementAt(i) != null) && (ds.isSelected(nodes.elementAt(i))))
+                table.addRowSelectionInterval(i, i);
+        }
+    }
+
+    /**
+     * shows the nodes that correspond to the marked lines in the table.
+     * If no lines are marked in the table, show all nodes from the vector
+     */
+    public static void showNodesFromTable(JTable table, Vector<Node> nodes) {
+        BoundingXYVisitor box = new BoundingXYVisitor();
+        Vector<Integer> consideredLines = getConsideredLines(table);
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            int j = consideredLines.elementAt(i);
+            if (nodes.elementAt(j) != null)
+                nodes.elementAt(j).accept(box);
+        }
+        if (box.getBounds() == null)
+            return;
+        box.enlargeBoundingBox();
+        MainApplication.getMap().mapView.zoomTo(box);
+    }
+
+    /**
+     * marks the nodes that correspond to the marked lines in the table.
+     * If no lines are marked in the table, mark all nodes from the vector
+     */
+    public static void markNodesFromTable(JTable table, Vector<Node> nodes) {
+        OsmPrimitive[] osmp = {null};
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        ds.setSelected(osmp);
+        Vector<Integer> consideredLines = getConsideredLines(table);
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            int j = consideredLines.elementAt(i);
+            if (nodes.elementAt(j) != null)
+                ds.addSelected(nodes.elementAt(j));
+        }
+    }
+
+    public static String timeOf(double t) {
+        t -= Math.floor(t / 24 / 60 / 60) * 24 * 60 * 60;
+
+        int hour = (int) Math.floor(t / 60 / 60);
+        t -= Math.floor(t / 60 / 60) * 60 * 60;
+        int minute = (int) Math.floor(t / 60);
+        t -= Math.floor(t / 60) * 60;
+        double second = t;
+
+        Format format = new DecimalFormat("00");
+        Format formatS = new DecimalFormat("00.###");
+        return format.format(hour) + ":" + format.format(minute) + ":" + formatS.format(second);
+    }
+
+    public Action getFocusAddAction() {
+        return new FocusAddAction();
+    }
+
+    private class FocusAddAction extends AbstractAction {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            Main.main.undoRedo.add(new GTFSAddCommand(GTFSImporterAction.this));
+            showNodesFromTable(dialog.getGTFSStopTable(), gtfsStopTM.nodes);
+        }
+    }
+
+/*  public Action getFocusWaypointShelterAction(String shelter)
+  {
+    return new FocusWaypointShelterAction(shelter);
+  }
+
+  public Action getFocusWaypointDeleteAction()
+  {
+    return new AbstractAction()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+    JTable table = dialog.getWaypointsTable();
+    int row = table.getEditingRow();
+    if (row < 0)
+      return;
+    table.clearSelection();
+    table.addRowSelectionInterval(row, row);
+/*  Main.main.undoRedo.add
+        (new WaypointsDisableCommand(GTFSImporterAction.this));*
+      }
+    };
+  }
+
+  public Action getFocusTrackStoplistNameAction()
+  {
+    return new FocusTrackStoplistNameAction();
+  }
+
+  public Action getFocusTrackStoplistShelterAction(String shelter)
+  {
+    return new FocusTrackStoplistShelterAction(shelter);
+  }
+
+  public Action getFocusStoplistDeleteAction()
+  {
+    return new AbstractAction()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+    JTable table = dialog.getStoplistTable();
+    int row = table.getEditingRow();
+    if (row < 0)
+      return;
+    table.clearSelection();
+    table.addRowSelectionInterval(row, row);
+/*  Main.main.undoRedo.add
+        (new TrackStoplistDeleteCommand(GTFSImporterAction.this));*
+      }
+    };
+  }
+
+  private class FocusWaypointNameAction extends AbstractAction
+  {
+    public void actionPerformed(ActionEvent e)
+    {
+      JTable table = dialog.getWaypointsTable();
+      showNodesFromTable(table, waypointTM.nodes);
+      markNodesFromTable(table, waypointTM.nodes);
+      int row = table.getEditingRow();
+      if (row < 0)
+    row = 0;
+      waypointTM.inEvent = true;
+      if (table.getCellEditor() != null)
+      {
+    if (!table.getCellEditor().stopCellEditing())
+      table.getCellEditor().cancelCellEditing();
+      }
+      table.editCellAt(row, 1);
+      table.getCellEditor().getTableCellEditorComponent
+      (table, "", true, row, 1);
+      waypointTM.inEvent = false;
+    }
+  };
+
+  private class FocusWaypointShelterAction extends AbstractAction
+  {
+    private String defaultShelter = null;
+
+    public FocusWaypointShelterAction(String defaultShelter)
+    {
+      this.defaultShelter = defaultShelter;
+    }
+
+    public void actionPerformed(ActionEvent e)
+    {
+      JTable table = dialog.getWaypointsTable();
+      showNodesFromTable(table, waypointTM.nodes);
+      markNodesFromTable(table, waypointTM.nodes);
+      int row = table.getEditingRow();
+      if (row < 0)
+    row = 0;
+      waypointTM.inEvent = true;
+      if (table.getCellEditor() != null)
+      {
+    if (!table.getCellEditor().stopCellEditing())
+      table.getCellEditor().cancelCellEditing();
+      }
+      table.editCellAt(row, 2);
+      waypointTM.inEvent = false;
+      table.getCellEditor().getTableCellEditorComponent
+          (table, defaultShelter, true, row, 2);
+    }
+  };
+
+  private class FocusTrackStoplistNameAction extends AbstractAction
+  {
+    public void actionPerformed(ActionEvent e)
+    {
+      JTable table = dialog.getStoplistTable();
+      showNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+      markNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+      int row = table.getEditingRow();
+      if (row < 0)
+    row = 0;
+      currentTrack.inEvent = true;
+      if (table.getCellEditor() != null)
+      {
+    if (!table.getCellEditor().stopCellEditing())
+      table.getCellEditor().cancelCellEditing();
+      }
+      table.editCellAt(row, 1);
+      table.getCellEditor().getTableCellEditorComponent
+          (table, "", true, row, 1);
+      currentTrack.inEvent = false;
+    }
+  };
+
+  private class FocusTrackStoplistShelterAction extends AbstractAction
+  {
+    private String defaultShelter = null;
+
+    public FocusTrackStoplistShelterAction(String defaultShelter)
+    {
+      this.defaultShelter = defaultShelter;
+    }
+
+    public void actionPerformed(ActionEvent e)
+    {
+      JTable table = dialog.getStoplistTable();
+      showNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+      markNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+      int row = table.getEditingRow();
+      if (row < 0)
+    row = 0;
+      currentTrack.inEvent = true;
+      if (table.getCellEditor() != null)
+      {
+    if (!table.getCellEditor().stopCellEditing())
+      table.getCellEditor().cancelCellEditing();
+      }
+      table.editCellAt(row, 2);
+      currentTrack.inEvent = false;
+      table.getCellEditor().getTableCellEditorComponent
+          (table, defaultShelter, true, row, 2);
+    }
+  };*/
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/RoutePatternAction.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/RoutePatternAction.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/RoutePatternAction.java	(revision 33765)
@@ -0,0 +1,1977 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableCellEditor;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.actions.mapmode.DeleteAction;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.models.ItineraryTableModel;
+import org.openstreetmap.josm.plugins.public_transport.refs.RouteReference;
+import org.openstreetmap.josm.plugins.public_transport.refs.StopReference;
+
+public class RoutePatternAction extends JosmAction {
+
+    public static int STOPLIST_ROLE_COLUMN = 2;
+
+    private static class RoutesLSL implements ListSelectionListener {
+        RoutePatternAction root = null;
+
+        RoutesLSL(RoutePatternAction rpa) {
+            root = rpa;
+        }
+
+        @Override
+        public void valueChanged(ListSelectionEvent e) {
+            root.routesSelectionChanged();
+        }
+    }
+
+    private static class TagTableModel extends DefaultTableModel implements TableModelListener {
+        Relation relation = null;
+
+        TreeSet<String> blacklist = null;
+
+        boolean hasFixedKeys = true;
+
+        TagTableModel(boolean hasFixedKeys) {
+            this.hasFixedKeys = hasFixedKeys;
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            if ((column == 0) && (hasFixedKeys))
+                return false;
+            return true;
+        }
+
+        public void readRelation(Relation rel) {
+            relation = rel;
+
+            for (int i = 0; i < getRowCount(); ++i) {
+                String value = rel.get((String) getValueAt(i, 0));
+                if (value == null)
+                    value = "";
+                setValueAt(value, i, 1);
+            }
+        }
+
+        public void readRelation(Relation rel, TreeSet<String> blacklist) {
+            relation = rel;
+            this.blacklist = blacklist;
+
+            setRowCount(0);
+            Iterator<Map.Entry<String, String>> iter = rel.getKeys().entrySet().iterator();
+            while (iter.hasNext()) {
+                Map.Entry<String, String> entry = iter.next();
+                if (!blacklist.contains(entry.getKey())) {
+                    Vector<String> newRow = new Vector<>();
+                    newRow.add(entry.getKey());
+                    newRow.add(entry.getValue());
+                    addRow(newRow);
+                }
+            }
+
+            for (int i = 0; i < getRowCount(); ++i) {
+                String value = rel.get((String) getValueAt(i, 0));
+                if (value == null)
+                    value = "";
+                setValueAt(value, i, 1);
+            }
+        }
+
+        @Override
+        public void tableChanged(TableModelEvent e) {
+            if (e.getType() == TableModelEvent.UPDATE) {
+                relation.setModified(true);
+
+                String key = (String) getValueAt(e.getFirstRow(), 0);
+                if (key == null)
+                    return;
+                if ((blacklist == null) || (!blacklist.contains(key))) {
+                    relation.setModified(true);
+                    if ("".equals(getValueAt(e.getFirstRow(), 1)))
+                        relation.remove(key);
+                    else
+                        relation.put(key, (String) getValueAt(e.getFirstRow(), 1));
+                } else {
+                    if (e.getColumn() == 0)
+                        setValueAt("", e.getFirstRow(), 0);
+                }
+            }
+        }
+    }
+
+    private static class CustomCellEditorTable extends JTable {
+        TreeMap<Integer, TableCellEditor> col1 = null;
+
+        TreeMap<Integer, TableCellEditor> col2 = null;
+
+        CustomCellEditorTable() {
+            col1 = new TreeMap<>();
+            col2 = new TreeMap<>();
+        }
+
+        @Override
+        public TableCellEditor getCellEditor(int row, int column) {
+            TableCellEditor editor = null;
+            if (column == 0)
+                editor = col1.get(Integer.valueOf(row));
+            else
+                editor = col2.get(Integer.valueOf(row));
+            if (editor == null)
+                return new DefaultCellEditor(new JTextField());
+            else
+                return editor;
+        }
+
+        public void setCellEditor(int row, int column, TableCellEditor editor) {
+            if (column == 0)
+                col1.put(Integer.valueOf(row), editor);
+            else
+                col2.put(Integer.valueOf(row), editor);
+        }
+    }
+
+    private static class StoplistTableModel extends DefaultTableModel {
+
+        public Vector<Node> nodes = new Vector<>();
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            if (column != STOPLIST_ROLE_COLUMN)
+                return false;
+            return true;
+        }
+
+        @Override
+        public void addRow(Object[] obj) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void insertRow(int insPos, Object[] obj) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void addRow(Node node, String role, double distance) {
+            insertRow(-1, node, role, distance);
+        }
+
+        public void insertRow(int insPos, Node node, String role, double distance) {
+            String[] buf = {"", "", "", ""};
+            String curName = node.get("name");
+            if (curName != null) {
+                buf[0] = curName;
+            } else {
+                buf[0] = tr("[ID] {0}", (Long.valueOf(node.getId())).toString());
+            }
+            String curRef = node.get("ref");
+            if (curRef != null) {
+                buf[1] = curRef;
+            }
+            buf[STOPLIST_ROLE_COLUMN] = role;
+            buf[3] = Double.toString(((double) (int) (distance * 40000 / 360.0 * 100)) / 100);
+
+            if (insPos == -1) {
+                nodes.addElement(node);
+                super.addRow(buf);
+            } else {
+                nodes.insertElementAt(node, insPos);
+                super.insertRow(insPos, buf);
+            }
+        }
+
+        public void clear() {
+            nodes.clear();
+            super.setRowCount(0);
+        }
+    }
+
+    private class StoplistTableModelListener implements TableModelListener {
+        @Override
+        public void tableChanged(TableModelEvent e) {
+            if (e.getType() == TableModelEvent.UPDATE) {
+                rebuildNodes();
+            }
+        }
+    }
+
+    private static class SegmentMetric {
+        public double aLat, aLon;
+
+        public double length;
+
+        public double d1, d2, o1, o2;
+
+        public double distance;
+
+        SegmentMetric(double fromLat, double fromLon, double toLat, double toLon,
+                double distance) {
+            this.distance = distance;
+
+            aLat = fromLat;
+            aLon = fromLon;
+
+            // Compute length and direction
+            // length is in units of latitude degrees
+            d1 = toLat - fromLat;
+            d2 = (toLon - fromLon) * Math.cos(fromLat * Math.PI / 180.0);
+            length = Math.sqrt(d1 * d1 + d2 * d2);
+
+            // Normalise direction
+            d1 = d1 / length;
+            d2 = d2 / length;
+
+            // Compute orthogonal direction (right hand size is positive)
+            o1 = -d2;
+            o2 = d1;
+
+            // Prepare lon direction to reduce the number of necessary multiplications
+            d2 = d2 * Math.cos(fromLat * Math.PI / 180.0);
+            o2 = o2 * Math.cos(fromLat * Math.PI / 180.0);
+        }
+    }
+
+    private static JDialog jDialog = null;
+
+    private static JTabbedPane tabbedPane = null;
+
+    private static DefaultListModel<RouteReference> relsListModel = null;
+
+    private static TagTableModel requiredTagsData = null;
+
+    private static CustomCellEditorTable requiredTagsTable = null;
+
+    private static TagTableModel commonTagsData = null;
+
+    private static CustomCellEditorTable commonTagsTable = null;
+
+    private static TagTableModel otherTagsData = null;
+
+    private static TreeSet<String> tagBlacklist = null;
+
+    private static CustomCellEditorTable otherTagsTable = null;
+
+    private static ItineraryTableModel itineraryData = null;
+
+    private static JTable itineraryTable = null;
+
+    private static StoplistTableModel stoplistData = null;
+
+    private static JTable stoplistTable = null;
+
+    private static JList<RouteReference> relsList = null;
+
+    private static JCheckBox cbRight = null;
+
+    private static JCheckBox cbLeft = null;
+
+    private static JTextField tfSuggestStopsLimit = null;
+
+    private static Relation currentRoute = null;
+
+    private static Vector<SegmentMetric> segmentMetrics = null;
+
+    private static Vector<RelationMember> markedWays = new Vector<>();
+
+    private static Vector<RelationMember> markedNodes = new Vector<>();
+
+    public RoutePatternAction() {
+        super(tr("Route patterns ..."), null, tr("Edit Route patterns for public transport"), null,
+                false);
+        putValue("toolbar", "publictransport/routepattern");
+        MainApplication.getToolbar().register(this);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent event) {
+        Frame frame = JOptionPane.getFrameForComponent(Main.parent);
+        DataSet mainDataSet = MainApplication.getLayerManager().getEditDataSet();
+
+        if (jDialog == null) {
+            jDialog = new JDialog(frame, tr("Route Patterns"), false);
+            tabbedPane = new JTabbedPane();
+            JPanel tabOverview = new JPanel();
+            tabbedPane.addTab(tr("Overview"), tabOverview);
+            JPanel tabTags = new JPanel();
+            tabbedPane.addTab(tr("Tags"), tabTags);
+            JPanel tabItinerary = new JPanel();
+            tabbedPane.addTab(tr("Itinerary"), tabItinerary);
+            JPanel tabStoplist = new JPanel();
+            tabbedPane.addTab(tr("Stops"), tabStoplist);
+            JPanel tabMeta = new JPanel();
+            tabbedPane.addTab(tr("Meta"), tabMeta);
+            tabbedPane.setEnabledAt(0, true);
+            tabbedPane.setEnabledAt(1, false);
+            tabbedPane.setEnabledAt(2, false);
+            tabbedPane.setEnabledAt(3, false);
+            tabbedPane.setEnabledAt(4, false);
+            jDialog.add(tabbedPane);
+
+            // Overview Tab
+            Container contentPane = tabOverview;
+            GridBagLayout gridbag = new GridBagLayout();
+            GridBagConstraints layoutCons = new GridBagConstraints();
+            contentPane.setLayout(gridbag);
+
+            JLabel headline = new JLabel(tr("Existing route patterns:"));
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 0;
+            layoutCons.gridwidth = 3;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(headline, layoutCons);
+            contentPane.add(headline);
+
+            relsListModel = new DefaultListModel<>();
+            relsList = new JList<>(relsListModel);
+            JScrollPane rpListSP = new JScrollPane(relsList);
+            String[] data = {"1", "2", "3", "4", "5", "6"};
+            relsListModel.copyInto(data);
+            relsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+            relsList.addListSelectionListener(new RoutesLSL(this));
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 1;
+            layoutCons.gridwidth = 3;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 1.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(rpListSP, layoutCons);
+            contentPane.add(rpListSP);
+
+            JButton bRefresh = new JButton(tr("Refresh"));
+            bRefresh.setActionCommand("routePattern.refresh");
+            bRefresh.addActionListener(this);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.gridheight = 2;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bRefresh, layoutCons);
+            contentPane.add(bRefresh);
+
+            JButton bNew = new JButton(tr("New"));
+            bNew.setActionCommand("routePattern.overviewNew");
+            bNew.addActionListener(this);
+
+            layoutCons.gridx = 1;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.gridheight = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bNew, layoutCons);
+            contentPane.add(bNew);
+
+            JButton bDelete = new JButton(tr("Delete"));
+            bDelete.setActionCommand("routePattern.overviewDelete");
+            bDelete.addActionListener(this);
+
+            layoutCons.gridx = 1;
+            layoutCons.gridy = 3;
+            layoutCons.gridwidth = 1;
+            layoutCons.gridheight = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bDelete, layoutCons);
+            contentPane.add(bDelete);
+
+            JButton bDuplicate = new JButton(tr("Duplicate"));
+            bDuplicate.setActionCommand("routePattern.overviewDuplicate");
+            bDuplicate.addActionListener(this);
+
+            layoutCons.gridx = 2;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.gridheight = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bDuplicate, layoutCons);
+            contentPane.add(bDuplicate);
+
+            JButton bReflect = new JButton(tr("Reflect"));
+            bReflect.setActionCommand("routePattern.overviewReflect");
+            bReflect.addActionListener(this);
+
+            layoutCons.gridx = 2;
+            layoutCons.gridy = 3;
+            layoutCons.gridwidth = 1;
+            layoutCons.gridheight = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bReflect, layoutCons);
+            contentPane.add(bReflect);
+
+            // Tags Tab
+            /* Container */ contentPane = tabTags;
+            /* GridBagLayout */ gridbag = new GridBagLayout();
+            /* GridBagConstraints */ layoutCons = new GridBagConstraints();
+            contentPane.setLayout(gridbag);
+
+            /* JLabel */ headline = new JLabel(tr("Required tags:"));
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 0;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(headline, layoutCons);
+            contentPane.add(headline);
+
+            requiredTagsTable = new CustomCellEditorTable();
+            requiredTagsData = new TagTableModel(true);
+            requiredTagsData.addColumn(tr("Key"));
+            requiredTagsData.addColumn(tr("Value"));
+            tagBlacklist = new TreeSet<>();
+            Vector<String> rowContent = new Vector<>();
+            /* TODO: keys and values should also be translated using TransText class */
+            rowContent.add("type");
+            tagBlacklist.add("type");
+            rowContent.add("route");
+            requiredTagsData.addRow(rowContent);
+            JComboBox<String> comboBox = new JComboBox<>();
+            comboBox.addItem("route");
+            requiredTagsTable.setCellEditor(0, 1, new DefaultCellEditor(comboBox));
+            rowContent = new Vector<>();
+            rowContent.add(0, "route");
+            tagBlacklist.add("route");
+            rowContent.add(1, "bus");
+            requiredTagsData.addRow(rowContent);
+            /* JComboBox */ comboBox = new JComboBox<>();
+            comboBox.addItem("bus");
+            comboBox.addItem("trolleybus");
+            comboBox.addItem("tram");
+            comboBox.addItem("light_rail");
+            comboBox.addItem("subway");
+            comboBox.addItem("rail");
+            requiredTagsTable.setCellEditor(1, 1, new DefaultCellEditor(comboBox));
+            rowContent = new Vector<>();
+            rowContent.add(0, "ref");
+            tagBlacklist.add("ref");
+            rowContent.add(1, "");
+            requiredTagsData.addRow(rowContent);
+            rowContent = new Vector<>();
+            rowContent.add(0, "to");
+            tagBlacklist.add("to");
+            rowContent.add(1, "");
+            requiredTagsData.addRow(rowContent);
+            rowContent = new Vector<>();
+            rowContent.add(0, "network");
+            tagBlacklist.add("network");
+            rowContent.add(1, "");
+            requiredTagsData.addRow(rowContent);
+            requiredTagsTable.setModel(requiredTagsData);
+            JScrollPane tableSP = new JScrollPane(requiredTagsTable);
+            requiredTagsData.addTableModelListener(requiredTagsData);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.25;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(tableSP, layoutCons);
+            Dimension preferredSize = tableSP.getPreferredSize();
+            preferredSize.setSize(tableSP.getPreferredSize().getWidth(),
+                    tableSP.getPreferredSize().getHeight() / 4.0);
+            tableSP.setPreferredSize(preferredSize);
+            contentPane.add(tableSP);
+
+            headline = new JLabel(tr("Common tags:"));
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 2;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(headline, layoutCons);
+            contentPane.add(headline);
+
+            commonTagsTable = new CustomCellEditorTable();
+            commonTagsData = new TagTableModel(true);
+            commonTagsData.addColumn(tr("Key"));
+            commonTagsData.addColumn(tr("Value"));
+            rowContent = new Vector<>();
+            rowContent.add(0, "loc_ref");
+            tagBlacklist.add("loc_ref");
+            rowContent.add(1, "");
+            commonTagsData.addRow(rowContent);
+            rowContent = new Vector<>();
+            rowContent.add(0, "direction");
+            tagBlacklist.add("direction");
+            rowContent.add(1, "");
+            commonTagsData.addRow(rowContent);
+            rowContent = new Vector<>();
+            rowContent.add(0, "from");
+            tagBlacklist.add("from");
+            rowContent.add(1, "");
+            commonTagsData.addRow(rowContent);
+            rowContent = new Vector<>();
+            rowContent.add(0, "operator");
+            tagBlacklist.add("operator");
+            rowContent.add(1, "");
+            commonTagsData.addRow(rowContent);
+            rowContent = new Vector<>();
+            rowContent.add(0, "color");
+            tagBlacklist.add("color");
+            rowContent.add(1, "");
+            commonTagsData.addRow(rowContent);
+            rowContent = new Vector<>();
+            rowContent.add(0, "name");
+            tagBlacklist.add("name");
+            rowContent.add(1, "");
+            commonTagsData.addRow(rowContent);
+            commonTagsTable.setModel(commonTagsData);
+            /* JScrollPane */ tableSP = new JScrollPane(commonTagsTable);
+            commonTagsData.addTableModelListener(commonTagsData);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 3;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.25;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(tableSP, layoutCons);
+            /* Dimension */ preferredSize = tableSP.getPreferredSize();
+            preferredSize.setSize(tableSP.getPreferredSize().getWidth(),
+                    tableSP.getPreferredSize().getHeight() / 4.0);
+            tableSP.setPreferredSize(preferredSize);
+            contentPane.add(tableSP);
+
+            headline = new JLabel(tr("Additional tags:"));
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 4;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(headline, layoutCons);
+            contentPane.add(headline);
+
+            otherTagsTable = new CustomCellEditorTable();
+            otherTagsData = new TagTableModel(false);
+            otherTagsData.addColumn(tr("Key"));
+            otherTagsData.addColumn(tr("Value"));
+            otherTagsTable.setModel(otherTagsData);
+            /* JScrollPane */ tableSP = new JScrollPane(otherTagsTable);
+            otherTagsData.addTableModelListener(otherTagsData);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 5;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 1.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(tableSP, layoutCons);
+            /* Dimension */ preferredSize = tableSP.getPreferredSize();
+            preferredSize.setSize(tableSP.getPreferredSize().getWidth(),
+                    tableSP.getPreferredSize().getHeight() / 2.0);
+            tableSP.setPreferredSize(preferredSize);
+            contentPane.add(tableSP);
+
+            JButton bAddTag = new JButton(tr("Add a new Tag"));
+            bAddTag.setActionCommand("routePattern.tagAddTag");
+            bAddTag.addActionListener(this);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 6;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bAddTag, layoutCons);
+            contentPane.add(bAddTag);
+
+            // Itinerary Tab
+            contentPane = tabItinerary;
+            gridbag = new GridBagLayout();
+            layoutCons = new GridBagConstraints();
+            contentPane.setLayout(gridbag);
+
+            itineraryTable = new JTable();
+            itineraryData = new ItineraryTableModel();
+            itineraryData.addColumn(tr("Name/Id"));
+            itineraryData.addColumn(tr("Role"));
+            itineraryTable.setModel(itineraryData);
+            /* JScrollPane */ tableSP = new JScrollPane(itineraryTable);
+            /* JComboBox */ comboBox = new JComboBox<>();
+            comboBox.addItem("");
+            comboBox.addItem("forward");
+            comboBox.addItem("backward");
+            itineraryTable.getColumnModel().getColumn(1)
+                    .setCellEditor(new DefaultCellEditor(comboBox));
+            itineraryData.addTableModelListener(itineraryData);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 0;
+            layoutCons.gridwidth = 4;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 1.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(tableSP, layoutCons);
+            contentPane.add(tableSP);
+
+            JButton bFind = new JButton(tr("Find"));
+            bFind.setActionCommand("routePattern.itineraryFind");
+            bFind.addActionListener(this);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 1;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bFind, layoutCons);
+            contentPane.add(bFind);
+
+            JButton bShow = new JButton(tr("Show"));
+            bShow.setActionCommand("routePattern.itineraryShow");
+            bShow.addActionListener(this);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bShow, layoutCons);
+            contentPane.add(bShow);
+
+            JButton bMark = new JButton(tr("Mark"));
+            bMark.setActionCommand("routePattern.itineraryMark");
+            bMark.addActionListener(this);
+
+            layoutCons.gridx = 1;
+            layoutCons.gridy = 1;
+            layoutCons.gridheight = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bMark, layoutCons);
+            contentPane.add(bMark);
+
+            JButton bAdd = new JButton(tr("Add"));
+            bAdd.setActionCommand("routePattern.itineraryAdd");
+            bAdd.addActionListener(this);
+
+            layoutCons.gridx = 2;
+            layoutCons.gridy = 1;
+            layoutCons.gridheight = 1;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bAdd, layoutCons);
+            contentPane.add(bAdd);
+
+            /* JButton */ bDelete = new JButton(tr("Delete"));
+            bDelete.setActionCommand("routePattern.itineraryDelete");
+            bDelete.addActionListener(this);
+
+            layoutCons.gridx = 2;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bDelete, layoutCons);
+            contentPane.add(bDelete);
+
+            JButton bSort = new JButton(tr("Sort"));
+            bSort.setActionCommand("routePattern.itinerarySort");
+            bSort.addActionListener(this);
+
+            layoutCons.gridx = 3;
+            layoutCons.gridy = 1;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bSort, layoutCons);
+            contentPane.add(bSort);
+
+            /* JButton */ bReflect = new JButton(tr("Reflect"));
+            bReflect.setActionCommand("routePattern.itineraryReflect");
+            bReflect.addActionListener(this);
+
+            layoutCons.gridx = 3;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bReflect, layoutCons);
+            contentPane.add(bReflect);
+
+            // Stoplist Tab
+            contentPane = tabStoplist;
+            gridbag = new GridBagLayout();
+            layoutCons = new GridBagConstraints();
+            contentPane.setLayout(gridbag);
+
+            stoplistTable = new JTable();
+            stoplistData = new StoplistTableModel();
+            stoplistData.addColumn(tr("Name/Id"));
+            stoplistData.addColumn(tr("Ref"));
+            stoplistData.addColumn(tr("Role"));
+            stoplistData.addColumn(tr("km"));
+            stoplistTable.setModel(stoplistData);
+            /* JScrollPane */ tableSP = new JScrollPane(stoplistTable);
+            /* JComboBox */ comboBox = new JComboBox<>();
+            comboBox.addItem("");
+            comboBox.addItem("forward_stop");
+            comboBox.addItem("backward_stop");
+            stoplistTable.getColumnModel().getColumn(STOPLIST_ROLE_COLUMN)
+                    .setCellEditor(new DefaultCellEditor(comboBox));
+            stoplistData.addTableModelListener(new StoplistTableModelListener());
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 0;
+            layoutCons.gridwidth = 4;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 1.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(tableSP, layoutCons);
+            contentPane.add(tableSP);
+
+            /* JButton */ bFind = new JButton(tr("Find"));
+            bFind.setActionCommand("routePattern.stoplistFind");
+            bFind.addActionListener(this);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 1;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bFind, layoutCons);
+            contentPane.add(bFind);
+
+            /* JButton */ bShow = new JButton(tr("Show"));
+            bShow.setActionCommand("routePattern.stoplistShow");
+            bShow.addActionListener(this);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bShow, layoutCons);
+            contentPane.add(bShow);
+
+            /* JButton */ bMark = new JButton(tr("Mark"));
+            bMark.setActionCommand("routePattern.stoplistMark");
+            bMark.addActionListener(this);
+
+            layoutCons.gridx = 1;
+            layoutCons.gridy = 1;
+            layoutCons.gridheight = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bMark, layoutCons);
+            contentPane.add(bMark);
+
+            /* JButton */ bAdd = new JButton(tr("Add"));
+            bAdd.setActionCommand("routePattern.stoplistAdd");
+            bAdd.addActionListener(this);
+
+            layoutCons.gridx = 2;
+            layoutCons.gridy = 1;
+            layoutCons.gridheight = 1;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bAdd, layoutCons);
+            contentPane.add(bAdd);
+
+            /* JButton */ bDelete = new JButton(tr("Delete"));
+            bDelete.setActionCommand("routePattern.stoplistDelete");
+            bDelete.addActionListener(this);
+
+            layoutCons.gridx = 2;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bDelete, layoutCons);
+            contentPane.add(bDelete);
+
+            /* JButton */ bSort = new JButton(tr("Sort"));
+            bSort.setActionCommand("routePattern.stoplistSort");
+            bSort.addActionListener(this);
+
+            layoutCons.gridx = 3;
+            layoutCons.gridy = 1;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bSort, layoutCons);
+            contentPane.add(bSort);
+
+            /* JButton */ bReflect = new JButton(tr("Reflect"));
+            bReflect.setActionCommand("routePattern.stoplistReflect");
+            bReflect.addActionListener(this);
+
+            layoutCons.gridx = 3;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bReflect, layoutCons);
+            contentPane.add(bReflect);
+
+            // Meta Tab
+            contentPane = tabMeta;
+            gridbag = new GridBagLayout();
+            layoutCons = new GridBagConstraints();
+            contentPane.setLayout(gridbag);
+
+            JLabel rightleft = new JLabel(tr("Stops are possible on the"));
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 1;
+            layoutCons.gridwidth = 2;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(rightleft, layoutCons);
+            contentPane.add(rightleft);
+
+            cbRight = new JCheckBox(tr("right hand side"), true);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 2;
+            layoutCons.gridwidth = 2;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(cbRight, layoutCons);
+            contentPane.add(cbRight);
+
+            cbLeft = new JCheckBox(tr("left hand side"), false);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 3;
+            layoutCons.gridwidth = 2;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(cbLeft, layoutCons);
+            contentPane.add(cbLeft);
+
+            JLabel maxdist = new JLabel(tr("Maximum distance from route"));
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 4;
+            layoutCons.gridwidth = 2;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(maxdist, layoutCons);
+            contentPane.add(maxdist);
+
+            tfSuggestStopsLimit = new JTextField("20", 4);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 5;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(tfSuggestStopsLimit, layoutCons);
+            contentPane.add(tfSuggestStopsLimit);
+
+            JLabel meters = new JLabel(tr("meters"));
+
+            layoutCons.gridx = 1;
+            layoutCons.gridy = 5;
+            layoutCons.gridwidth = 1;
+            layoutCons.weightx = 0.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(meters, layoutCons);
+            contentPane.add(meters);
+
+            JButton bSuggestStops = new JButton(tr("Suggest Stops"));
+            bSuggestStops.setActionCommand("routePattern.metaSuggestStops");
+            bSuggestStops.addActionListener(this);
+
+            layoutCons.gridx = 0;
+            layoutCons.gridy = 6;
+            layoutCons.gridwidth = 3;
+            layoutCons.weightx = 1.0;
+            layoutCons.weighty = 0.0;
+            layoutCons.fill = GridBagConstraints.BOTH;
+            gridbag.setConstraints(bSuggestStops, layoutCons);
+            contentPane.add(bSuggestStops);
+
+            jDialog.pack();
+        }
+
+        if ("routePattern.refresh".equals(event.getActionCommand())) {
+            refreshData();
+        } else if ("routePattern.overviewNew".equals(event.getActionCommand())) {
+            currentRoute = new Relation();
+            currentRoute.put("type", "route");
+            currentRoute.put("route", "bus");
+            mainDataSet.addPrimitive(currentRoute);
+
+            refreshData();
+
+            for (int i = 0; i < relsListModel.size(); ++i) {
+                if (currentRoute == relsListModel.elementAt(i).route)
+                    relsList.setSelectedIndex(i);
+            }
+        } else if ("routePattern.overviewDuplicate".equals(event.getActionCommand())) {
+            currentRoute = new Relation(currentRoute, true);
+            currentRoute.put("type", "route");
+            currentRoute.put("route", "bus");
+            mainDataSet.addPrimitive(currentRoute);
+
+            refreshData();
+
+            for (int i = 0; i < relsListModel.size(); ++i) {
+                if (currentRoute == relsListModel.elementAt(i).route)
+                    relsList.setSelectedIndex(i);
+            }
+        } else if ("routePattern.overviewReflect".equals(event.getActionCommand())) {
+            currentRoute.setModified(true);
+            String tag_from = currentRoute.get("from");
+            String tag_to = currentRoute.get("to");
+            currentRoute.put("from", tag_to);
+            currentRoute.put("to", tag_from);
+
+            Vector<RelationMember> itemsToReflect = new Vector<>();
+            Vector<RelationMember> otherItems = new Vector<>();
+
+            // Temp
+            Node firstNode = null;
+            // Node lastNode = null;
+
+            for (int i = 0; i < currentRoute.getMembersCount(); ++i) {
+                RelationMember item = currentRoute.getMember(i);
+
+                if (item.isWay()) {
+                    String role = item.getRole();
+                    if ("backward".equals(role))
+                        role = "forward";
+                    else if ("forward".equals(role))
+                        role = "backward";
+                    else
+                        role = "backward";
+
+                    itemsToReflect.add(new RelationMember(role, item.getWay()));
+
+                    // Temp
+                    if (firstNode == null) {
+                        firstNode = item.getWay().getNode(0);
+                    }
+                    // lastNode = item.getWay().getNode(item.getWay().getNodesCount() - 1);
+                } else if (item.isNode())
+                    itemsToReflect.add(item);
+                else
+                    otherItems.add(item);
+            }
+
+            currentRoute.setMembers(null);
+            for (int i = itemsToReflect.size() - 1; i >= 0; --i) {
+                currentRoute.addMember(itemsToReflect.elementAt(i));
+            }
+            for (int i = 0; i < otherItems.size(); ++i) {
+                currentRoute.addMember(otherItems.elementAt(i));
+            }
+
+            refreshData();
+
+            for (int i = 0; i < relsListModel.size(); ++i) {
+                if (currentRoute == relsListModel.elementAt(i).route)
+                    relsList.setSelectedIndex(i);
+            }
+
+            // Temp
+            /*
+             * if (firstNode != null) {
+             * Vector< AStarAlgorithm.Edge > path = new PublicTransportAStar(firstNode, lastNode).shortestPath(); Iterator<
+             * AStarAlgorithm.Edge > iter = path.iterator(); while (iter.hasNext()) { PublicTransportAStar.PartialWayEdge edge =
+             * (PublicTransportAStar.PartialWayEdge)iter.next(); System.out.print(edge.way.getUniqueId()); System.out.print("\t");
+             * System.out.print(edge.beginIndex); System.out.print("\t"); System.out.print(edge.endIndex); System.out.print("\n"); } }
+             */
+        } else if ("routePattern.overviewDelete".equals(event.getActionCommand())) {
+            DeleteAction.deleteRelation(MainApplication.getLayerManager().getEditLayer(), currentRoute);
+
+            currentRoute = null;
+            tabbedPane.setEnabledAt(1, false);
+            tabbedPane.setEnabledAt(2, false);
+            tabbedPane.setEnabledAt(3, false);
+            tabbedPane.setEnabledAt(4, false);
+
+            refreshData();
+        } else if ("routePattern.tagAddTag".equals(event.getActionCommand())) {
+            Vector<String> rowContent = new Vector<>();
+            rowContent.add("");
+            rowContent.add("");
+            otherTagsData.addRow(rowContent);
+        } else if ("routePattern.itineraryFind".equals(event.getActionCommand())) {
+            if (mainDataSet == null)
+                return;
+
+            itineraryTable.clearSelection();
+
+            for (int i = 0; i < itineraryData.getRowCount(); ++i) {
+                if ((itineraryData.ways.elementAt(i) != null)
+                        && (mainDataSet.isSelected(itineraryData.ways.elementAt(i))))
+                    itineraryTable.addRowSelectionInterval(i, i);
+            }
+        } else if ("routePattern.itineraryShow".equals(event.getActionCommand())) {
+            BoundingXYVisitor box = new BoundingXYVisitor();
+            if (itineraryTable.getSelectedRowCount() > 0) {
+                for (int i = 0; i < itineraryData.getRowCount(); ++i) {
+                    if ((itineraryTable.isRowSelected(i))
+                            && (itineraryData.ways.elementAt(i) != null)) {
+                        itineraryData.ways.elementAt(i).accept(box);
+                    }
+                }
+            } else {
+                for (int i = 0; i < itineraryData.getRowCount(); ++i) {
+                    if (itineraryData.ways.elementAt(i) != null) {
+                        itineraryData.ways.elementAt(i).accept(box);
+                    }
+                }
+            }
+            if (box.getBounds() == null)
+                return;
+            box.enlargeBoundingBox();
+            MainApplication.getMap().mapView.zoomTo(box);
+        } else if ("routePattern.itineraryMark".equals(event.getActionCommand())) {
+            OsmPrimitive[] osmp = {null};
+            MainApplication.getLayerManager().getEditDataSet().setSelected(osmp);
+            markedWays.clear();
+            if (itineraryTable.getSelectedRowCount() > 0) {
+                for (int i = 0; i < itineraryData.getRowCount(); ++i) {
+                    if ((itineraryTable.isRowSelected(i))
+                            && (itineraryData.ways.elementAt(i) != null)) {
+                        mainDataSet.addSelected(itineraryData.ways.elementAt(i));
+
+                        RelationMember markedWay = new RelationMember(
+                                (String) (itineraryData.getValueAt(i, 1)),
+                                itineraryData.ways.elementAt(i));
+                        markedWays.addElement(markedWay);
+                    }
+                }
+            } else {
+                for (int i = 0; i < itineraryData.getRowCount(); ++i) {
+                    if (itineraryData.ways.elementAt(i) != null) {
+                        mainDataSet.addSelected(itineraryData.ways.elementAt(i));
+
+                        RelationMember markedWay = new RelationMember(
+                                (String) (itineraryData.getValueAt(i, 1)),
+                                itineraryData.ways.elementAt(i));
+                        markedWays.addElement(markedWay);
+                    }
+                }
+            }
+        } else if ("routePattern.itineraryAdd".equals(event.getActionCommand())) {
+            int insPos = itineraryTable.getSelectedRow();
+            Iterator<RelationMember> relIter = markedWays.iterator();
+            TreeSet<Way> addedWays = new TreeSet<>();
+            if (mainDataSet == null)
+                return;
+
+            while (relIter.hasNext()) {
+                RelationMember curMember = relIter.next();
+                if ((curMember.isWay()) && (mainDataSet.isSelected(curMember.getWay()))) {
+                    itineraryData.insertRow(insPos, curMember.getWay(), curMember.getRole());
+                    if (insPos >= 0)
+                        ++insPos;
+
+                    addedWays.add(curMember.getWay());
+                }
+            }
+
+            Collection<Way> selectedWays = mainDataSet.getSelectedWays();
+            Iterator<Way> wayIter = selectedWays.iterator();
+
+            while (wayIter.hasNext()) {
+                Way curMember = wayIter.next();
+                if (!(addedWays.contains(curMember))) {
+                    itineraryData.insertRow(insPos, curMember, "");
+                    if (insPos >= 0)
+                        ++insPos;
+                }
+            }
+
+            if ((insPos > 0) && (insPos < itineraryData.getRowCount())) {
+                while ((insPos < itineraryData.getRowCount())
+                        && (itineraryData.ways.elementAt(insPos) == null)) {
+                    ++insPos;
+                }
+                itineraryTable.removeRowSelectionInterval(0, itineraryData.getRowCount() - 1);
+                if (insPos < itineraryData.getRowCount())
+                    itineraryTable.addRowSelectionInterval(insPos, insPos);
+            }
+
+            itineraryData.cleanupGaps();
+            segmentMetrics = fillSegmentMetrics();
+            rebuildWays();
+        } else if ("routePattern.itineraryDelete".equals(event.getActionCommand())) {
+            for (int i = itineraryData.getRowCount() - 1; i >= 0; --i) {
+                if ((itineraryTable.isRowSelected(i))
+                        && (itineraryData.ways.elementAt(i) != null)) {
+                    itineraryData.ways.removeElementAt(i);
+                    itineraryData.removeRow(i);
+                }
+            }
+
+            itineraryData.cleanupGaps();
+            segmentMetrics = fillSegmentMetrics();
+            rebuildWays();
+        } else if ("routePattern.itinerarySort".equals(event.getActionCommand())) {
+            TreeSet<Way> usedWays = new TreeSet<>();
+            TreeMap<Node, LinkedList<RelationMember>> frontNodes = new TreeMap<>();
+            TreeMap<Node, LinkedList<RelationMember>> backNodes = new TreeMap<>();
+            Vector<LinkedList<RelationMember>> loops = new Vector<>();
+            int insPos = itineraryTable.getSelectedRow();
+
+            if (itineraryTable.getSelectedRowCount() > 0) {
+                for (int i = itineraryData.getRowCount() - 1; i >= 0; --i) {
+                    if ((itineraryTable.isRowSelected(i))
+                            && (itineraryData.ways.elementAt(i) != null)) {
+                        if (!(usedWays.contains(itineraryData.ways.elementAt(i)))) {
+                            addWayToSortingData(itineraryData.ways.elementAt(i), frontNodes,
+                                    backNodes, loops);
+                            usedWays.add(itineraryData.ways.elementAt(i));
+                        }
+
+                        itineraryData.ways.removeElementAt(i);
+                        itineraryData.removeRow(i);
+                    }
+                }
+            } else {
+                for (int i = itineraryData.getRowCount() - 1; i >= 0; --i) {
+                    if (itineraryData.ways.elementAt(i) != null) {
+                        if (!(usedWays.contains(itineraryData.ways.elementAt(i)))) {
+                            addWayToSortingData(itineraryData.ways.elementAt(i), frontNodes,
+                                    backNodes, loops);
+                            usedWays.add(itineraryData.ways.elementAt(i));
+                        }
+                    }
+                }
+
+                itineraryData.clear();
+            }
+
+            Iterator<Map.Entry<Node, LinkedList<RelationMember>>> entryIter = frontNodes.entrySet()
+                    .iterator();
+            while (entryIter.hasNext()) {
+                Iterator<RelationMember> relIter = entryIter.next().getValue().iterator();
+                while (relIter.hasNext()) {
+                    RelationMember curMember = relIter.next();
+                    itineraryData.insertRow(insPos, curMember.getWay(), curMember.getRole());
+                    if (insPos >= 0)
+                        ++insPos;
+                }
+            }
+
+            Iterator<LinkedList<RelationMember>> listIter = loops.iterator();
+            while (listIter.hasNext()) {
+                Iterator<RelationMember> relIter = listIter.next().iterator();
+                while (relIter.hasNext()) {
+                    RelationMember curMember = relIter.next();
+                    itineraryData.insertRow(insPos, curMember.getWay(), curMember.getRole());
+                    if (insPos >= 0)
+                        ++insPos;
+                }
+            }
+
+            itineraryData.cleanupGaps();
+            segmentMetrics = fillSegmentMetrics();
+            rebuildWays();
+        } else if ("routePattern.itineraryReflect".equals(event.getActionCommand())) {
+            Vector<RelationMember> itemsToReflect = new Vector<>();
+            int insPos = itineraryTable.getSelectedRow();
+
+            if (itineraryTable.getSelectedRowCount() > 0) {
+                for (int i = itineraryData.getRowCount() - 1; i >= 0; --i) {
+                    if ((itineraryTable.isRowSelected(i))
+                            && (itineraryData.ways.elementAt(i) != null)) {
+                        String role = (String) (itineraryData.getValueAt(i, 1));
+                        if ("backward".equals(role))
+                            role = "forward";
+                        else if ("forward".equals(role))
+                            role = "backward";
+                        else
+                            role = "backward";
+                        RelationMember markedWay = new RelationMember(role,
+                                itineraryData.ways.elementAt(i));
+                        itemsToReflect.addElement(markedWay);
+
+                        itineraryData.ways.removeElementAt(i);
+                        itineraryData.removeRow(i);
+                    }
+                }
+            } else {
+                for (int i = itineraryData.getRowCount() - 1; i >= 0; --i) {
+                    if (itineraryData.ways.elementAt(i) != null) {
+                        String role = (String) (itineraryData.getValueAt(i, 1));
+                        if ("backward".equals(role))
+                            role = "forward";
+                        else if ("forward".equals(role))
+                            role = "backward";
+                        else
+                            role = "backward";
+                        RelationMember markedWay = new RelationMember(role,
+                                itineraryData.ways.elementAt(i));
+                        itemsToReflect.addElement(markedWay);
+                    }
+                }
+
+                itineraryData.clear();
+            }
+
+            int startPos = insPos;
+            Iterator<RelationMember> relIter = itemsToReflect.iterator();
+            while (relIter.hasNext()) {
+                RelationMember curMember = relIter.next();
+                if (curMember.isWay()) {
+                    itineraryData.insertRow(insPos, curMember.getWay(), curMember.getRole());
+                    if (insPos >= 0)
+                        ++insPos;
+                }
+            }
+            if (insPos >= 0)
+                itineraryTable.addRowSelectionInterval(startPos, insPos - 1);
+
+            itineraryData.cleanupGaps();
+            segmentMetrics = fillSegmentMetrics();
+            rebuildWays();
+        } else if ("routePattern.stoplistFind".equals(event.getActionCommand())) {
+            if (mainDataSet == null)
+                return;
+
+            stoplistTable.clearSelection();
+
+            for (int i = 0; i < stoplistData.getRowCount(); ++i) {
+                if ((stoplistData.nodes.elementAt(i) != null)
+                        && (mainDataSet.isSelected(stoplistData.nodes.elementAt(i))))
+                    stoplistTable.addRowSelectionInterval(i, i);
+            }
+        } else if ("routePattern.stoplistShow".equals(event.getActionCommand())) {
+            BoundingXYVisitor box = new BoundingXYVisitor();
+            if (stoplistTable.getSelectedRowCount() > 0) {
+                for (int i = 0; i < stoplistData.getRowCount(); ++i) {
+                    if (stoplistTable.isRowSelected(i)) {
+                        stoplistData.nodes.elementAt(i).accept(box);
+                    }
+                }
+            } else {
+                for (int i = 0; i < stoplistData.getRowCount(); ++i) {
+                    stoplistData.nodes.elementAt(i).accept(box);
+                }
+            }
+            if (box.getBounds() == null)
+                return;
+            box.enlargeBoundingBox();
+            MainApplication.getMap().mapView.zoomTo(box);
+        } else if ("routePattern.stoplistMark".equals(event.getActionCommand())) {
+            OsmPrimitive[] osmp = {null};
+            MainApplication.getLayerManager().getEditDataSet().setSelected(osmp);
+            markedNodes.clear();
+            if (stoplistTable.getSelectedRowCount() > 0) {
+                for (int i = 0; i < stoplistData.getRowCount(); ++i) {
+                    if (stoplistTable.isRowSelected(i)) {
+                        mainDataSet.addSelected(stoplistData.nodes.elementAt(i));
+
+                        RelationMember markedNode = new RelationMember(
+                                (String) (stoplistData.getValueAt(i, 1)),
+                                stoplistData.nodes.elementAt(i));
+                        markedNodes.addElement(markedNode);
+                    }
+                }
+            } else {
+                for (int i = 0; i < stoplistData.getRowCount(); ++i) {
+                    mainDataSet.addSelected(stoplistData.nodes.elementAt(i));
+
+                    RelationMember markedNode = new RelationMember(
+                            (String) (stoplistData.getValueAt(i, 1)),
+                            stoplistData.nodes.elementAt(i));
+                    markedNodes.addElement(markedNode);
+                }
+            }
+        } else if ("routePattern.stoplistAdd".equals(event.getActionCommand())) {
+            int insPos = stoplistTable.getSelectedRow();
+            Iterator<RelationMember> relIter = markedNodes.iterator();
+            TreeSet<Node> addedNodes = new TreeSet<>();
+            if (mainDataSet == null)
+                return;
+
+            while (relIter.hasNext()) {
+                RelationMember curMember = relIter.next();
+                if ((curMember.isNode()) && (mainDataSet.isSelected(curMember.getNode()))) {
+                    StopReference sr = detectMinDistance(curMember.getNode(), segmentMetrics,
+                            cbRight.isSelected(), cbLeft.isSelected());
+                    stoplistData.insertRow(insPos, curMember.getNode(), curMember.getRole(),
+                            calcOffset(sr, segmentMetrics));
+                    if (insPos >= 0)
+                        ++insPos;
+
+                    addedNodes.add(curMember.getNode());
+                }
+            }
+
+            Collection<Node> selectedNodes = mainDataSet.getSelectedNodes();
+            Iterator<Node> nodeIter = selectedNodes.iterator();
+
+            while (nodeIter.hasNext()) {
+                Node curMember = nodeIter.next();
+                if (!(addedNodes.contains(curMember))) {
+                    StopReference sr = detectMinDistance(curMember, segmentMetrics,
+                            cbRight.isSelected(), cbLeft.isSelected());
+                    stoplistData.insertRow(insPos, curMember, "", calcOffset(sr, segmentMetrics));
+                    if (insPos >= 0)
+                        ++insPos;
+                }
+            }
+
+            if ((insPos > 0) && (insPos < stoplistData.getRowCount())) {
+                while ((insPos < stoplistData.getRowCount())
+                        && (stoplistData.nodes.elementAt(insPos) == null)) {
+                    ++insPos;
+                }
+                stoplistTable.removeRowSelectionInterval(0, stoplistData.getRowCount() - 1);
+                if (insPos < stoplistData.getRowCount())
+                    stoplistTable.addRowSelectionInterval(insPos, insPos);
+            }
+
+            rebuildNodes();
+        } else if ("routePattern.stoplistDelete".equals(event.getActionCommand())) {
+            for (int i = stoplistData.getRowCount() - 1; i >= 0; --i) {
+                if (stoplistTable.isRowSelected(i)) {
+                    stoplistData.nodes.removeElementAt(i);
+                    stoplistData.removeRow(i);
+                }
+            }
+
+            rebuildNodes();
+        } else if ("routePattern.stoplistSort".equals(event.getActionCommand())) {
+            // Prepare Segments: The segments of all usable ways are arranged in a linear
+            // list such that a coor can directly be checked concerning position and offset
+            Vector<StopReference> srm = new Vector<>();
+            int insPos = stoplistTable.getSelectedRow();
+            if (stoplistTable.getSelectedRowCount() > 0) {
+                // Determine for each member its position on the itinerary: position means here the
+                // point on the itinerary that has minimal distance to the coor
+                for (int i = stoplistData.getRowCount() - 1; i >= 0; --i) {
+                    if (stoplistTable.isRowSelected(i)) {
+                        StopReference sr = detectMinDistance(stoplistData.nodes.elementAt(i),
+                                segmentMetrics, cbRight.isSelected(), cbLeft.isSelected());
+                        if (sr != null) {
+                            if (sr.distance < Double.parseDouble(tfSuggestStopsLimit.getText())
+                                    * 9.0 / 1000000.0) {
+                                sr.role = (String) stoplistData.getValueAt(i, STOPLIST_ROLE_COLUMN);
+                                srm.addElement(sr);
+                            } else {
+                                sr.role = (String) stoplistData.getValueAt(i, STOPLIST_ROLE_COLUMN);
+                                sr.index = segmentMetrics.size() * 2;
+                                sr.pos = 0;
+                                srm.addElement(sr);
+                            }
+
+                            stoplistData.nodes.removeElementAt(i);
+                            stoplistData.removeRow(i);
+                        }
+                    }
+                }
+            } else {
+                // Determine for each member its position on the itinerary: position means here the
+                // point on the itinerary that has minimal distance to the coor
+                for (int i = stoplistData.getRowCount() - 1; i >= 0; --i) {
+                    StopReference sr = detectMinDistance(stoplistData.nodes.elementAt(i),
+                            segmentMetrics, cbRight.isSelected(), cbLeft.isSelected());
+                    if (sr != null) {
+                        if (sr.distance < Double.parseDouble(tfSuggestStopsLimit.getText()) * 9.0
+                                / 1000000.0) {
+                            sr.role = (String) stoplistData.getValueAt(i, STOPLIST_ROLE_COLUMN);
+                            srm.addElement(sr);
+                        } else {
+                            sr.role = (String) stoplistData.getValueAt(i, STOPLIST_ROLE_COLUMN);
+                            sr.index = segmentMetrics.size() * 2;
+                            sr.pos = 0;
+                            srm.addElement(sr);
+                        }
+                    }
+                }
+
+                stoplistData.clear();
+            }
+
+            Collections.sort(srm);
+
+            for (int i = 0; i < srm.size(); ++i) {
+                StopReference sr = detectMinDistance(srm.elementAt(i).node, segmentMetrics,
+                        cbRight.isSelected(), cbLeft.isSelected());
+                stoplistData.insertRow(insPos, srm.elementAt(i).node, srm.elementAt(i).role,
+                        calcOffset(sr, segmentMetrics));
+                if (insPos >= 0)
+                    ++insPos;
+            }
+
+            rebuildNodes();
+        } else if ("routePattern.stoplistReflect".equals(event.getActionCommand())) {
+            Vector<RelationMember> itemsToReflect = new Vector<>();
+            int insPos = stoplistTable.getSelectedRow();
+
+            if (stoplistTable.getSelectedRowCount() > 0) {
+                for (int i = stoplistData.getRowCount() - 1; i >= 0; --i) {
+                    if (stoplistTable.isRowSelected(i)) {
+                        String role = (String) (stoplistData.getValueAt(i, STOPLIST_ROLE_COLUMN));
+                        RelationMember markedNode = new RelationMember(role,
+                                stoplistData.nodes.elementAt(i));
+                        itemsToReflect.addElement(markedNode);
+
+                        stoplistData.nodes.removeElementAt(i);
+                        stoplistData.removeRow(i);
+                    }
+                }
+            } else {
+                for (int i = stoplistData.getRowCount() - 1; i >= 0; --i) {
+                    String role = (String) (stoplistData.getValueAt(i, STOPLIST_ROLE_COLUMN));
+                    RelationMember markedNode = new RelationMember(role,
+                            stoplistData.nodes.elementAt(i));
+                    itemsToReflect.addElement(markedNode);
+                }
+
+                stoplistData.clear();
+            }
+
+            int startPos = insPos;
+            Iterator<RelationMember> relIter = itemsToReflect.iterator();
+            while (relIter.hasNext()) {
+                RelationMember curMember = relIter.next();
+                if (curMember.isNode()) {
+                    StopReference sr = detectMinDistance(curMember.getNode(), segmentMetrics,
+                            cbRight.isSelected(), cbLeft.isSelected());
+                    stoplistData.insertRow(insPos, curMember.getNode(), curMember.getRole(),
+                            calcOffset(sr, segmentMetrics));
+                    if (insPos >= 0)
+                        ++insPos;
+                }
+            }
+            if (insPos >= 0)
+                stoplistTable.addRowSelectionInterval(startPos, insPos - 1);
+
+            rebuildNodes();
+        } else if ("routePattern.metaSuggestStops".equals(event.getActionCommand())) {
+            // Prepare Segments: The segments of all usable ways are arranged in a linear
+            // list such that a coor can directly be checked concerning position and offset
+            Vector<StopReference> srm = new Vector<>();
+            // Determine for each member its position on the itinerary: position means here the
+            // point on the itinerary that has minimal distance to the coor
+            mainDataSet = MainApplication.getLayerManager().getEditDataSet();
+            if (mainDataSet != null) {
+                String stopKey = "";
+                String stopValue = "";
+                if ("bus".equals(currentRoute.get("route"))) {
+                    stopKey = "highway";
+                    stopValue = "bus_stop";
+                } else if ("trolleybus".equals(currentRoute.get("route"))) {
+                    stopKey = "highway";
+                    stopValue = "bus_stop";
+                } else if ("tram".equals(currentRoute.get("route"))) {
+                    stopKey = "railway";
+                    stopValue = "tram_stop";
+                } else if ("light_rail".equals(currentRoute.get("route"))) {
+                    stopKey = "railway";
+                    stopValue = "station";
+                } else if ("subway".equals(currentRoute.get("route"))) {
+                    stopKey = "railway";
+                    stopValue = "station";
+                } else if ("rail".equals(currentRoute.get("route"))) {
+                    stopKey = "railway";
+                    stopValue = "station";
+                }
+
+                Collection<Node> nodeCollection = mainDataSet.getNodes();
+                Iterator<Node> nodeIter = nodeCollection.iterator();
+                while (nodeIter.hasNext()) {
+                    Node currentNode = nodeIter.next();
+                    if (!currentNode.isUsable())
+                        continue;
+                    if (stopValue.equals(currentNode.get(stopKey))) {
+                        StopReference sr = detectMinDistance(currentNode, segmentMetrics,
+                                cbRight.isSelected(), cbLeft.isSelected());
+                        if ((sr != null)
+                                && (sr.distance < Double.parseDouble(tfSuggestStopsLimit.getText())
+                                        * 9.0 / 1000000.0))
+                            srm.addElement(sr);
+                    }
+                }
+            } else {
+                JOptionPane.showMessageDialog(null,
+                        tr("There exists no dataset."
+                                + " Try to download data from the server or open an OSM file."),
+                        tr("No data found"), JOptionPane.ERROR_MESSAGE);
+            }
+
+            Collections.sort(srm);
+
+            stoplistData.clear();
+            for (int i = 0; i < srm.size(); ++i) {
+                StopReference sr = detectMinDistance(srm.elementAt(i).node, segmentMetrics,
+                        cbRight.isSelected(), cbLeft.isSelected());
+                stoplistData.addRow(srm.elementAt(i).node, srm.elementAt(i).role,
+                        calcOffset(sr, segmentMetrics));
+            }
+
+            rebuildNodes();
+        } else {
+            refreshData();
+
+            jDialog.setLocationRelativeTo(frame);
+            jDialog.setVisible(true);
+        }
+    }
+
+    private void refreshData() {
+        Relation copy = currentRoute;
+        relsListModel.clear();
+        currentRoute = copy;
+
+        DataSet mainDataSet = MainApplication.getLayerManager().getEditDataSet();
+        if (mainDataSet != null) {
+            Vector<RouteReference> relRefs = new Vector<>();
+            Collection<Relation> relCollection = mainDataSet.getRelations();
+            Iterator<Relation> relIter = relCollection.iterator();
+
+            while (relIter.hasNext()) {
+                Relation currentRel = relIter.next();
+                if (!currentRel.isDeleted()) {
+                    String routeVal = currentRel.get("route");
+                    if ("bus".equals(routeVal))
+                        relRefs.add(new RouteReference(currentRel));
+                    else if ("trolleybus".equals(routeVal))
+                        relRefs.add(new RouteReference(currentRel));
+                    else if ("tram".equals(routeVal))
+                        relRefs.add(new RouteReference(currentRel));
+                    else if ("light_rail".equals(routeVal))
+                        relRefs.add(new RouteReference(currentRel));
+                    else if ("subway".equals(routeVal))
+                        relRefs.add(new RouteReference(currentRel));
+                    else if ("rail".equals(routeVal))
+                        relRefs.add(new RouteReference(currentRel));
+                }
+            }
+
+            Collections.sort(relRefs);
+
+            Iterator<RouteReference> iter = relRefs.iterator();
+            while (iter.hasNext()) {
+                relsListModel.addElement(iter.next());
+            }
+        } else {
+            JOptionPane.showMessageDialog(null,
+                    tr("There exists no dataset."
+                            + " Try to download data from the server or open an OSM file."),
+                    tr("No data found"), JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+    // Rebuild ways in the relation currentRoute
+    public static void rebuildWays() {
+        currentRoute.setModified(true);
+        List<RelationMember> members = currentRoute.getMembers();
+        ListIterator<RelationMember> iter = members.listIterator();
+        while (iter.hasNext()) {
+            if (iter.next().isWay())
+                iter.remove();
+        }
+        for (int i = 0; i < itineraryData.getRowCount(); ++i) {
+            if (itineraryData.ways.elementAt(i) != null) {
+                RelationMember member = new RelationMember(
+                        (String) (itineraryData.getValueAt(i, 1)), itineraryData.ways.elementAt(i));
+                members.add(member);
+            }
+        }
+        currentRoute.setMembers(members);
+    }
+
+    // Rebuild nodes in the relation currentRoute
+    private void rebuildNodes() {
+        currentRoute.setModified(true);
+        for (int i = currentRoute.getMembersCount() - 1; i >= 0; --i) {
+            if (currentRoute.getMember(i).isNode()) {
+                currentRoute.removeMember(i);
+            }
+        }
+        for (int i = 0; i < stoplistData.getRowCount(); ++i) {
+            RelationMember member = new RelationMember(
+                    (String) (stoplistData.getValueAt(i, STOPLIST_ROLE_COLUMN)),
+                    stoplistData.nodes.elementAt(i));
+            currentRoute.addMember(member);
+        }
+    }
+
+    private void addWayToSortingData(Way way, TreeMap<Node, LinkedList<RelationMember>> frontNodes,
+            TreeMap<Node, LinkedList<RelationMember>> backNodes,
+            Vector<LinkedList<RelationMember>> loops) {
+        if (way.getNodesCount() < 1)
+            return;
+
+        Node firstNode = way.getNode(0);
+        Node lastNode = way.getNode(way.getNodesCount() - 1);
+
+        if (frontNodes.get(firstNode) != null) {
+            LinkedList<RelationMember> list = frontNodes.get(firstNode);
+            list.addFirst(new RelationMember("backward", way));
+            frontNodes.remove(firstNode);
+
+            Node lastListNode;
+            if ("backward".equals(list.getLast().getRole()))
+                lastListNode = list.getLast().getWay().getNode(0);
+            else
+                lastListNode = list.getLast().getWay()
+                        .getNode(list.getLast().getWay().getNodesCount() - 1);
+            if (lastNode.equals(lastListNode)) {
+                backNodes.remove(lastListNode);
+                loops.add(list);
+            } else if (frontNodes.get(lastNode) != null) {
+                backNodes.remove(lastListNode);
+                LinkedList<RelationMember> listToAppend = frontNodes.get(lastNode);
+                Iterator<RelationMember> memberIter = list.iterator();
+                while (memberIter.hasNext()) {
+                    RelationMember member = memberIter.next();
+                    if ("backward".equals(member.getRole()))
+                        listToAppend.addFirst(new RelationMember("forward", member.getWay()));
+                    else
+                        listToAppend.addFirst(new RelationMember("backward", member.getWay()));
+                }
+                frontNodes.remove(lastNode);
+                frontNodes.put(lastListNode, listToAppend);
+            } else if (backNodes.get(lastNode) != null) {
+                backNodes.remove(lastListNode);
+                LinkedList<RelationMember> listToAppend = backNodes.get(lastNode);
+                Iterator<RelationMember> memberIter = list.iterator();
+                while (memberIter.hasNext()) {
+                    RelationMember member = memberIter.next();
+                    listToAppend.addLast(member);
+                }
+                backNodes.remove(lastNode);
+                backNodes.put(lastListNode, listToAppend);
+            } else
+                frontNodes.put(lastNode, list);
+        } else if (backNodes.get(firstNode) != null) {
+            LinkedList<RelationMember> list = backNodes.get(firstNode);
+            list.addLast(new RelationMember("forward", way));
+            backNodes.remove(firstNode);
+
+            Node firstListNode;
+            if ("backward".equals(list.getFirst().getRole()))
+                firstListNode = list.getFirst().getWay()
+                        .getNode(list.getFirst().getWay().getNodesCount() - 1);
+            else
+                firstListNode = list.getFirst().getWay().getNode(0);
+            if (lastNode.equals(firstListNode)) {
+                frontNodes.remove(firstListNode);
+                loops.add(list);
+            } else if (frontNodes.get(lastNode) != null) {
+                frontNodes.remove(firstListNode);
+                LinkedList<RelationMember> listToAppend = frontNodes.get(lastNode);
+                ListIterator<RelationMember> memberIter = list.listIterator(list.size());
+                while (memberIter.hasPrevious()) {
+                    RelationMember member = memberIter.previous();
+                    listToAppend.addFirst(member);
+                }
+                frontNodes.remove(lastNode);
+                frontNodes.put(firstListNode, listToAppend);
+            } else if (backNodes.get(lastNode) != null) {
+                frontNodes.remove(firstListNode);
+                LinkedList<RelationMember> listToAppend = backNodes.get(lastNode);
+                ListIterator<RelationMember> memberIter = list.listIterator(list.size());
+                while (memberIter.hasPrevious()) {
+                    RelationMember member = memberIter.previous();
+                    if ("backward".equals(member.getRole()))
+                        listToAppend.addLast(new RelationMember("forward", member.getWay()));
+                    else
+                        listToAppend.addLast(new RelationMember("backward", member.getWay()));
+                }
+                backNodes.remove(lastNode);
+                backNodes.put(firstListNode, listToAppend);
+            } else
+                backNodes.put(lastNode, list);
+        } else if (frontNodes.get(lastNode) != null) {
+            LinkedList<RelationMember> list = frontNodes.get(lastNode);
+            list.addFirst(new RelationMember("forward", way));
+            frontNodes.remove(lastNode);
+            frontNodes.put(firstNode, list);
+        } else if (backNodes.get(lastNode) != null) {
+            LinkedList<RelationMember> list = backNodes.get(lastNode);
+            list.addLast(new RelationMember("backward", way));
+            backNodes.remove(lastNode);
+            backNodes.put(firstNode, list);
+        } else {
+            LinkedList<RelationMember> newList = new LinkedList<>();
+            newList.add(new RelationMember("forward", way));
+            frontNodes.put(firstNode, newList);
+            backNodes.put(lastNode, newList);
+        }
+    }
+
+    private void routesSelectionChanged() {
+        int selectedPos = relsList.getAnchorSelectionIndex();
+        if (relsList.isSelectedIndex(selectedPos)) {
+            currentRoute = relsListModel.elementAt(selectedPos).route;
+            tabbedPane.setEnabledAt(1, true);
+            tabbedPane.setEnabledAt(2, true);
+            tabbedPane.setEnabledAt(3, true);
+            tabbedPane.setEnabledAt(4, true);
+
+            // Prepare Tags
+            requiredTagsData.readRelation(currentRoute);
+            commonTagsData.readRelation(currentRoute);
+            otherTagsData.readRelation(currentRoute, tagBlacklist);
+
+            // Prepare Itinerary
+            itineraryData.clear();
+            List<RelationMember> relMembers = currentRoute.getMembers();
+            Iterator<RelationMember> relIter = relMembers.iterator();
+            fillItineraryTable(relIter, 0, -1);
+
+            // Prepare Stoplist
+            stoplistData.clear();
+            /* List<RelationMember> */ relMembers = currentRoute.getMembers();
+            /* Iterator<RelationMember> */ relIter = relMembers.iterator();
+            fillStoplistTable(relIter, -1);
+        } else {
+            currentRoute = null;
+            tabbedPane.setEnabledAt(1, false);
+            tabbedPane.setEnabledAt(2, false);
+            tabbedPane.setEnabledAt(3, false);
+            tabbedPane.setEnabledAt(4, false);
+        }
+    }
+
+    private void fillItineraryTable(Iterator<RelationMember> relIter, long lastNodeId, int insPos) {
+        while (relIter.hasNext()) {
+            RelationMember curMember = relIter.next();
+            if (curMember.isWay()) {
+                itineraryData.insertRow(insPos, curMember.getWay(), curMember.getRole());
+                if (insPos >= 0)
+                    ++insPos;
+            }
+        }
+        itineraryData.cleanupGaps();
+        segmentMetrics = fillSegmentMetrics();
+    }
+
+    private double calcOffset(StopReference sr, Vector<SegmentMetric> segmentMetrics) {
+        double offset = 0;
+        if ((sr.index + 1) / 2 < segmentMetrics.size()) {
+            offset = segmentMetrics.elementAt((sr.index + 1) / 2).distance;
+            if (sr.index % 2 == 0)
+                offset += sr.pos;
+        } else
+            offset = segmentMetrics.elementAt(segmentMetrics.size() - 1).distance
+                    + segmentMetrics.elementAt(segmentMetrics.size() - 1).length;
+
+        return offset;
+    }
+
+    private void fillStoplistTable(Iterator<RelationMember> relIter, int insPos) {
+
+        while (relIter.hasNext()) {
+            RelationMember curMember = relIter.next();
+            if (curMember.isNode()) {
+                StopReference sr = detectMinDistance(curMember.getNode(), segmentMetrics,
+                        cbRight.isSelected(), cbLeft.isSelected());
+                if (sr == null)
+                    stoplistData.insertRow(insPos, curMember.getNode(), curMember.getRole(), 360.0);
+                else {
+                    stoplistData.insertRow(insPos, curMember.getNode(), curMember.getRole(),
+                            calcOffset(sr, segmentMetrics));
+                    if (insPos >= 0)
+                        ++insPos;
+                }
+            }
+        }
+    }
+
+    private Vector<SegmentMetric> fillSegmentMetrics() {
+        Vector<SegmentMetric> segmentMetrics = new Vector<>();
+        double distance = 0;
+        for (int i = 0; i < itineraryData.getRowCount(); ++i) {
+            if (itineraryData.ways.elementAt(i) != null) {
+                Way way = itineraryData.ways.elementAt(i);
+                if (!(way.isIncomplete())) {
+                    if ("backward".equals((itineraryData.getValueAt(i, 1)))) {
+                        for (int j = way.getNodesCount() - 2; j >= 0; --j) {
+                            SegmentMetric sm = new SegmentMetric(way.getNode(j + 1).getCoor().lat(),
+                                    way.getNode(j + 1).getCoor().lon(),
+                                    way.getNode(j).getCoor().lat(), way.getNode(j).getCoor().lon(),
+                                    distance);
+                            segmentMetrics.add(sm);
+                            distance += sm.length;
+                        }
+                    } else {
+                        for (int j = 0; j < way.getNodesCount() - 1; ++j) {
+                            SegmentMetric sm = new SegmentMetric(way.getNode(j).getCoor().lat(),
+                                    way.getNode(j).getCoor().lon(),
+                                    way.getNode(j + 1).getCoor().lat(),
+                                    way.getNode(j + 1).getCoor().lon(), distance);
+                            segmentMetrics.add(sm);
+                            distance += sm.length;
+                        }
+                    }
+                }
+            } else
+                segmentMetrics.add(null);
+        }
+        return segmentMetrics;
+    }
+
+    private StopReference detectMinDistance(Node node, Vector<SegmentMetric> segmentMetrics,
+            boolean rhsPossible, boolean lhsPossible) {
+        if (node == null || node.getCoor() == null)
+            return null;
+
+        int minIndex = -1;
+        double position = -1.0;
+        double distance = 180.0;
+        double lat = node.getCoor().lat();
+        double lon = node.getCoor().lon();
+
+        int curIndex = -2;
+        double angleLat = 100.0;
+        double angleLon = 200.0;
+        Iterator<SegmentMetric> iter = segmentMetrics.iterator();
+        while (iter.hasNext()) {
+            curIndex += 2;
+            SegmentMetric sm = iter.next();
+
+            if (sm == null) {
+                angleLat = 100.0;
+                angleLon = 200.0;
+
+                continue;
+            }
+
+            double curPosition = (lat - sm.aLat) * sm.d1 + (lon - sm.aLon) * sm.d2;
+
+            if (curPosition < 0) {
+                if (angleLat <= 90.0) {
+                    double lastSegAngle = Math.atan2(angleLat - sm.aLat, angleLon - sm.aLon);
+                    double segAngle = Math.atan2(sm.d1, -sm.o1);
+                    double vertexAngle = Math.atan2(lat - sm.aLat, lon - sm.aLon);
+
+                    boolean vertexOnSeg = (vertexAngle == segAngle)
+                            || (vertexAngle == lastSegAngle);
+                    boolean vertexOnTheLeft = (!vertexOnSeg)
+                            && (((lastSegAngle > vertexAngle) && (vertexAngle > segAngle))
+                                    || ((vertexAngle > segAngle) && (segAngle > lastSegAngle))
+                                    || ((segAngle > lastSegAngle) && (lastSegAngle > vertexAngle)));
+
+                    double currentDistance = Math
+                            .sqrt((lat - sm.aLat) * (lat - sm.aLat) + (lon - sm.aLon)
+                                    * (lon - sm.aLon) * Math.cos(sm.aLat * Math.PI / 180.0)
+                                    * Math.cos(sm.aLat * Math.PI / 180.0));
+                    curPosition = vertexAngle - segAngle;
+                    if (vertexOnTheLeft)
+                        curPosition = -curPosition;
+                    if (curPosition < 0)
+                        curPosition += 2 * Math.PI;
+                    if ((Math.abs(currentDistance) < distance)
+                            && (((!vertexOnTheLeft) && (rhsPossible))
+                                    || ((vertexOnTheLeft) && (lhsPossible)) || (vertexOnSeg))) {
+                        distance = Math.abs(currentDistance);
+                        minIndex = curIndex - 1;
+                        position = curPosition;
+                    }
+                }
+                angleLat = 100.0;
+                angleLon = 200.0;
+            } else if (curPosition > sm.length) {
+                angleLat = sm.aLat;
+                angleLon = sm.aLon;
+            } else {
+                double currentDistance = (lat - sm.aLat) * sm.o1 + (lon - sm.aLon) * sm.o2;
+                if ((Math.abs(currentDistance) < distance)
+                        && (((currentDistance >= 0) && (rhsPossible))
+                                || ((currentDistance <= 0) && (lhsPossible)))) {
+                    distance = Math.abs(currentDistance);
+                    minIndex = curIndex;
+                    position = curPosition;
+                }
+
+                angleLat = 100.0;
+                angleLon = 200.0;
+            }
+        }
+
+        if (minIndex == -1)
+            return new StopReference(segmentMetrics.size() * 2, 0, 180.0, node.get("name"), "",
+                    node);
+
+        return new StopReference(minIndex, position, distance, node.get("name"), "", node);
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/StopImporterAction.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/StopImporterAction.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/actions/StopImporterAction.java	(revision 33765)
@@ -0,0 +1,503 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DecimalFormat;
+import java.text.Format;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.DefaultListModel;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JTable;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.io.GpxReader;
+import org.openstreetmap.josm.plugins.public_transport.commands.SettingsStoptypeCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.TrackStoplistAddCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.TrackStoplistDeleteCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.TrackStoplistDetachCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.TrackStoplistRelocateCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.TrackStoplistSortCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.TrackSuggestStopsCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.WaypointsDetachCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.WaypointsDisableCommand;
+import org.openstreetmap.josm.plugins.public_transport.commands.WaypointsEnableCommand;
+import org.openstreetmap.josm.plugins.public_transport.dialogs.StopImporterDialog;
+import org.openstreetmap.josm.plugins.public_transport.models.WaypointTableModel;
+import org.openstreetmap.josm.plugins.public_transport.refs.TrackReference;
+import org.xml.sax.SAXException;
+
+public class StopImporterAction extends JosmAction {
+    private static StopImporterDialog dialog = null;
+
+    private static DefaultListModel<TrackReference> tracksListModel = null;
+
+    private static GpxData data = null;
+
+    private static TrackReference currentTrack = null;
+
+    private static WaypointTableModel waypointTM = null;
+
+    public boolean inEvent = false;
+
+    /**
+     * Constructs a new {@code StopImporterAction}.
+     */
+    public StopImporterAction() {
+        super(tr("Create Stops from GPX ..."), null, tr("Create Stops from a GPX file"), null,
+                false);
+        putValue("toolbar", "publictransport/stopimporter");
+        MainApplication.getToolbar().register(this);
+    }
+
+    public WaypointTableModel getWaypointTableModel() {
+        return waypointTM;
+    }
+
+    public StopImporterDialog getDialog() {
+        return dialog;
+    }
+
+    public DefaultListModel<TrackReference> getTracksListModel() {
+        if (tracksListModel == null)
+            tracksListModel = new DefaultListModel<>();
+        return tracksListModel;
+    }
+
+    public TrackReference getCurrentTrack() {
+        return currentTrack;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent event) {
+        if (dialog == null)
+            dialog = new StopImporterDialog(this);
+
+        dialog.setVisible(true);
+
+        if (tr("Create Stops from GPX ...").equals(event.getActionCommand())) {
+            String curDir = Main.pref.get("lastDirectory");
+            if (curDir.equals("")) {
+                curDir = ".";
+            }
+            JFileChooser fc = new JFileChooser(new File(curDir));
+            fc.setDialogTitle(tr("Select GPX file"));
+            fc.setMultiSelectionEnabled(false);
+
+            int answer = fc.showOpenDialog(Main.parent);
+            if (answer != JFileChooser.APPROVE_OPTION)
+                return;
+
+            if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir))
+                Main.pref.put("lastDirectory", fc.getCurrentDirectory().getAbsolutePath());
+
+            importData(fc.getSelectedFile());
+
+            refreshData();
+        } else if ("stopImporter.settingsGPSTimeStart".equals(event.getActionCommand())) {
+            if ((!inEvent) && (dialog.gpsTimeStartValid()) && (currentTrack != null))
+                Main.main.undoRedo.add(new TrackStoplistRelocateCommand(this));
+        } else if ("stopImporter.settingsStopwatchStart".equals(event.getActionCommand())) {
+            if ((!inEvent) && (dialog.stopwatchStartValid()) && (currentTrack != null))
+                Main.main.undoRedo.add(new TrackStoplistRelocateCommand(this));
+        } else if ("stopImporter.settingsTimeWindow".equals(event.getActionCommand())) {
+            if (currentTrack != null)
+                currentTrack.timeWindow = dialog.getTimeWindow();
+        } else if ("stopImporter.settingsThreshold".equals(event.getActionCommand())) {
+            if (currentTrack != null)
+                currentTrack.threshold = dialog.getThreshold();
+        } else if ("stopImporter.settingsSuggestStops".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new TrackSuggestStopsCommand(this));
+        else if ("stopImporter.stoplistFind".equals(event.getActionCommand()))
+            findNodesInTable(dialog.getStoplistTable(), currentTrack.stoplistTM.getNodes());
+        else if ("stopImporter.stoplistShow".equals(event.getActionCommand()))
+            showNodesFromTable(dialog.getStoplistTable(), currentTrack.stoplistTM.getNodes());
+        else if ("stopImporter.stoplistMark".equals(event.getActionCommand()))
+            markNodesFromTable(dialog.getStoplistTable(), currentTrack.stoplistTM.getNodes());
+        else if ("stopImporter.stoplistDetach".equals(event.getActionCommand())) {
+            Main.main.undoRedo.add(new TrackStoplistDetachCommand(this));
+            dialog.getStoplistTable().clearSelection();
+        } else if ("stopImporter.stoplistAdd".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new TrackStoplistAddCommand(this));
+        else if ("stopImporter.stoplistDelete".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new TrackStoplistDeleteCommand(this));
+        else if ("stopImporter.stoplistSort".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new TrackStoplistSortCommand(this));
+        else if ("stopImporter.waypointsFind".equals(event.getActionCommand()))
+            findNodesInTable(dialog.getWaypointsTable(), waypointTM.nodes);
+        else if ("stopImporter.waypointsShow".equals(event.getActionCommand()))
+            showNodesFromTable(dialog.getWaypointsTable(), waypointTM.nodes);
+        else if ("stopImporter.waypointsMark".equals(event.getActionCommand()))
+            markNodesFromTable(dialog.getWaypointsTable(), waypointTM.nodes);
+        else if ("stopImporter.waypointsDetach".equals(event.getActionCommand())) {
+            Main.main.undoRedo.add(new WaypointsDetachCommand(this));
+            dialog.getWaypointsTable().clearSelection();
+        } else if ("stopImporter.waypointsAdd".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new WaypointsEnableCommand(this));
+        else if ("stopImporter.waypointsDelete".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new WaypointsDisableCommand(this));
+        else if ("stopImporter.settingsStoptype".equals(event.getActionCommand()))
+            Main.main.undoRedo.add(new SettingsStoptypeCommand(this));
+    }
+
+    private void importData(final File file) {
+        try {
+            InputStream is;
+            if (file.getName().endsWith(".gpx.gz"))
+                is = new GZIPInputStream(new FileInputStream(file));
+            else
+                is = new FileInputStream(file);
+            // Workaround for SAX BOM bug
+            // https://bugs.openjdk.java.net/browse/JDK-6206835
+            if (!((is.read() == 0xef) && (is.read() == 0xbb) && (is.read() == 0xbf))) {
+                is.close();
+                if (file.getName().endsWith(".gpx.gz"))
+                    is = new GZIPInputStream(new FileInputStream(file));
+                else
+                    is = new FileInputStream(file);
+            }
+            final GpxReader r = new GpxReader(is);
+            final boolean parsedProperly = r.parse(true);
+            data = r.getGpxData();
+
+            if (!parsedProperly) {
+                JOptionPane.showMessageDialog(null,
+                        tr("Error occurred while parsing gpx file {0}. Only a part of the file will be available.",
+                                file.getName()));
+            }
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            JOptionPane.showMessageDialog(null, tr("File \"{0}\" does not exist", file.getName()));
+        } catch (SAXException e) {
+            e.printStackTrace();
+            JOptionPane.showMessageDialog(null, tr("Parsing file \"{0}\" failed", file.getName()));
+        } catch (IOException e) {
+            e.printStackTrace();
+            JOptionPane.showMessageDialog(null, tr("IOException \"{0}\" occurred", e.toString()));
+        }
+    }
+
+    private void refreshData() {
+        tracksListModel.clear();
+        if (data != null) {
+            Vector<TrackReference> trackRefs = new Vector<>();
+            Iterator<GpxTrack> trackIter = data.tracks.iterator();
+            while (trackIter.hasNext()) {
+                GpxTrack track = trackIter.next();
+                trackRefs.add(new TrackReference(track, this));
+            }
+
+            Collections.sort(trackRefs);
+
+            Iterator<TrackReference> iter = trackRefs.iterator();
+            while (iter.hasNext()) {
+                tracksListModel.addElement(iter.next());
+            }
+
+            waypointTM = new WaypointTableModel(this);
+            Iterator<WayPoint> waypointIter = data.waypoints.iterator();
+            while (waypointIter.hasNext()) {
+                WayPoint waypoint = waypointIter.next();
+                waypointTM.addRow(waypoint);
+            }
+            dialog.setWaypointsTableModel(waypointTM);
+        } else {
+            JOptionPane.showMessageDialog(null,
+                    tr("The GPX file contained no tracks or waypoints."), tr("No data found"),
+                    JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+    public void tracksSelectionChanged(int selectedPos) {
+        if (selectedPos >= 0) {
+            currentTrack = (tracksListModel.elementAt(selectedPos));
+            dialog.setTrackValid(true);
+
+            // Prepare Settings
+            dialog.setSettings(currentTrack.gpsSyncTime, currentTrack.stopwatchStart,
+                    currentTrack.timeWindow, currentTrack.threshold);
+
+            // Prepare Stoplist
+            dialog.setStoplistTableModel(tracksListModel.elementAt(selectedPos).stoplistTM);
+        } else {
+            currentTrack = null;
+            dialog.setTrackValid(false);
+        }
+    }
+
+    public Node createNode(LatLon latLon, String name) {
+        return createNode(latLon, dialog.getStoptype(), name);
+    }
+
+    public static Node createNode(LatLon latLon, String type, String name) {
+        Node node = new Node(latLon);
+        setTagsWrtType(node, type);
+        node.put("name", name);
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        if (ds == null) {
+            JOptionPane.showMessageDialog(null,
+                    tr("There exists no dataset."
+                            + " Try to download data from the server or open an OSM file."),
+                    tr("No data found"), JOptionPane.ERROR_MESSAGE);
+
+            return null;
+        }
+        ds.addPrimitive(node);
+        return node;
+    }
+
+    /** sets the tags of the node according to the type */
+    public static void setTagsWrtType(Node node, String type) {
+        node.remove("highway");
+        node.remove("railway");
+        if ("bus".equals(type))
+            node.put("highway", "bus_stop");
+        else if ("tram".equals(type))
+            node.put("railway", "tram_stop");
+        else if ("light_rail".equals(type))
+            node.put("railway", "station");
+        else if ("subway".equals(type))
+            node.put("railway", "station");
+        else if ("rail".equals(type))
+            node.put("railway", "station");
+    }
+
+    /**
+     * returns a collection of all selected lines or a collection of all lines otherwise
+     */
+    public static Vector<Integer> getConsideredLines(JTable table) {
+        int[] selectedLines = table.getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < table.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+        return consideredLines;
+    }
+
+    /** marks the table items whose nodes are marked on the map */
+    public static void findNodesInTable(JTable table, Vector<Node> nodes) {
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        if (ds == null)
+            return;
+
+        table.clearSelection();
+
+        for (int i = 0; i < table.getRowCount(); ++i) {
+            if ((nodes.elementAt(i) != null) && (ds.isSelected(nodes.elementAt(i))))
+                table.addRowSelectionInterval(i, i);
+        }
+    }
+
+    /**
+     * shows the nodes that correspond to the marked lines in the table. If no lines are marked in the table, show all nodes from the vector
+     */
+    public static void showNodesFromTable(JTable table, Vector<Node> nodes) {
+        BoundingXYVisitor box = new BoundingXYVisitor();
+        Vector<Integer> consideredLines = getConsideredLines(table);
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            int j = consideredLines.elementAt(i);
+            if (nodes.elementAt(j) != null)
+                nodes.elementAt(j).accept(box);
+        }
+        if (box.getBounds() == null)
+            return;
+        box.enlargeBoundingBox();
+        MainApplication.getMap().mapView.zoomTo(box);
+    }
+
+    /**
+     * marks the nodes that correspond to the marked lines in the table. If no lines are marked in the table, mark all nodes from the vector
+     */
+    public static void markNodesFromTable(JTable table, Vector<Node> nodes) {
+        OsmPrimitive[] osmp = {null};
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        ds.setSelected(osmp);
+        Vector<Integer> consideredLines = getConsideredLines(table);
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            int j = consideredLines.elementAt(i);
+            if (nodes.elementAt(j) != null)
+                ds.addSelected(nodes.elementAt(j));
+        }
+    }
+
+    public static String timeOf(double t) {
+        t -= Math.floor(t / 24 / 60 / 60) * 24 * 60 * 60;
+
+        int hour = (int) Math.floor(t / 60 / 60);
+        t -= Math.floor(t / 60 / 60) * 60 * 60;
+        int minute = (int) Math.floor(t / 60);
+        t -= Math.floor(t / 60) * 60;
+        double second = t;
+
+        Format format = new DecimalFormat("00");
+        Format formatS = new DecimalFormat("00.###");
+        return (format.format(hour) + ":" + format.format(minute) + ":" + formatS.format(second));
+    }
+
+    public Action getFocusWaypointNameAction() {
+        return new FocusWaypointNameAction();
+    }
+
+    public Action getFocusWaypointShelterAction(String shelter) {
+        return new FocusWaypointShelterAction(shelter);
+    }
+
+    public Action getFocusWaypointDeleteAction() {
+        return new AbstractAction() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                JTable table = dialog.getWaypointsTable();
+                int row = table.getEditingRow();
+                if (row < 0)
+                    return;
+                table.clearSelection();
+                table.addRowSelectionInterval(row, row);
+                Main.main.undoRedo.add(new WaypointsDisableCommand(StopImporterAction.this));
+            }
+        };
+    }
+
+    public Action getFocusTrackStoplistNameAction() {
+        return new FocusTrackStoplistNameAction();
+    }
+
+    public Action getFocusTrackStoplistShelterAction(String shelter) {
+        return new FocusTrackStoplistShelterAction(shelter);
+    }
+
+    public Action getFocusStoplistDeleteAction() {
+        return new AbstractAction() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                JTable table = dialog.getStoplistTable();
+                int row = table.getEditingRow();
+                if (row < 0)
+                    return;
+                table.clearSelection();
+                table.addRowSelectionInterval(row, row);
+                Main.main.undoRedo.add(new TrackStoplistDeleteCommand(StopImporterAction.this));
+            }
+        };
+    }
+
+    private static class FocusWaypointNameAction extends AbstractAction {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            JTable table = dialog.getWaypointsTable();
+            showNodesFromTable(table, waypointTM.nodes);
+            markNodesFromTable(table, waypointTM.nodes);
+            int row = table.getEditingRow();
+            if (row < 0)
+                row = 0;
+            waypointTM.inEvent = true;
+            if (table.getCellEditor() != null) {
+                if (!table.getCellEditor().stopCellEditing())
+                    table.getCellEditor().cancelCellEditing();
+            }
+            table.editCellAt(row, 1);
+            table.getCellEditor().getTableCellEditorComponent(table, "", true, row, 1);
+            waypointTM.inEvent = false;
+        }
+    }
+
+    private static class FocusWaypointShelterAction extends AbstractAction {
+        private String defaultShelter = null;
+
+        FocusWaypointShelterAction(String defaultShelter) {
+            this.defaultShelter = defaultShelter;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            JTable table = dialog.getWaypointsTable();
+            showNodesFromTable(table, waypointTM.nodes);
+            markNodesFromTable(table, waypointTM.nodes);
+            int row = table.getEditingRow();
+            if (row < 0)
+                row = 0;
+            waypointTM.inEvent = true;
+            if (table.getCellEditor() != null) {
+                if (!table.getCellEditor().stopCellEditing())
+                    table.getCellEditor().cancelCellEditing();
+            }
+            table.editCellAt(row, 2);
+            waypointTM.inEvent = false;
+            table.getCellEditor().getTableCellEditorComponent(table, defaultShelter, true, row, 2);
+        }
+    }
+
+    private static class FocusTrackStoplistNameAction extends AbstractAction {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            JTable table = dialog.getStoplistTable();
+            showNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+            markNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+            int row = table.getEditingRow();
+            if (row < 0)
+                row = 0;
+            currentTrack.inEvent = true;
+            if (table.getCellEditor() != null) {
+                if (!table.getCellEditor().stopCellEditing())
+                    table.getCellEditor().cancelCellEditing();
+            }
+            table.editCellAt(row, 1);
+            table.getCellEditor().getTableCellEditorComponent(table, "", true, row, 1);
+            currentTrack.inEvent = false;
+        }
+    }
+
+    private static class FocusTrackStoplistShelterAction extends AbstractAction {
+        private String defaultShelter = null;
+
+        FocusTrackStoplistShelterAction(String defaultShelter) {
+            this.defaultShelter = defaultShelter;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            JTable table = dialog.getStoplistTable();
+            showNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+            markNodesFromTable(table, currentTrack.stoplistTM.getNodes());
+            int row = table.getEditingRow();
+            if (row < 0)
+                row = 0;
+            currentTrack.inEvent = true;
+            if (table.getCellEditor() != null) {
+                if (!table.getCellEditor().stopCellEditing())
+                    table.getCellEditor().cancelCellEditing();
+            }
+            table.editCellAt(row, 2);
+            currentTrack.inEvent = false;
+            table.getCellEditor().getTableCellEditorComponent(table, defaultShelter, true, row, 2);
+        }
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/AbstractGTFSCatchJoinCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/AbstractGTFSCatchJoinCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/AbstractGTFSCatchJoinCommand.java	(revision 33765)
@@ -0,0 +1,116 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.GTFSStopTableModel;
+
+public abstract class AbstractGTFSCatchJoinCommand extends Command {
+    private List<Integer> workingLines = null;
+
+    private Node undoMapNode = null;
+
+    private Node undoTableNode = null;
+
+    private GTFSStopTableModel gtfsStopTM = null;
+
+    private String type = null;
+
+    private final boolean isCatch;
+
+    public AbstractGTFSCatchJoinCommand(GTFSImporterAction controller, boolean isCatch) {
+        gtfsStopTM = controller.getGTFSStopTableModel();
+        workingLines = new ArrayList<>();
+        this.isCatch = isCatch;
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getGTFSStopTable().getSelectedRows();
+        if (selectedLines.length != 1)
+            return;
+        workingLines.add(selectedLines[0]);
+    }
+
+    @Override
+    public boolean executeCommand() {
+        if (workingLines.size() != 1)
+            return false;
+        Node dest = null;
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        Iterator<Node> iter = ds.getSelectedNodes().iterator();
+        int j = workingLines.get(0);
+        while (iter.hasNext()) {
+            Node n = iter.next();
+            if ((n != null) && (n.equals(gtfsStopTM.nodes.elementAt(j))))
+                continue;
+            if (dest != null)
+                return false;
+            dest = n;
+        }
+        if (dest == null)
+            return false;
+        undoMapNode = new Node(dest);
+
+        Node node = gtfsStopTM.nodes.elementAt(j);
+        undoTableNode = node;
+        if (node != null) {
+            ds.removePrimitive(node);
+            node.setDeleted(true);
+        }
+
+        if (isCatch)
+            dest.setCoor(gtfsStopTM.coors.elementAt(j));
+        dest.put("highway", "bus_stop");
+        dest.put("stop_id", (String) gtfsStopTM.getValueAt(j, 0));
+        if (dest.get("name") == null)
+            dest.put("name", (String) gtfsStopTM.getValueAt(j, 1));
+        if (isCatch)
+            dest.put("note", "moved by gtfs import");
+        gtfsStopTM.nodes.set(j, dest);
+        type = (String) gtfsStopTM.getValueAt(j, 2);
+        gtfsStopTM.setValueAt(isCatch ? "fed" : tr("moved"), j, 2);
+
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        if (workingLines.size() != 1)
+            return;
+        int j = workingLines.get(0);
+
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        Node node = gtfsStopTM.nodes.elementAt(j);
+        if (node != null) {
+            ds.removePrimitive(node);
+            node.setDeleted(true);
+        }
+
+        if (undoMapNode != null) {
+            undoMapNode.setDeleted(false);
+            ds.addPrimitive(undoMapNode);
+        }
+        if (undoTableNode != null) {
+            undoTableNode.setDeleted(false);
+            ds.addPrimitive(undoTableNode);
+        }
+        gtfsStopTM.nodes.set(j, undoTableNode);
+        gtfsStopTM.setValueAt(type, j, 2);
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+        // Do nothing
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSAddCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSAddCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSAddCommand.java	(revision 33765)
@@ -0,0 +1,88 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.GTFSStopTableModel;
+
+public class GTFSAddCommand extends Command {
+    private Vector<Integer> workingLines = null;
+
+    private Vector<String> typesForUndo = null;
+
+    private GTFSStopTableModel gtfsStopTM = null;
+
+    private String type = null;
+
+    public GTFSAddCommand(GTFSImporterAction controller) {
+        gtfsStopTM = controller.getGTFSStopTableModel();
+        type = controller.getDialog().getStoptype();
+        workingLines = new Vector<>();
+        typesForUndo = new Vector<>();
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getGTFSStopTable().getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < gtfsStopTM.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+
+        // keep only lines where a node can be added
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            if (gtfsStopTM.nodes.elementAt(consideredLines.elementAt(i)) == null)
+                workingLines.add(consideredLines.elementAt(i));
+        }
+    }
+
+    @Override
+    public boolean executeCommand() {
+        typesForUndo.clear();
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            typesForUndo.add((String) gtfsStopTM.getValueAt(j, 2));
+            Node node = GTFSImporterAction.createNode(gtfsStopTM.coors.elementAt(j),
+                    (String) gtfsStopTM.getValueAt(j, 0), (String) gtfsStopTM.getValueAt(j, 1));
+            gtfsStopTM.nodes.set(j, node);
+            gtfsStopTM.setValueAt(tr("added"), j, 2);
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = gtfsStopTM.nodes.elementAt(j);
+            gtfsStopTM.nodes.set(j, null);
+            gtfsStopTM.setValueAt(typesForUndo.elementAt(i), j, 2);
+            if (node == null)
+                continue;
+            MainApplication.getLayerManager().getEditDataSet().removePrimitive(node);
+            node.setDeleted(true);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Enable GTFSStops");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSCatchCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSCatchCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSCatchCommand.java	(revision 33765)
@@ -0,0 +1,18 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+
+public class GTFSCatchCommand extends AbstractGTFSCatchJoinCommand {
+
+    public GTFSCatchCommand(GTFSImporterAction controller) {
+        super(controller, true);
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Catch GTFS stops");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSDeleteCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSDeleteCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSDeleteCommand.java	(revision 33765)
@@ -0,0 +1,93 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.GTFSStopTableModel;
+
+public class GTFSDeleteCommand extends Command {
+    private Vector<Integer> workingLines = null;
+
+    private Vector<Node> nodesForUndo = null;
+
+    private Vector<String> typesForUndo = null;
+
+    private GTFSStopTableModel gtfsStopTM = null;
+
+    public GTFSDeleteCommand(GTFSImporterAction controller) {
+        gtfsStopTM = controller.getGTFSStopTableModel();
+        workingLines = new Vector<>();
+        nodesForUndo = new Vector<>();
+        typesForUndo = new Vector<>();
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getGTFSStopTable().getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < gtfsStopTM.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+
+        // keep only lines where a node can be added
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            if (gtfsStopTM.nodes.elementAt(consideredLines.elementAt(i)) != null)
+                workingLines.add(consideredLines.elementAt(i));
+        }
+    }
+
+    @Override
+    public boolean executeCommand() {
+        nodesForUndo.clear();
+        typesForUndo.clear();
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = gtfsStopTM.nodes.elementAt(j);
+            nodesForUndo.add(node);
+            typesForUndo.add((String) gtfsStopTM.getValueAt(j, 2));
+            if (node == null)
+                continue;
+            gtfsStopTM.nodes.set(j, null);
+            gtfsStopTM.setValueAt(tr("skipped"), j, 2);
+            MainApplication.getLayerManager().getEditDataSet().removePrimitive(node);
+            node.setDeleted(true);
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = nodesForUndo.elementAt(i);
+            gtfsStopTM.nodes.set(j, node);
+            gtfsStopTM.setValueAt(typesForUndo.elementAt(i), j, 2);
+            if (node == null)
+                continue;
+            node.setDeleted(false);
+            MainApplication.getLayerManager().getEditDataSet().addPrimitive(node);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Disable GTFS");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSJoinCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSJoinCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/GTFSJoinCommand.java	(revision 33765)
@@ -0,0 +1,18 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+
+public class GTFSJoinCommand extends AbstractGTFSCatchJoinCommand {
+
+    public GTFSJoinCommand(GTFSImporterAction controller) {
+        super(controller, false);
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Join GTFS stops");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/SettingsStoptypeCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/SettingsStoptypeCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/SettingsStoptypeCommand.java	(revision 33765)
@@ -0,0 +1,89 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import javax.swing.DefaultListModel;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.WaypointTableModel;
+import org.openstreetmap.josm.plugins.public_transport.refs.TrackReference;
+
+public class SettingsStoptypeCommand extends Command {
+    private static class HighwayRailway {
+        HighwayRailway(Node node) {
+            this.node = node;
+            highway = node.get("highway");
+            railway = node.get("railway");
+        }
+
+        public Node node;
+
+        public String highway;
+
+        public String railway;
+    }
+
+    private Vector<HighwayRailway> oldStrings = null;
+
+    private WaypointTableModel waypointTM = null;
+
+    private DefaultListModel<?> tracksListModel = null;
+
+    private String type = null;
+
+    public SettingsStoptypeCommand(StopImporterAction controller) {
+        waypointTM = controller.getWaypointTableModel();
+        tracksListModel = controller.getTracksListModel();
+        type = controller.getDialog().getStoptype();
+        oldStrings = new Vector<>();
+    }
+
+    @Override
+    public boolean executeCommand() {
+        oldStrings.clear();
+        for (int i = 0; i < waypointTM.getRowCount(); ++i) {
+            if (waypointTM.nodes.elementAt(i) != null) {
+                Node node = waypointTM.nodes.elementAt(i);
+                oldStrings.add(new HighwayRailway(node));
+                StopImporterAction.setTagsWrtType(node, type);
+            }
+        }
+        for (int j = 0; j < tracksListModel.size(); ++j) {
+            TrackReference track = (TrackReference) tracksListModel.elementAt(j);
+            for (int i = 0; i < track.stoplistTM.getRowCount(); ++i) {
+                if (track.stoplistTM.nodeAt(i) != null) {
+                    Node node = track.stoplistTM.nodeAt(i);
+                    oldStrings.add(new HighwayRailway(node));
+                    StopImporterAction.setTagsWrtType(node, type);
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < oldStrings.size(); ++i) {
+            HighwayRailway hr = oldStrings.elementAt(i);
+            hr.node.put("highway", hr.highway);
+            hr.node.put("railway", hr.railway);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Change stop type");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistAddCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistAddCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistAddCommand.java	(revision 33765)
@@ -0,0 +1,46 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.TrackStoplistTableModel;
+
+public class TrackStoplistAddCommand extends Command {
+    private int workingLine;
+
+    private TrackStoplistTableModel stoplistTM = null;
+
+    public TrackStoplistAddCommand(StopImporterAction controller) {
+        stoplistTM = controller.getCurrentTrack().stoplistTM;
+        workingLine = controller.getDialog().getStoplistTable().getSelectedRow();
+    }
+
+    @Override
+    public boolean executeCommand() {
+        stoplistTM.insertRow(workingLine, "00:00:00");
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        int workingLine = this.workingLine;
+        if (workingLine < 0)
+            workingLine = stoplistTM.getRowCount() - 1;
+        stoplistTM.removeRow(workingLine);
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Add track stop");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistDeleteCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistDeleteCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistDeleteCommand.java	(revision 33765)
@@ -0,0 +1,98 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.TrackStoplistTableModel;
+
+public class TrackStoplistDeleteCommand extends Command {
+    private static class NodeTimeName {
+        NodeTimeName(Node node, String time, String name, TransText shelter) {
+            this.node = node;
+            this.time = time;
+            this.name = name;
+            this.shelter = shelter;
+        }
+
+        public Node node;
+
+        public String time;
+
+        public String name;
+
+        public TransText shelter;
+    }
+
+    private Vector<Integer> workingLines = null;
+
+    private Vector<NodeTimeName> nodesForUndo = null;
+
+    private TrackStoplistTableModel stoplistTM = null;
+
+    public TrackStoplistDeleteCommand(StopImporterAction controller) {
+        stoplistTM = controller.getCurrentTrack().stoplistTM;
+        workingLines = new Vector<>();
+        nodesForUndo = new Vector<>();
+
+        // use selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getStoplistTable().getSelectedRows();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                workingLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < stoplistTM.getRowCount(); ++i) {
+                workingLines.add(Integer.valueOf(i));
+            }
+        }
+    }
+
+    @Override
+    public boolean executeCommand() {
+        nodesForUndo.clear();
+        for (int i = workingLines.size() - 1; i >= 0; --i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = stoplistTM.nodeAt(j);
+            nodesForUndo.add(new NodeTimeName(node, (String) stoplistTM.getValueAt(j, 0),
+                    (String) stoplistTM.getValueAt(j, 1), (TransText) stoplistTM.getValueAt(j, 2)));
+            stoplistTM.removeRow(j);
+            if (node == null)
+                continue;
+            MainApplication.getLayerManager().getEditDataSet().removePrimitive(node);
+            node.setDeleted(true);
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            NodeTimeName ntn = nodesForUndo.elementAt(workingLines.size() - i - 1);
+            stoplistTM.insertRow(j, ntn.node, ntn.time, ntn.name, ntn.shelter);
+            if (ntn.node == null)
+                continue;
+            ntn.node.setDeleted(false);
+            MainApplication.getLayerManager().getEditDataSet().addPrimitive(ntn.node);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Delete track stop");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistDetachCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistDetachCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistDetachCommand.java	(revision 33765)
@@ -0,0 +1,77 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.TrackStoplistTableModel;
+
+public class TrackStoplistDetachCommand extends Command {
+    private Vector<Integer> workingLines = null;
+
+    private Vector<Node> nodesForUndo = null;
+
+    private TrackStoplistTableModel stoplistTM = null;
+
+    public TrackStoplistDetachCommand(StopImporterAction controller) {
+        stoplistTM = controller.getCurrentTrack().stoplistTM;
+        workingLines = new Vector<>();
+        nodesForUndo = new Vector<>();
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getStoplistTable().getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < stoplistTM.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+
+        // keep only lines where a node can be added
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            if (stoplistTM.nodeAt(consideredLines.elementAt(i)) != null)
+                workingLines.add(consideredLines.elementAt(i));
+        }
+    }
+
+    @Override
+    public boolean executeCommand() {
+        nodesForUndo.clear();
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = stoplistTM.nodeAt(j);
+            nodesForUndo.add(node);
+            stoplistTM.setNodeAt(j, null);
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = nodesForUndo.elementAt(i);
+            stoplistTM.setNodeAt(j, node);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Detach track stop list");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistNameCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistNameCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistNameCommand.java	(revision 33765)
@@ -0,0 +1,105 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.dialogs.StopImporterDialog;
+import org.openstreetmap.josm.plugins.public_transport.refs.TrackReference;
+
+public class TrackStoplistNameCommand extends Command {
+    private int workingLine = 0;
+
+    private TrackReference trackref = null;
+
+    private String oldName = null;
+
+    private String name = null;
+
+    private String oldTime = null;
+
+    private String time = null;
+
+    private String oldShelter = null;
+
+    private TransText shelter = null;
+
+    private LatLon oldLatLon = null;
+
+    public TrackStoplistNameCommand(TrackReference trackref, int workingLine) {
+        this.trackref = trackref;
+        this.workingLine = workingLine;
+        Node node = trackref.stoplistTM.nodeAt(workingLine);
+        if (node != null) {
+            oldName = node.get("name");
+            oldTime = trackref.stoplistTM.timeAt(workingLine);
+            oldShelter = node.get("shelter");
+            oldLatLon = node.getCoor();
+        }
+        this.time = (String) trackref.stoplistTM.getValueAt(workingLine, 0);
+        this.name = (String) trackref.stoplistTM.getValueAt(workingLine, 1);
+        this.shelter = (TransText) trackref.stoplistTM.getValueAt(workingLine, 2);
+        if ("".equals(this.shelter.text))
+            this.shelter = null;
+    }
+
+    @Override
+    public boolean executeCommand() {
+        Node node = trackref.stoplistTM.nodeAt(workingLine);
+        if (node != null) {
+            node.put("name", name);
+            node.put("shelter", shelter.text);
+            double dTime = StopImporterDialog.parseTime(time);
+            node.setCoor(trackref.computeCoor(dTime));
+        }
+        trackref.inEvent = true;
+        if (time == null)
+            trackref.stoplistTM.setValueAt("", workingLine, 0);
+        else
+            trackref.stoplistTM.setValueAt(time, workingLine, 0);
+        if (name == null)
+            trackref.stoplistTM.setValueAt("", workingLine, 1);
+        else
+            trackref.stoplistTM.setValueAt(name, workingLine, 1);
+        trackref.stoplistTM.setValueAt(shelter, workingLine, 2);
+        trackref.inEvent = false;
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        Node node = trackref.stoplistTM.nodeAt(workingLine);
+        if (node != null) {
+            node.put("name", oldName);
+            node.put("shelter", oldShelter);
+            node.setCoor(oldLatLon);
+        }
+        trackref.inEvent = true;
+        if (oldTime == null)
+            trackref.stoplistTM.setValueAt("", workingLine, 0);
+        else
+            trackref.stoplistTM.setValueAt(oldTime, workingLine, 0);
+        if (oldName == null)
+            trackref.stoplistTM.setValueAt("", workingLine, 1);
+        else
+            trackref.stoplistTM.setValueAt(oldName, workingLine, 1);
+        trackref.stoplistTM.setValueAt(new TransText(oldShelter), workingLine, 2);
+        trackref.inEvent = false;
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Edit track stop list");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistRelocateCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistRelocateCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistRelocateCommand.java	(revision 33765)
@@ -0,0 +1,90 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.dialogs.StopImporterDialog;
+import org.openstreetmap.josm.plugins.public_transport.refs.TrackReference;
+
+public class TrackStoplistRelocateCommand extends Command {
+    private StopImporterAction controller = null;
+
+    private TrackReference currentTrack = null;
+
+    private String oldGpsSyncTime = null;
+
+    private String oldStopwatchStart = null;
+
+    private String gpsSyncTime = null;
+
+    private String stopwatchStart = null;
+
+    public TrackStoplistRelocateCommand(StopImporterAction controller) {
+        this.controller = controller;
+        this.currentTrack = controller.getCurrentTrack();
+        this.gpsSyncTime = controller.getDialog().getGpsTimeStart();
+        this.stopwatchStart = controller.getDialog().getStopwatchStart();
+        this.oldGpsSyncTime = currentTrack.gpsSyncTime;
+        this.oldStopwatchStart = currentTrack.stopwatchStart;
+    }
+
+    @Override
+    public boolean executeCommand() {
+        currentTrack.gpsSyncTime = gpsSyncTime;
+        currentTrack.stopwatchStart = stopwatchStart;
+        for (int i = 0; i < currentTrack.stoplistTM.getNodes().size(); ++i) {
+            Node node = currentTrack.stoplistTM.nodeAt(i);
+            if (node == null)
+                continue;
+
+            double time = StopImporterDialog
+                    .parseTime((String) currentTrack.stoplistTM.getValueAt(i, 0));
+            node.setCoor(currentTrack.computeCoor(time));
+        }
+        if (currentTrack == controller.getCurrentTrack()) {
+            controller.inEvent = true;
+            controller.getDialog().setGpsTimeStart(gpsSyncTime);
+            controller.getDialog().setStopwatchStart(stopwatchStart);
+            controller.inEvent = false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        currentTrack.gpsSyncTime = oldGpsSyncTime;
+        currentTrack.stopwatchStart = oldStopwatchStart;
+        for (int i = 0; i < currentTrack.stoplistTM.getNodes().size(); ++i) {
+            Node node = currentTrack.stoplistTM.nodeAt(i);
+            if (node == null)
+                continue;
+
+            double time = StopImporterDialog
+                    .parseTime((String) currentTrack.stoplistTM.getValueAt(i, 0));
+            node.setCoor(currentTrack.computeCoor(time));
+        }
+        if (currentTrack == controller.getCurrentTrack()) {
+            controller.inEvent = true;
+            controller.getDialog().setGpsTimeStart(oldGpsSyncTime);
+            controller.getDialog().setStopwatchStart(oldStopwatchStart);
+            controller.inEvent = false;
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Relocate nodes in track stoplist");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistSortCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistSortCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackStoplistSortCommand.java	(revision 33765)
@@ -0,0 +1,137 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.dialogs.StopImporterDialog;
+import org.openstreetmap.josm.plugins.public_transport.models.TrackStoplistTableModel;
+
+public class TrackStoplistSortCommand extends Command {
+    private TrackStoplistTableModel stoplistTM = null;
+
+    private Vector<Vector<Object>> tableDataModel = null;
+
+    private Vector<Node> nodes = null;
+
+    private Vector<String> times = null;
+
+    private Vector<Integer> workingLines = null;
+
+    private int insPos;
+
+    private String stopwatchStart;
+
+    public TrackStoplistSortCommand(StopImporterAction controller) {
+        stoplistTM = controller.getCurrentTrack().stoplistTM;
+        workingLines = new Vector<>();
+        insPos = controller.getDialog().getStoplistTable().getSelectedRow();
+        stopwatchStart = controller.getCurrentTrack().stopwatchStart;
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getStoplistTable().getSelectedRows();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                workingLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < stoplistTM.getRowCount(); ++i) {
+                workingLines.add(Integer.valueOf(i));
+            }
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public boolean executeCommand() {
+        tableDataModel = (Vector<Vector<Object>>) stoplistTM.getDataVector().clone();
+        nodes = (Vector<Node>) stoplistTM.getNodes().clone();
+        times = (Vector<String>) stoplistTM.getTimes().clone();
+
+        Vector<NodeSortEntry> nodesToSort = new Vector<>();
+        for (int i = workingLines.size() - 1; i >= 0; --i) {
+            int j = workingLines.elementAt(i).intValue();
+            nodesToSort.add(new NodeSortEntry(stoplistTM.nodeAt(j),
+                    (String) stoplistTM.getValueAt(j, 0), (String) stoplistTM.getValueAt(j, 1),
+                    (TransText) stoplistTM.getValueAt(j, 2),
+                    StopImporterDialog.parseTime(stopwatchStart)));
+            stoplistTM.removeRow(j);
+        }
+
+        Collections.sort(nodesToSort);
+
+        int insPos = this.insPos;
+        Iterator<NodeSortEntry> iter = nodesToSort.iterator();
+        while (iter.hasNext()) {
+            NodeSortEntry nse = iter.next();
+            stoplistTM.insertRow(insPos, nse.node, nse.time, nse.name, nse.shelter);
+            if (insPos >= 0)
+                ++insPos;
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        stoplistTM.setDataVector(tableDataModel);
+        stoplistTM.setNodes(nodes);
+        stoplistTM.setTimes(times);
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: sort track stop list");
+    }
+
+    private static class NodeSortEntry implements Comparable<NodeSortEntry> {
+        public Node node = null;
+
+        public String time = null;
+
+        public String name = null;
+
+        public TransText shelter = null;
+
+        public double startTime = 0;
+
+        NodeSortEntry(Node node, String time, String name, TransText shelter,
+                double startTime) {
+            this.node = node;
+            this.time = time;
+            this.name = name;
+            this.shelter = shelter;
+        }
+
+        @Override
+        public int compareTo(NodeSortEntry nse) {
+            double time = StopImporterDialog.parseTime(this.time);
+            if (time - startTime > 12 * 60 * 60)
+                time -= 24 * 60 * 60;
+
+            double nseTime = StopImporterDialog.parseTime(nse.time);
+            if (nseTime - startTime > 12 * 60 * 60)
+                nseTime -= 24 * 60 * 60;
+
+            if (time < nseTime)
+                return -1;
+            else if (time > nseTime)
+                return 1;
+            else
+                return 0;
+        }
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackSuggestStopsCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackSuggestStopsCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/TrackSuggestStopsCommand.java	(revision 33765)
@@ -0,0 +1,239 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.dialogs.StopImporterDialog;
+import org.openstreetmap.josm.plugins.public_transport.models.TrackStoplistTableModel;
+
+public class TrackSuggestStopsCommand extends Command {
+    private TrackStoplistTableModel stoplistTM = null;
+
+    private String type = null;
+
+    private String stopwatchStart;
+
+    private String gpsStartTime;
+
+    private String gpsSyncTime;
+
+    private double timeWindow;
+
+    private double threshold;
+
+    private Collection<GpxTrackSegment> segments = null;
+
+    private Vector<Vector<Object>> tableDataModel = null;
+
+    private Vector<Node> nodes = null;
+
+    private Vector<String> times = null;
+
+    public TrackSuggestStopsCommand(StopImporterAction controller) {
+        if (controller.getCurrentTrack() == null)
+            return;
+        stoplistTM = controller.getCurrentTrack().stoplistTM;
+        type = controller.getDialog().getStoptype();
+        stopwatchStart = controller.getCurrentTrack().stopwatchStart;
+        gpsStartTime = controller.getCurrentTrack().gpsStartTime;
+        gpsSyncTime = controller.getCurrentTrack().gpsSyncTime;
+        timeWindow = controller.getCurrentTrack().timeWindow;
+        threshold = controller.getCurrentTrack().threshold;
+        segments = controller.getCurrentTrack().getGpxTrack().getSegments();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public boolean executeCommand() {
+        if (stoplistTM == null)
+            return false;
+        tableDataModel = (Vector<Vector<Object>>) stoplistTM.getDataVector().clone();
+        nodes = (Vector<Node>) stoplistTM.getNodes().clone();
+        times = (Vector<String>) stoplistTM.getTimes().clone();
+
+        for (int i = 0; i < stoplistTM.getNodes().size(); ++i) {
+            Node node = stoplistTM.nodeAt(i);
+            if (node == null)
+                continue;
+            MainApplication.getLayerManager().getEditDataSet().removePrimitive(node);
+            node.setDeleted(true);
+        }
+        stoplistTM.clear();
+
+        Vector<WayPoint> wayPoints = new Vector<>();
+        Iterator<GpxTrackSegment> siter = segments.iterator();
+        while (siter.hasNext()) {
+            Iterator<WayPoint> witer = siter.next().getWayPoints().iterator();
+            while (witer.hasNext()) {
+                wayPoints.add(witer.next());
+            }
+        }
+        Vector<Double> wayPointsDist = new Vector<>(wayPoints.size());
+
+        int i = 0;
+        double time = -48 * 60 * 60;
+        double dGpsStartTime = StopImporterDialog.parseTime(gpsStartTime);
+        while ((i < wayPoints.size()) && (time < dGpsStartTime + timeWindow / 2)) {
+            if (wayPoints.elementAt(i).getString("time") != null)
+                time = StopImporterDialog
+                        .parseTime(wayPoints.elementAt(i).getString("time").substring(11, 19));
+            if (time < dGpsStartTime)
+                time += 24 * 60 * 60;
+            wayPointsDist.add(Double.valueOf(Double.POSITIVE_INFINITY));
+            ++i;
+        }
+        while (i < wayPoints.size()) {
+            int j = i;
+            double time2 = time;
+            while ((j > 0) && (time - timeWindow / 2 < time2)) {
+                --j;
+                if (wayPoints.elementAt(j).getString("time") != null)
+                    time2 = StopImporterDialog
+                            .parseTime(wayPoints.elementAt(j).getString("time").substring(11, 19));
+                if (time2 < dGpsStartTime)
+                    time2 += 24 * 60 * 60;
+            }
+            int k = i + 1;
+            time2 = time;
+            while ((k < wayPoints.size()) && (time + timeWindow / 2 > time2)) {
+                if (wayPoints.elementAt(k).getString("time") != null)
+                    time2 = StopImporterDialog
+                            .parseTime(wayPoints.elementAt(k).getString("time").substring(11, 19));
+                if (time2 < dGpsStartTime)
+                    time2 += 24 * 60 * 60;
+                ++k;
+            }
+
+            if (j < k) {
+                double dist = 0;
+                LatLon latLonI = wayPoints.elementAt(i).getCoor();
+                for (int l = j; l < k; ++l) {
+                    double distL = latLonI.greatCircleDistance(wayPoints.elementAt(l).getCoor());
+                    if (distL > dist)
+                        dist = distL;
+                }
+                wayPointsDist.add(Double.valueOf(dist));
+            } else
+                wayPointsDist.add(Double.valueOf(Double.POSITIVE_INFINITY));
+
+            if (wayPoints.elementAt(i).getString("time") != null)
+                time = StopImporterDialog
+                        .parseTime(wayPoints.elementAt(i).getString("time").substring(11, 19));
+            if (time < dGpsStartTime)
+                time += 24 * 60 * 60;
+            ++i;
+        }
+
+        LatLon lastStopCoor = null;
+        for (i = 1; i < wayPoints.size() - 1; ++i) {
+            if (wayPointsDist.elementAt(i).doubleValue() >= threshold)
+                continue;
+            if ((wayPointsDist.elementAt(i).compareTo(wayPointsDist.elementAt(i - 1)) != -1)
+                    || (wayPointsDist.elementAt(i).compareTo(wayPointsDist.elementAt(i + 1)) != -1))
+                continue;
+
+            LatLon latLon = wayPoints.elementAt(i).getCoor();
+            if ((lastStopCoor != null) && (lastStopCoor.greatCircleDistance(latLon) < threshold))
+                continue;
+
+            if (wayPoints.elementAt(i).getString("time") != null) {
+                time = StopImporterDialog
+                        .parseTime(wayPoints.elementAt(i).getString("time").substring(11, 19));
+                double gpsSyncTime = StopImporterDialog.parseTime(this.gpsSyncTime);
+                if (gpsSyncTime < dGpsStartTime - 12 * 60 * 60)
+                    gpsSyncTime += 24 * 60 * 60;
+                double timeDelta = gpsSyncTime - StopImporterDialog.parseTime(stopwatchStart);
+                time -= timeDelta;
+                Node node = StopImporterAction.createNode(latLon, type, "");
+                stoplistTM.insertRow(-1, node, StopImporterAction.timeOf(time), "",
+                        new TransText(null));
+            }
+
+            lastStopCoor = latLon;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        if (stoplistTM == null)
+            return;
+        for (int i = 0; i < stoplistTM.getNodes().size(); ++i) {
+            Node node = stoplistTM.nodeAt(i);
+            if (node == null)
+                continue;
+            MainApplication.getLayerManager().getEditDataSet().removePrimitive(node);
+            node.setDeleted(true);
+        }
+
+        stoplistTM.setDataVector(tableDataModel);
+        stoplistTM.setNodes(nodes);
+        stoplistTM.setTimes(times);
+
+        for (int i = 0; i < stoplistTM.getNodes().size(); ++i) {
+            Node node = stoplistTM.nodeAt(i);
+            if (node == null)
+                continue;
+            node.setDeleted(false);
+            MainApplication.getLayerManager().getEditDataSet().addPrimitive(node);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Suggest stops");
+    }
+/*
+    private class NodeSortEntry implements Comparable<NodeSortEntry> {
+        public Node node = null;
+
+        public String time = null;
+
+        public String name = null;
+
+        public double startTime = 0;
+
+        public NodeSortEntry(Node node, String time, String name, double startTime) {
+            this.node = node;
+            this.time = time;
+            this.name = name;
+        }
+
+        @Override
+        public int compareTo(NodeSortEntry nse) {
+            double time = StopImporterDialog.parseTime(this.time);
+            if (time - startTime > 12 * 60 * 60)
+                time -= 24 * 60 * 60;
+
+            double nseTime = StopImporterDialog.parseTime(nse.time);
+            if (nseTime - startTime > 12 * 60 * 60)
+                nseTime -= 24 * 60 * 60;
+
+            if (time < nseTime)
+                return -1;
+            else if (time > nseTime)
+                return 1;
+            else
+                return 0;
+        }
+    }*/
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsDetachCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsDetachCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsDetachCommand.java	(revision 33765)
@@ -0,0 +1,77 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.WaypointTableModel;
+
+public class WaypointsDetachCommand extends Command {
+    private Vector<Integer> workingLines = null;
+
+    private Vector<Node> nodesForUndo = null;
+
+    private WaypointTableModel waypointTM = null;
+
+    public WaypointsDetachCommand(StopImporterAction controller) {
+        waypointTM = controller.getWaypointTableModel();
+        workingLines = new Vector<>();
+        nodesForUndo = new Vector<>();
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getWaypointsTable().getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < waypointTM.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+
+        // keep only lines where a node can be added
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            if (waypointTM.nodes.elementAt(consideredLines.elementAt(i)) != null)
+                workingLines.add(consideredLines.elementAt(i));
+        }
+    }
+
+    @Override
+    public boolean executeCommand() {
+        nodesForUndo.clear();
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = waypointTM.nodes.elementAt(j);
+            nodesForUndo.add(node);
+            waypointTM.nodes.set(j, null);
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = nodesForUndo.elementAt(i);
+            waypointTM.nodes.set(j, node);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Detach waypoints");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsDisableCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsDisableCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsDisableCommand.java	(revision 33765)
@@ -0,0 +1,86 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.WaypointTableModel;
+
+public class WaypointsDisableCommand extends Command {
+    private Vector<Integer> workingLines = null;
+
+    private Vector<Node> nodesForUndo = null;
+
+    private WaypointTableModel waypointTM = null;
+
+    public WaypointsDisableCommand(StopImporterAction controller) {
+        waypointTM = controller.getWaypointTableModel();
+        workingLines = new Vector<>();
+        nodesForUndo = new Vector<>();
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getWaypointsTable().getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < waypointTM.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+
+        // keep only lines where a node can be added
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            if (waypointTM.nodes.elementAt(consideredLines.elementAt(i)) != null)
+                workingLines.add(consideredLines.elementAt(i));
+        }
+    }
+
+    @Override
+    public boolean executeCommand() {
+        nodesForUndo.clear();
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = waypointTM.nodes.elementAt(j);
+            nodesForUndo.add(node);
+            if (node == null)
+                continue;
+            waypointTM.nodes.set(j, null);
+            MainApplication.getLayerManager().getEditDataSet().removePrimitive(node);
+            node.setDeleted(true);
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = nodesForUndo.elementAt(i);
+            waypointTM.nodes.set(j, node);
+            if (node == null)
+                continue;
+            node.setDeleted(false);
+            MainApplication.getLayerManager().getEditDataSet().addPrimitive(node);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Disable waypoints");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsEnableCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsEnableCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsEnableCommand.java	(revision 33765)
@@ -0,0 +1,84 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.Vector;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.WaypointTableModel;
+
+public class WaypointsEnableCommand extends Command {
+    private Vector<Integer> workingLines = null;
+
+    private WaypointTableModel waypointTM = null;
+
+    private String type = null;
+
+    public WaypointsEnableCommand(StopImporterAction controller) {
+        waypointTM = controller.getWaypointTableModel();
+        type = controller.getDialog().getStoptype();
+        workingLines = new Vector<>();
+
+        // use either selected lines or all lines if no line is selected
+        int[] selectedLines = controller.getDialog().getWaypointsTable().getSelectedRows();
+        Vector<Integer> consideredLines = new Vector<>();
+        if (selectedLines.length > 0) {
+            for (int i = 0; i < selectedLines.length; ++i) {
+                consideredLines.add(selectedLines[i]);
+            }
+        } else {
+            for (int i = 0; i < waypointTM.getRowCount(); ++i) {
+                consideredLines.add(Integer.valueOf(i));
+            }
+        }
+
+        // keep only lines where a node can be added
+        for (int i = 0; i < consideredLines.size(); ++i) {
+            if (waypointTM.nodes.elementAt(consideredLines.elementAt(i)) == null)
+                workingLines.add(consideredLines.elementAt(i));
+        }
+    }
+
+    @Override
+    public boolean executeCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = StopImporterAction.createNode(waypointTM.coors.elementAt(j), type,
+                    (String) waypointTM.getValueAt(j, 1));
+            TransText shelter = (TransText) waypointTM.getValueAt(j, 2);
+            node.put("shelter", shelter.text);
+            waypointTM.nodes.set(j, node);
+        }
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        for (int i = 0; i < workingLines.size(); ++i) {
+            int j = workingLines.elementAt(i).intValue();
+            Node node = waypointTM.nodes.elementAt(j);
+            waypointTM.nodes.set(j, null);
+            if (node == null)
+                continue;
+            MainApplication.getLayerManager().getEditDataSet().removePrimitive(node);
+            node.setDeleted(true);
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Enable waypoints");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsNameCommand.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsNameCommand.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/commands/WaypointsNameCommand.java	(revision 33765)
@@ -0,0 +1,78 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.commands;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.models.WaypointTableModel;
+
+public class WaypointsNameCommand extends Command {
+    private int workingLine = 0;
+
+    private WaypointTableModel waypointTM = null;
+
+    private String oldName = null;
+
+    private String name = null;
+
+    private String oldShelter = null;
+
+    private TransText shelter;
+
+    public WaypointsNameCommand(WaypointTableModel waypointTM, int workingLine, String name,
+            TransText shelter) {
+        this.waypointTM = waypointTM;
+        this.workingLine = workingLine;
+        if (waypointTM.nodes.elementAt(workingLine) != null) {
+            oldName = waypointTM.nodes.elementAt(workingLine).get("name");
+            oldShelter = waypointTM.nodes.elementAt(workingLine).get("shelter");
+        }
+        this.name = name;
+        this.shelter = shelter;
+    }
+
+    @Override
+    public boolean executeCommand() {
+        if (waypointTM.nodes.elementAt(workingLine) != null) {
+            waypointTM.nodes.elementAt(workingLine).put("name", name);
+            waypointTM.nodes.elementAt(workingLine).put("shelter", shelter.text);
+        }
+        waypointTM.inEvent = true;
+        if (name == null)
+            waypointTM.setValueAt("", workingLine, 1);
+        else
+            waypointTM.setValueAt(name, workingLine, 1);
+        waypointTM.setValueAt(shelter, workingLine, 2);
+        waypointTM.inEvent = false;
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        if (waypointTM.nodes.elementAt(workingLine) != null) {
+            waypointTM.nodes.elementAt(workingLine).put("name", oldName);
+            waypointTM.nodes.elementAt(workingLine).put("shelter", oldShelter);
+        }
+        waypointTM.inEvent = true;
+        if (oldName == null)
+            waypointTM.setValueAt("", workingLine, 1);
+        else
+            waypointTM.setValueAt(oldName, workingLine, 1);
+        waypointTM.setValueAt(new TransText(oldShelter), workingLine, 2);
+        waypointTM.inEvent = false;
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified,
+            Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Public Transport: Edit waypoint name");
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/AbstractImporterDialog.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/AbstractImporterDialog.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/AbstractImporterDialog.java	(revision 33765)
@@ -0,0 +1,158 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.dialogs;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Frame;
+
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+
+/**
+ * Abstract superclass of {@link GTFSImporterDialog} and {@link StopImporterDialog}.
+ */
+public abstract class AbstractImporterDialog<T extends JosmAction> {
+
+    private static final String[] stoptypes = new String[] {
+            marktr("bus"),
+            marktr("tram"),
+            marktr("light_rail"),
+            marktr("subway"),
+            marktr("rail") };
+
+    private final JDialog jDialog;
+
+    protected final JTabbedPane tabbedPane;
+
+    protected final JComboBox<TransText> cbStoptype;
+
+    protected final JTextField tfGPSTimeStart;
+
+    protected final JTextField tfStopwatchStart;
+
+    protected final JTextField tfTimeWindow;
+
+    protected final JTextField tfThreshold;
+
+    public AbstractImporterDialog(T controller, String dialogTitle, String actionPrefix) {
+        Frame frame = JOptionPane.getFrameForComponent(Main.parent);
+        jDialog = new JDialog(frame, dialogTitle, false);
+        tabbedPane = new JTabbedPane();
+        jDialog.add(tabbedPane);
+
+        cbStoptype = new JComboBox<>();
+        cbStoptype.setEditable(false);
+        for (String type : stoptypes) {
+            cbStoptype.addItem(new TransText(type));
+        }
+        cbStoptype.setActionCommand(actionPrefix + ".settingsStoptype");
+        cbStoptype.addActionListener(controller);
+
+        tfGPSTimeStart = new JTextField("00:00:00", 15);
+        tfGPSTimeStart.setActionCommand(actionPrefix + ".settingsGPSTimeStart");
+        tfGPSTimeStart.addActionListener(controller);
+
+        tfStopwatchStart = new JTextField("00:00:00", 15);
+        tfStopwatchStart.setActionCommand(actionPrefix + ".settingsStopwatchStart");
+        tfStopwatchStart.addActionListener(controller);
+
+        tfTimeWindow = new JTextField("15", 4);
+        tfTimeWindow.setActionCommand(actionPrefix + ".settingsTimeWindow");
+        tfTimeWindow.addActionListener(controller);
+
+        tfThreshold = new JTextField("20", 4);
+        tfThreshold.setActionCommand(actionPrefix + ".settingsThreshold");
+        tfThreshold.addActionListener(controller);
+
+        initDialog(controller);
+
+        jDialog.pack();
+        jDialog.setLocationRelativeTo(frame);
+    }
+
+    protected abstract void initDialog(T controller);
+
+    public void setTrackValid(boolean valid) {
+        tabbedPane.setEnabledAt(2, valid);
+    }
+
+    public void setVisible(boolean visible) {
+        jDialog.setVisible(visible);
+    }
+
+    public void setSettings(String gpsSyncTime, String stopwatchStart, double timeWindow,
+            double threshold) {
+        tfGPSTimeStart.setText(gpsSyncTime);
+        tfStopwatchStart.setText(stopwatchStart);
+        tfTimeWindow.setText(Double.toString(timeWindow));
+        tfThreshold.setText(Double.toString(threshold));
+    }
+
+    public String getStoptype() {
+        return ((TransText) cbStoptype.getSelectedItem()).text;
+    }
+
+    public boolean gpsTimeStartValid() {
+        if (parseTime(tfGPSTimeStart.getText()) >= 0) {
+            return true;
+        } else {
+            JOptionPane.showMessageDialog(null, tr("Can''t parse a time from this string."),
+                    tr("Invalid value"), JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+    }
+
+    public String getGpsTimeStart() {
+        return tfGPSTimeStart.getText();
+    }
+
+    public void setGpsTimeStart(String s) {
+        tfGPSTimeStart.setText(s);
+    }
+
+    public boolean stopwatchStartValid() {
+        if (parseTime(tfStopwatchStart.getText()) >= 0) {
+            return true;
+        } else {
+            JOptionPane.showMessageDialog(null, tr("Can''t parse a time from this string."),
+                    tr("Invalid value"), JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+    }
+
+    public String getStopwatchStart() {
+        return tfStopwatchStart.getText();
+    }
+
+    public void setStopwatchStart(String s) {
+        tfStopwatchStart.setText(s);
+    }
+
+    public double getTimeWindow() {
+        return Double.parseDouble(tfTimeWindow.getText());
+    }
+
+    public double getThreshold() {
+        return Double.parseDouble(tfThreshold.getText());
+    }
+
+    public static double parseTime(String s) {
+        if ((s.charAt(2) != ':') || (s.charAt(5) != ':') || (s.length() < 8))
+            return -1;
+        int hour = Integer.parseInt(s.substring(0, 2));
+        int minute = Integer.parseInt(s.substring(3, 5));
+        double second = Double.parseDouble(s.substring(6, s.length()));
+        if ((hour < 0) || (hour > 23) || (minute < 0) || (minute > 59) || (second < 0)
+                || (second >= 60.0))
+            return -1;
+        return (second + minute * 60 + hour * 60 * 60);
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/GTFSImporterDialog.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/GTFSImporterDialog.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/GTFSImporterDialog.java	(revision 33765)
@@ -0,0 +1,369 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.dialogs;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.GTFSStopTableModel;
+
+public class GTFSImporterDialog extends AbstractImporterDialog<GTFSImporterAction> {
+    private JTable gtfsStopTable = null;
+
+    public GTFSImporterDialog(GTFSImporterAction controller) {
+        super(controller, tr("Create Stops from GTFS"), "gtfsImporter");
+    }
+
+    @Override
+    protected void initDialog(GTFSImporterAction controller) {
+        JPanel tabSettings = new JPanel();
+        tabbedPane.addTab(tr("Settings"), tabSettings);
+        JPanel tabWaypoints = new JPanel();
+        tabbedPane.addTab(tr("GTFS-Stops"), tabWaypoints);
+        tabbedPane.setEnabledAt(0, false);
+        tabbedPane.setEnabledAt(1, true);
+
+        // Settings Tab
+        JPanel contentPane = tabSettings;
+        GridBagLayout gridbag = new GridBagLayout();
+        GridBagConstraints layoutCons = new GridBagConstraints();
+        contentPane.setLayout(gridbag);
+
+        JLabel label = new JLabel(tr("Type of stops to add"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 0;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(cbStoptype, layoutCons);
+        contentPane.add(cbStoptype);
+
+        label = new JLabel(tr("Time on your GPS device"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 3;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfGPSTimeStart, layoutCons);
+        contentPane.add(tfGPSTimeStart);
+
+        /* I18n: Don't change the time format, you only may translate the letters */
+        label = new JLabel(tr("HH:MM:SS.sss"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 3;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        label = new JLabel(tr("Time on your stopwatch"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 4;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 5;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfStopwatchStart, layoutCons);
+        contentPane.add(tfStopwatchStart);
+
+        /* I18n: Don't change the time format, you only may translate the letters */
+        label = new JLabel(tr("HH:MM:SS.sss"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 5;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        label = new JLabel(tr("Time window"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 6;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 7;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfTimeWindow, layoutCons);
+        contentPane.add(tfTimeWindow);
+
+        label = new JLabel(tr("seconds"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 7;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        label = new JLabel(tr("Move Threshold"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 8;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 9;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfThreshold, layoutCons);
+        contentPane.add(tfThreshold);
+
+        label = new JLabel(tr("meters"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 9;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        JButton bSuggestStops = new JButton(tr("Suggest Stops"));
+        bSuggestStops.setActionCommand("gtfsImporter.settingsSuggestStops");
+        bSuggestStops.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 10;
+        layoutCons.gridwidth = 3;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bSuggestStops, layoutCons);
+        contentPane.add(bSuggestStops);
+
+        // Waypoints Tab
+        contentPane = tabWaypoints;
+        gridbag = new GridBagLayout();
+        layoutCons = new GridBagConstraints();
+        contentPane.setLayout(gridbag);
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt N"), "gtfsImporter.gtfsStopsFocusAdd");
+        contentPane.getActionMap().put("gtfsImporter.gtfsStopsFocusAdd",
+                controller.getFocusAddAction());
+/*    contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put
+        (KeyStroke.getKeyStroke("alt S"), "gtfsImporter.focusShelterYes");
+    contentPane.getActionMap().put
+    ("gtfsImporter.focusShelterYes",
+     controller.getFocusWaypointShelterAction("yes"));
+    contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put
+        (KeyStroke.getKeyStroke("alt T"), "gtfsImporter.focusShelterNo");
+    contentPane.getActionMap().put
+    ("gtfsImporter.focusShelterNo",
+     controller.getFocusWaypointShelterAction("no"));
+    contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put
+        (KeyStroke.getKeyStroke("alt U"), "gtfsImporter.focusShelterImplicit");
+    contentPane.getActionMap().put
+    ("gtfsImporter.focusShelterImplicit",
+     controller.getFocusWaypointShelterAction("implicit"));
+    contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put
+        (KeyStroke.getKeyStroke("alt D"), "gtfsImporter.gtfsStopsDelete");
+    contentPane.getActionMap().put
+    ("gtfsImporter.gtfsStopsDelete",
+     controller.getFocusWaypointDeleteAction());*/
+
+        gtfsStopTable = new JTable();
+        JScrollPane tableSP = new JScrollPane(gtfsStopTable);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 0;
+        layoutCons.gridwidth = 4;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 1.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tableSP, layoutCons);
+        contentPane.add(tableSP);
+
+        JButton bFind = new JButton(tr("Find"));
+        bFind.setActionCommand("gtfsImporter.gtfsStopsFind");
+        bFind.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bFind, layoutCons);
+        contentPane.add(bFind);
+
+        JButton bShow = new JButton(tr("Show"));
+        bShow.setActionCommand("gtfsImporter.gtfsStopsShow");
+        bShow.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bShow, layoutCons);
+        contentPane.add(bShow);
+
+        JButton bMark = new JButton(tr("Mark"));
+        bMark.setActionCommand("gtfsImporter.gtfsStopsMark");
+        bMark.addActionListener(controller);
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bMark, layoutCons);
+        contentPane.add(bMark);
+
+        JButton bCatch = new JButton(tr("Catch"));
+        bCatch.setActionCommand("gtfsImporter.gtfsStopsCatch");
+        bCatch.addActionListener(controller);
+
+        layoutCons.gridx = 2;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bCatch, layoutCons);
+        contentPane.add(bCatch);
+
+        JButton bJoin = new JButton(tr("Join"));
+        bJoin.setActionCommand("gtfsImporter.gtfsStopsJoin");
+        bJoin.addActionListener(controller);
+
+        layoutCons.gridx = 2;
+        layoutCons.gridy = 2;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bJoin, layoutCons);
+        contentPane.add(bJoin);
+
+        JButton bAdd = new JButton(tr("Enable"));
+        bAdd.setActionCommand("gtfsImporter.gtfsStopsAdd");
+        bAdd.addActionListener(controller);
+
+        layoutCons.gridx = 3;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bAdd, layoutCons);
+        contentPane.add(bAdd);
+
+        JButton bDelete = new JButton(tr("Disable"));
+        bDelete.setActionCommand("gtfsImporter.gtfsStopsDelete");
+        bDelete.addActionListener(controller);
+
+        layoutCons.gridx = 3;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bDelete, layoutCons);
+        contentPane.add(bDelete);
+    }
+
+    public JTable getGTFSStopTable() {
+        return gtfsStopTable;
+    }
+
+    public void setGTFSStopTableModel(GTFSStopTableModel model) {
+        gtfsStopTable.setModel(model);
+        int width = gtfsStopTable.getPreferredSize().width;
+        gtfsStopTable.getColumnModel().getColumn(0).setPreferredWidth((int) (width * 0.3));
+        gtfsStopTable.getColumnModel().getColumn(1).setPreferredWidth((int) (width * 0.6));
+        gtfsStopTable.getColumnModel().getColumn(2).setPreferredWidth((int) (width * 0.1));
+    }
+
+/*  private class TracksLSL implements ListSelectionListener
+  {
+    GTFSImporterAction root = null;
+
+    public TracksLSL(GTFSImporterAction sia)
+    {
+      root = sia;
+    }
+
+    public void valueChanged(ListSelectionEvent e)
+    {
+      int selectedPos = tracksList.getAnchorSelectionIndex();
+      if (tracksList.isSelectedIndex(selectedPos))
+    root.tracksSelectionChanged(selectedPos);
+      else
+    root.tracksSelectionChanged(-1);
+    }
+  };*/
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/StopImporterDialog.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/StopImporterDialog.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/dialogs/StopImporterDialog.java	(revision 33765)
@@ -0,0 +1,559 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.dialogs;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.models.TrackStoplistTableModel;
+import org.openstreetmap.josm.plugins.public_transport.models.WaypointTableModel;
+import org.openstreetmap.josm.plugins.public_transport.refs.TrackReference;
+
+public class StopImporterDialog extends AbstractImporterDialog<StopImporterAction> {
+    private JList<TrackReference> tracksList = null;
+
+    private JTable stoplistTable = null;
+
+    private JTable waypointTable = null;
+
+    public StopImporterDialog(StopImporterAction controller) {
+        super(controller, tr("Create Stops from GPX"), "stopImporter");
+    }
+
+    @Override
+    protected void initDialog(StopImporterAction controller) {
+        JPanel tabTracks = new JPanel();
+        tabbedPane.addTab(tr("Tracks"), tabTracks);
+        JPanel tabSettings = new JPanel();
+        tabbedPane.addTab(tr("Settings"), tabSettings);
+        JPanel tabStops = new JPanel();
+        tabbedPane.addTab(tr("Stops"), tabStops);
+        JPanel tabWaypoints = new JPanel();
+        tabbedPane.addTab(tr("Waypoints"), tabWaypoints);
+        tabbedPane.setEnabledAt(0, true);
+        tabbedPane.setEnabledAt(1, true);
+        tabbedPane.setEnabledAt(2, false);
+        tabbedPane.setEnabledAt(3, true);
+
+        // Tracks Tab
+        JPanel contentPane = tabTracks;
+        GridBagLayout gridbag = new GridBagLayout();
+        GridBagConstraints layoutCons = new GridBagConstraints();
+        contentPane.setLayout(gridbag);
+
+        JLabel label = new JLabel(tr("Tracks in this GPX file:"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 0;
+        layoutCons.gridwidth = 3;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        DefaultListModel<TrackReference> tracksListModel = controller.getTracksListModel();
+        tracksList = new JList<>(tracksListModel);
+        JScrollPane rpListSP = new JScrollPane(tracksList);
+        String[] data = {"1", "2", "3", "4", "5", "6"};
+        tracksListModel.copyInto(data);
+        tracksList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        tracksList.addListSelectionListener(new TracksLSL(controller));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 1;
+        layoutCons.gridwidth = 3;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 1.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(rpListSP, layoutCons);
+        contentPane.add(rpListSP);
+
+        // Settings Tab
+        contentPane = tabSettings;
+        gridbag = new GridBagLayout();
+        layoutCons = new GridBagConstraints();
+        contentPane.setLayout(gridbag);
+
+        label = new JLabel(tr("Type of stops to add"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 0;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(cbStoptype, layoutCons);
+        contentPane.add(cbStoptype);
+
+        label = new JLabel(tr("Time on your GPS device"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 3;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfGPSTimeStart, layoutCons);
+        contentPane.add(tfGPSTimeStart);
+
+        label = new JLabel(tr("HH:MM:SS.sss"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 3;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        label = new JLabel(tr("Time on your stopwatch"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 4;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 5;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfStopwatchStart, layoutCons);
+        contentPane.add(tfStopwatchStart);
+
+        label = new JLabel(tr("HH:MM:SS.sss"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 5;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        label = new JLabel(tr("Time window"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 6;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 7;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfTimeWindow, layoutCons);
+        contentPane.add(tfTimeWindow);
+
+        label = new JLabel(tr("seconds"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 7;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        label = new JLabel(tr("Move Threshold"));
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 8;
+        layoutCons.gridwidth = 2;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 9;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tfThreshold, layoutCons);
+        contentPane.add(tfThreshold);
+
+        label = new JLabel(tr("meters"));
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 9;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 0.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(label, layoutCons);
+        contentPane.add(label);
+
+        JButton bSuggestStops = new JButton(tr("Suggest Stops"));
+        bSuggestStops.setActionCommand("stopImporter.settingsSuggestStops");
+        bSuggestStops.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 10;
+        layoutCons.gridwidth = 3;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bSuggestStops, layoutCons);
+        contentPane.add(bSuggestStops);
+
+        // Stops Tab
+        contentPane = tabStops;
+        gridbag = new GridBagLayout();
+        layoutCons = new GridBagConstraints();
+        contentPane.setLayout(gridbag);
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt N"), "stopImporter.focusName");
+        contentPane.getActionMap().put("stopImporter.focusName",
+                controller.getFocusTrackStoplistNameAction());
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt S"), "stopImporter.focusShelterYes");
+        contentPane.getActionMap().put("stopImporter.focusShelterYes",
+                controller.getFocusTrackStoplistShelterAction("yes"));
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt T"), "stopImporter.focusShelterNo");
+        contentPane.getActionMap().put("stopImporter.focusShelterNo",
+                controller.getFocusTrackStoplistShelterAction("no"));
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt U"), "stopImporter.focusShelterImplicit");
+        contentPane.getActionMap().put("stopImporter.focusShelterImplicit",
+                controller.getFocusTrackStoplistShelterAction("implicit"));
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt D"), "stopImporter.stoplistDelete");
+        contentPane.getActionMap().put("stopImporter.stoplistDelete",
+                controller.getFocusStoplistDeleteAction());
+
+        stoplistTable = new JTable();
+        JScrollPane tableSP = new JScrollPane(stoplistTable);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 0;
+        layoutCons.gridwidth = 4;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 1.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tableSP, layoutCons);
+        contentPane.add(tableSP);
+
+        JButton bFind = new JButton(tr("Find"));
+        bFind.setActionCommand("stopImporter.stoplistFind");
+        bFind.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bFind, layoutCons);
+        contentPane.add(bFind);
+
+        JButton bShow = new JButton(tr("Show"));
+        bShow.setActionCommand("stopImporter.stoplistShow");
+        bShow.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bShow, layoutCons);
+        contentPane.add(bShow);
+
+        JButton bMark = new JButton(tr("Mark"));
+        bMark.setActionCommand("stopImporter.stoplistMark");
+        bMark.addActionListener(controller);
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bMark, layoutCons);
+        contentPane.add(bMark);
+
+        JButton bDetach = new JButton(tr("Detach"));
+        bDetach.setActionCommand("stopImporter.stoplistDetach");
+        bDetach.addActionListener(controller);
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 2;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bDetach, layoutCons);
+        contentPane.add(bDetach);
+
+        JButton bAdd = new JButton(tr("Add"));
+        bAdd.setActionCommand("stopImporter.stoplistAdd");
+        bAdd.addActionListener(controller);
+
+        layoutCons.gridx = 2;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bAdd, layoutCons);
+        contentPane.add(bAdd);
+
+        JButton bDelete = new JButton(tr("Delete"));
+        bDelete.setActionCommand("stopImporter.stoplistDelete");
+        bDelete.addActionListener(controller);
+
+        layoutCons.gridx = 2;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bDelete, layoutCons);
+        contentPane.add(bDelete);
+
+        JButton bSort = new JButton(tr("Sort"));
+        bSort.setActionCommand("stopImporter.stoplistSort");
+        bSort.addActionListener(controller);
+
+        layoutCons.gridx = 3;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bSort, layoutCons);
+        contentPane.add(bSort);
+
+        // Waypoints Tab
+        contentPane = tabWaypoints;
+        gridbag = new GridBagLayout();
+        layoutCons = new GridBagConstraints();
+        contentPane.setLayout(gridbag);
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt N"), "stopImporter.focusName");
+        contentPane.getActionMap().put("stopImporter.focusName",
+                controller.getFocusWaypointNameAction());
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt S"), "stopImporter.focusShelterYes");
+        contentPane.getActionMap().put("stopImporter.focusShelterYes",
+                controller.getFocusWaypointShelterAction("yes"));
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt T"), "stopImporter.focusShelterNo");
+        contentPane.getActionMap().put("stopImporter.focusShelterNo",
+                controller.getFocusWaypointShelterAction("no"));
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt U"), "stopImporter.focusShelterImplicit");
+        contentPane.getActionMap().put("stopImporter.focusShelterImplicit",
+                controller.getFocusWaypointShelterAction("implicit"));
+        contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+                .put(KeyStroke.getKeyStroke("alt D"), "stopImporter.waypointsDelete");
+        contentPane.getActionMap().put("stopImporter.waypointsDelete",
+                controller.getFocusWaypointDeleteAction());
+
+        waypointTable = new JTable();
+        tableSP = new JScrollPane(waypointTable);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 0;
+        layoutCons.gridwidth = 3;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 1.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(tableSP, layoutCons);
+        contentPane.add(tableSP);
+
+        bFind = new JButton(tr("Find"));
+        bFind.setActionCommand("stopImporter.waypointsFind");
+        bFind.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bFind, layoutCons);
+        contentPane.add(bFind);
+
+        bShow = new JButton(tr("Show"));
+        bShow.setActionCommand("stopImporter.waypointsShow");
+        bShow.addActionListener(controller);
+
+        layoutCons.gridx = 0;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bShow, layoutCons);
+        contentPane.add(bShow);
+
+        bMark = new JButton(tr("Mark"));
+        bMark.setActionCommand("stopImporter.waypointsMark");
+        bMark.addActionListener(controller);
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bMark, layoutCons);
+        contentPane.add(bMark);
+
+        bDetach = new JButton(tr("Detach"));
+        bDetach.setActionCommand("stopImporter.waypointsDetach");
+        bDetach.addActionListener(controller);
+
+        layoutCons.gridx = 1;
+        layoutCons.gridy = 2;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bDetach, layoutCons);
+        contentPane.add(bDetach);
+
+        bAdd = new JButton(tr("Enable"));
+        bAdd.setActionCommand("stopImporter.waypointsAdd");
+        bAdd.addActionListener(controller);
+
+        layoutCons.gridx = 2;
+        layoutCons.gridy = 1;
+        layoutCons.gridheight = 1;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bAdd, layoutCons);
+        contentPane.add(bAdd);
+
+        bDelete = new JButton(tr("Disable"));
+        bDelete.setActionCommand("stopImporter.waypointsDelete");
+        bDelete.addActionListener(controller);
+
+        layoutCons.gridx = 2;
+        layoutCons.gridy = 2;
+        layoutCons.gridwidth = 1;
+        layoutCons.weightx = 1.0;
+        layoutCons.weighty = 0.0;
+        layoutCons.fill = GridBagConstraints.BOTH;
+        gridbag.setConstraints(bDelete, layoutCons);
+        contentPane.add(bDelete);
+    }
+
+    public JTable getStoplistTable() {
+        return stoplistTable;
+    }
+
+    public void setStoplistTableModel(TrackStoplistTableModel model) {
+        stoplistTable.setModel(model);
+        JComboBox<TransText> comboBox = new JComboBox<>();
+        comboBox.addItem(new TransText(null));
+        comboBox.addItem(new TransText(marktr("yes")));
+        comboBox.addItem(new TransText(marktr("no")));
+        comboBox.addItem(new TransText(marktr("implicit")));
+        stoplistTable.getColumnModel().getColumn(2).setCellEditor(new DefaultCellEditor(comboBox));
+        int width = stoplistTable.getPreferredSize().width;
+        stoplistTable.getColumnModel().getColumn(0).setPreferredWidth((int) (width * 0.4));
+        stoplistTable.getColumnModel().getColumn(1).setPreferredWidth((int) (width * 0.5));
+        stoplistTable.getColumnModel().getColumn(2).setPreferredWidth((int) (width * 0.1));
+    }
+
+    public JTable getWaypointsTable() {
+        return waypointTable;
+    }
+
+    public void setWaypointsTableModel(WaypointTableModel model) {
+        waypointTable.setModel(model);
+        JComboBox<TransText> comboBox = new JComboBox<>();
+        comboBox.addItem(new TransText(null));
+        comboBox.addItem(new TransText(marktr("yes")));
+        comboBox.addItem(new TransText(marktr("no")));
+        comboBox.addItem(new TransText(marktr("implicit")));
+        waypointTable.getColumnModel().getColumn(2).setCellEditor(new DefaultCellEditor(comboBox));
+        int width = waypointTable.getPreferredSize().width;
+        waypointTable.getColumnModel().getColumn(0).setPreferredWidth((int) (width * 0.4));
+        waypointTable.getColumnModel().getColumn(1).setPreferredWidth((int) (width * 0.5));
+        waypointTable.getColumnModel().getColumn(2).setPreferredWidth((int) (width * 0.1));
+    }
+
+    private class TracksLSL implements ListSelectionListener {
+        StopImporterAction root = null;
+
+        TracksLSL(StopImporterAction sia) {
+            root = sia;
+        }
+
+        @Override
+        public void valueChanged(ListSelectionEvent e) {
+            int selectedPos = tracksList.getAnchorSelectionIndex();
+            if (tracksList.isSelectedIndex(selectedPos))
+                root.tracksSelectionChanged(selectedPos);
+            else
+                root.tracksSelectionChanged(-1);
+        }
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/GTFSStopTableModel.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/GTFSStopTableModel.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/GTFSStopTableModel.java	(revision 33765)
@@ -0,0 +1,220 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.models;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.plugins.public_transport.actions.GTFSImporterAction;
+
+public class GTFSStopTableModel extends DefaultTableModel implements TableModelListener {
+    private GTFSImporterAction controller = null;
+
+    public Vector<Node> nodes = new Vector<>();
+
+    public Vector<LatLon> coors = new Vector<>();
+
+    private int idCol = -1;
+
+    private int nameCol = -1;
+
+    private int latCol = -1;
+
+    private int lonCol = -1;
+
+    private char separator = ',';
+
+    public GTFSStopTableModel(GTFSImporterAction controller, String columnConfig) {
+        int pos = columnConfig.indexOf(separator);
+        if (pos == -1) {
+            separator = ';';
+            pos = columnConfig.indexOf(separator);
+        }
+        if (pos == -1) {
+            separator = '\t';
+            pos = columnConfig.indexOf(separator);
+        }
+        int oldPos = 0;
+        int i = 0;
+        while (pos > -1) {
+            String title = stripQuot(columnConfig.substring(oldPos, pos));
+            if ("stop_id".equals(title))
+                idCol = i;
+            else if ("stop_name".equals(title))
+                nameCol = i;
+            else if ("stop_lat".equals(title))
+                latCol = i;
+            else if ("stop_lon".equals(title))
+                lonCol = i;
+            ++i;
+            oldPos = pos + 1;
+            pos = columnConfig.indexOf(separator, oldPos);
+        }
+        String title = columnConfig.substring(oldPos);
+        if ("stop_id".equals(title))
+            idCol = i;
+        else if ("stop_name".equals(title))
+            nameCol = i;
+        else if ("stop_lat".equals(title))
+            latCol = i;
+        else if ("stop_lon".equals(title))
+            lonCol = i;
+
+        this.controller = controller;
+        addColumn(tr("Id"));
+        addColumn(tr("Name"));
+        addColumn(tr("State"));
+        addTableModelListener(this);
+    }
+
+    @Override
+    public boolean isCellEditable(int row, int column) {
+        return false;
+    }
+
+    @Override
+    public void addRow(Object[] obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void insertRow(int insPos, Object[] obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addRow(String s) {
+        insertRow(-1, s, new Vector<Node>());
+    }
+
+    public void addRow(String s, Vector<Node> existingStops) {
+        insertRow(-1, s, existingStops);
+    }
+
+    /**
+     * tokenizes a line as follows: any comma outside a pair of double quotation marks is taken as field separator.
+     * In particular, neither \" nor \, have a special meaning. Returns the position of the next field separator, if any.
+     * Otherwise it returns -1. s - the string to tokenize. startPos
+     * - the position of the last field separator plus 1 or the value 0.
+     */
+    private int tokenize(String s, int startPos) {
+        int pos = startPos;
+        boolean insideDoubleQuoted = false;
+        while (pos < s.length()) {
+            if ('"' == s.charAt(pos))
+                insideDoubleQuoted = !insideDoubleQuoted;
+            else if ((separator == s.charAt(pos)) && (!insideDoubleQuoted))
+                break;
+            ++pos;
+        }
+        if (pos < s.length())
+            return pos;
+        else
+            return -1;
+    }
+
+    private String stripQuot(String s) {
+        int pos = s.indexOf('"');
+        while (pos > -1) {
+            s = s.substring(0, pos) + s.substring(pos + 1);
+            pos = s.indexOf('"');
+        }
+        return s;
+    }
+
+    public void insertRow(int insPos, String s, Vector<Node> existingStops) {
+        String[] buf = {"", "", tr("pending")};
+        int pos = tokenize(s, 0);
+        int oldPos = 0;
+        int i = 0;
+        double lat = 0;
+        double lon = 0;
+        while (pos > -1) {
+            if (i == idCol)
+                buf[0] = stripQuot(s.substring(oldPos, pos));
+            else if (i == nameCol)
+                buf[1] = stripQuot(s.substring(oldPos, pos));
+            else if (i == latCol)
+                lat = Double.parseDouble(stripQuot(s.substring(oldPos, pos)));
+            else if (i == lonCol)
+                lon = Double.parseDouble(stripQuot(s.substring(oldPos, pos)));
+            ++i;
+            oldPos = pos + 1;
+            pos = tokenize(s, oldPos);
+        }
+        if (i == idCol)
+            buf[0] = stripQuot(s.substring(oldPos));
+        else if (i == nameCol)
+            buf[1] = stripQuot(s.substring(oldPos));
+        else if (i == latCol)
+            lat = Double.parseDouble(stripQuot(s.substring(oldPos)));
+        else if (i == lonCol)
+            lon = Double.parseDouble(stripQuot(s.substring(oldPos)));
+
+        LatLon coor = new LatLon(lat, lon);
+
+        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
+        if (ds != null) {
+            boolean inside = false;
+            Iterator<DataSource> iter = ds.getDataSources().iterator();
+            while (iter.hasNext()) {
+                if (iter.next().bounds.contains(coor)) {
+                    inside = true;
+                    break;
+                }
+            }
+            if (!inside)
+                buf[2] = tr("outside");
+        }
+
+        boolean nearBusStop = false;
+        Iterator<Node> iter = existingStops.iterator();
+        while (iter.hasNext()) {
+            Node node = iter.next();
+            if (coor.greatCircleDistance(node.getCoor()) < 1000) {
+                nearBusStop = true;
+                break;
+            }
+        }
+
+        if (insPos == -1) {
+            if ((nearBusStop) || !(tr("pending").equals(buf[2])))
+                nodes.addElement(null);
+            else {
+                Node node = GTFSImporterAction.createNode(coor, buf[0], buf[1]);
+                nodes.addElement(node);
+                buf[2] = tr("added");
+            }
+            coors.addElement(coor);
+            super.addRow(buf);
+        } else {
+            if ((nearBusStop) || !(tr("pending").equals(buf[2])))
+                nodes.insertElementAt(null, insPos);
+            else {
+                Node node = GTFSImporterAction.createNode(coor, buf[0], buf[1]);
+                nodes.insertElementAt(node, insPos);
+                buf[2] = tr("added");
+            }
+            coors.insertElementAt(coor, insPos);
+            super.insertRow(insPos, buf);
+        }
+    }
+
+    public void clear() {
+        nodes.clear();
+        super.setRowCount(0);
+    }
+
+    @Override
+    public void tableChanged(TableModelEvent e) {
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/ItineraryTableModel.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/ItineraryTableModel.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/ItineraryTableModel.java	(revision 33765)
@@ -0,0 +1,150 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.models;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Vector;
+
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.plugins.public_transport.actions.RoutePatternAction;
+
+public class ItineraryTableModel extends DefaultTableModel implements TableModelListener {
+    public Vector<Way> ways = new Vector<>();
+
+    public boolean inEvent = false;
+
+    @Override
+    public boolean isCellEditable(int row, int column) {
+        if (column != 1)
+            return false;
+        if (ways.elementAt(row) == null)
+            return false;
+        return true;
+    }
+
+    @Override
+    public void addRow(Object[] obj) {
+        ways.addElement(null);
+        super.addRow(obj);
+    }
+
+    @Override
+    public void insertRow(int insPos, Object[] obj) {
+        if (insPos == -1) {
+            ways.addElement(null);
+            super.addRow(obj);
+        } else {
+            ways.insertElementAt(null, insPos);
+            super.insertRow(insPos, obj);
+        }
+    }
+
+    public void addRow(Way way, String role) {
+        insertRow(-1, way, role);
+    }
+
+    public void insertRow(int insPos, Way way, String role) {
+        String[] buf = {"", ""};
+        String curName = way.get("name");
+        if (way.isIncomplete())
+            buf[0] = tr("[incomplete]");
+        else if (way.getNodesCount() < 1)
+            buf[0] = tr("[empty way]");
+        else if (curName != null)
+            buf[0] = curName;
+        else
+            buf[0] = tr("[ID] {0}", (Long.valueOf(way.getId())).toString());
+        buf[1] = role;
+        if (insPos == -1) {
+            ways.addElement(way);
+            super.addRow(buf);
+        } else {
+            ways.insertElementAt(way, insPos);
+            super.insertRow(insPos, buf);
+        }
+    }
+
+    public void clear() {
+        ways.clear();
+        super.setRowCount(0);
+    }
+
+    public void cleanupGaps() {
+        inEvent = true;
+        Node lastNode = null;
+
+        for (int i = 0; i < getRowCount(); ++i) {
+            if (ways.elementAt(i) == null) {
+                ++i;
+                if (i >= getRowCount())
+                    break;
+            }
+            while ((ways.elementAt(i) == null) && ((i == 0) || (ways.elementAt(i - 1) == null))) {
+                ways.removeElementAt(i);
+                removeRow(i);
+                if (i >= getRowCount())
+                    break;
+            }
+            if (i >= getRowCount())
+                break;
+
+            boolean gapRequired = gapNecessary(ways.elementAt(i), (String) (getValueAt(i, 1)),
+                    lastNode);
+            if ((i > 0) && (!gapRequired) && (ways.elementAt(i - 1) == null)) {
+                ways.removeElementAt(i - 1);
+                removeRow(i - 1);
+                --i;
+            } else if ((i > 0) && gapRequired && (ways.elementAt(i - 1) != null)) {
+                String[] buf = {"", ""};
+                buf[0] = tr("[gap]");
+                insertRow(i, buf);
+                ++i;
+            }
+            lastNode = getLastNode(ways.elementAt(i), (String) (getValueAt(i, 1)));
+        }
+        while ((getRowCount() > 0) && (ways.elementAt(getRowCount() - 1) == null)) {
+            ways.removeElementAt(getRowCount() - 1);
+            removeRow(getRowCount() - 1);
+        }
+        inEvent = false;
+    }
+
+    @Override
+    public void tableChanged(TableModelEvent e) {
+        if (e.getType() == TableModelEvent.UPDATE) {
+            if (inEvent)
+                return;
+            cleanupGaps();
+            RoutePatternAction.rebuildWays();
+        }
+    }
+
+    private Node getLastNode(Way way, String role) {
+        if ((way == null) || (way.isIncomplete()) || (way.getNodesCount() < 1))
+            return null;
+        else {
+            if ("backward".equals(role))
+                return way.getNode(0);
+            else
+                return way.getNode(way.getNodesCount() - 1);
+        }
+    }
+
+    private boolean gapNecessary(Way way, String role, Node lastNode) {
+        if ((way != null) && (!(way.isIncomplete())) && (way.getNodesCount() >= 1)) {
+            Node firstNode = null;
+            if ("backward".equals(role))
+                firstNode = way.getNode(way.getNodesCount() - 1);
+            else
+                firstNode = way.getNode(0);
+            if ((lastNode != null) && (!lastNode.equals(firstNode)))
+                return true;
+        }
+        return false;
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/TrackStoplistTableModel.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/TrackStoplistTableModel.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/TrackStoplistTableModel.java	(revision 33765)
@@ -0,0 +1,119 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.models;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Vector;
+
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.refs.TrackReference;
+
+public class TrackStoplistTableModel extends DefaultTableModel {
+    private Vector<Node> nodes = null;
+
+    private Vector<String> times = null;
+
+    private static Vector<String> columns = null;
+
+    public TrackStoplistTableModel(TrackReference tr) {
+        if (columns == null) {
+            columns = new Vector<>();
+            columns.add(tr("Time"));
+            columns.add(tr("Name"));
+            columns.add(tr("Shelter"));
+        }
+        nodes = new Vector<>();
+        times = new Vector<>();
+
+        setColumnIdentifiers(columns);
+        addTableModelListener(tr);
+    }
+
+    @Override
+    public boolean isCellEditable(int row, int column) {
+        return true;
+    }
+
+    @Override
+    public void addRow(Object[] obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void insertRow(int insPos, Object[] obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addRow(String time) {
+        insertRow(-1, time);
+    }
+
+    public void insertRow(int insPos, String time) {
+        insertRow(insPos, null, time, "", new TransText(null));
+    }
+
+    @Override
+    public void removeRow(int pos) {
+        super.removeRow(pos);
+        nodes.removeElementAt(pos);
+        times.removeElementAt(pos);
+    }
+
+    public Node nodeAt(int i) {
+        return nodes.elementAt(i);
+    }
+
+    public void setNodeAt(int i, Node node) {
+        nodes.set(i, node);
+    }
+
+    public final Vector<Node> getNodes() {
+        return nodes;
+    }
+
+    public void setNodes(Vector<Node> nodes) {
+        this.nodes = nodes;
+    }
+
+    public String timeAt(int i) {
+        return times.elementAt(i);
+    }
+
+    public void setTimeAt(int i, String time) {
+        times.set(i, time);
+    }
+
+    public final Vector<String> getTimes() {
+        return times;
+    }
+
+    public void setTimes(Vector<String> times) {
+        this.times = times;
+    }
+
+    public void insertRow(int insPos, Node node, String time, String name, TransText shelter) {
+        Object[] buf = {time, name, shelter};
+        if (insPos == -1) {
+            nodes.addElement(node);
+            times.addElement(time);
+            super.addRow(buf);
+        } else {
+            nodes.insertElementAt(node, insPos);
+            times.insertElementAt(time, insPos);
+            super.insertRow(insPos, buf);
+        }
+    }
+
+    public void clear() {
+        nodes.clear();
+        times.clear();
+        super.setRowCount(0);
+    }
+
+    public void setDataVector(Vector<Vector<Object>> dataVector) {
+        setDataVector(dataVector, columns);
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/WaypointTableModel.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/WaypointTableModel.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/models/WaypointTableModel.java	(revision 33765)
@@ -0,0 +1,95 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.models;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Vector;
+
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.plugins.public_transport.TransText;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.commands.WaypointsNameCommand;
+
+public class WaypointTableModel extends DefaultTableModel implements TableModelListener {
+    private StopImporterAction controller = null;
+
+    public boolean inEvent = false;
+
+    public Vector<Node> nodes = new Vector<>();
+
+    public Vector<LatLon> coors = new Vector<>();
+
+    public WaypointTableModel(StopImporterAction controller) {
+        this.controller = controller;
+        addColumn(tr("Time"));
+        addColumn(tr("Stopname"));
+        addColumn(tr("Shelter"));
+        addTableModelListener(this);
+    }
+
+    @Override
+    public boolean isCellEditable(int row, int column) {
+        if (column >= 1)
+            return true;
+        return false;
+    }
+
+    @Override
+    public void addRow(Object[] obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void insertRow(int insPos, Object[] obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addRow(WayPoint wp) {
+        insertRow(-1, wp);
+    }
+
+    public void insertRow(int insPos, WayPoint wp) {
+        String time = wp.getString("time");
+        if (time == null)
+            time = "";
+        String name = wp.getString("name");
+        if (name == null)
+            name = "";
+
+        Node node = controller.createNode(wp.getCoor(), name);
+
+        Object[] buf = {time, name, new TransText(null)};
+        if (insPos == -1) {
+            nodes.addElement(node);
+            coors.addElement(wp.getCoor());
+            super.addRow(buf);
+        } else {
+            nodes.insertElementAt(node, insPos);
+            coors.insertElementAt(wp.getCoor(), insPos);
+            super.insertRow(insPos, buf);
+        }
+    }
+
+    public void clear() {
+        nodes.clear();
+        super.setRowCount(0);
+    }
+
+    @Override
+    public void tableChanged(TableModelEvent e) {
+        if (e.getType() == TableModelEvent.UPDATE) {
+            if (inEvent)
+                return;
+            Main.main.undoRedo.add(new WaypointsNameCommand(this, e.getFirstRow(),
+                    (String) getValueAt(e.getFirstRow(), 1),
+                    (TransText) getValueAt(e.getFirstRow(), 2)));
+        }
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/RouteReference.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/RouteReference.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/RouteReference.java	(revision 33765)
@@ -0,0 +1,77 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.refs;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.data.osm.Relation;
+
+public class RouteReference implements Comparable<RouteReference> {
+    public final Relation route;
+
+    public RouteReference(Relation route) {
+        this.route = route;
+    }
+
+    @Override
+    public int compareTo(RouteReference rr) {
+        if (route.get("route") != null) {
+            if (rr.route.get("route") == null)
+                return -1;
+            int result = route.get("route").compareTo(rr.route.get("route"));
+            if (result != 0)
+                return result;
+        } else if (rr.route.get("route") != null)
+            return 1;
+        if (route.get("ref") != null) {
+            if (rr.route.get("ref") == null)
+                return -1;
+            int result = route.get("ref").compareTo(rr.route.get("ref"));
+            if (result != 0)
+                return result;
+        } else if (rr.route.get("ref") != null)
+            return 1;
+        if (route.get("to") != null) {
+            if (rr.route.get("to") == null)
+                return -1;
+            int result = route.get("to").compareTo(rr.route.get("to"));
+            if (result != 0)
+                return result;
+        } else if (rr.route.get("to") != null)
+            return 1;
+        if (route.get("direction") != null) {
+            if (rr.route.get("direction") == null)
+                return -1;
+            int result = route.get("direction").compareTo(rr.route.get("direction"));
+            if (result != 0)
+                return result;
+        } else if (rr.route.get("direction") != null)
+            return 1;
+        if (route.getId() < rr.route.getId())
+            return -1;
+        else if (route.getId() > rr.route.getId())
+            return 1;
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        String buf = route.get("route");
+        if ((route.get("ref") != null) && (route.get("ref") != "")) {
+            buf += " " + route.get("ref");
+        }
+        if ((route.get("loc_ref") != null) && (route.get("loc_ref") != "")) {
+            buf += " [" + route.get("loc_ref") + "]";
+        }
+
+        if ((route.get("to") != null) && (route.get("to") != "")) {
+            buf += ": " + route.get("to");
+        } else if ((route.get("direction") != null) && (route.get("direction") != "")) {
+            buf += " " + route.get("ref") + ": " + route.get("direction");
+        } else {
+            buf += " " + route.get("ref");
+        }
+        buf += tr(" [ID] {0}", Long.toString(route.getId()));
+
+        return buf;
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/StopReference.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/StopReference.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/StopReference.java	(revision 33765)
@@ -0,0 +1,41 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.refs;
+
+import org.openstreetmap.josm.data.osm.Node;
+
+public class StopReference implements Comparable<StopReference> {
+    public int index = 0;
+
+    public double pos = 0;
+
+    public double distance = 0;
+
+    public String name = "";
+
+    public String role = "";
+
+    public Node node;
+
+    public StopReference(int inIndex, double inPos, double inDistance, String inName,
+            String inRole, Node inNode) {
+        index = inIndex;
+        pos = inPos;
+        distance = inDistance;
+        name = inName;
+        role = inRole;
+        node = inNode;
+    }
+
+    @Override
+    public int compareTo(StopReference sr) {
+        if (this.index < sr.index)
+            return -1;
+        if (this.index > sr.index)
+            return 1;
+        if (this.pos < sr.pos)
+            return -1;
+        if (this.pos > sr.pos)
+            return 1;
+        return 0;
+    }
+}
Index: /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/TrackReference.java
===================================================================
--- /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/TrackReference.java	(revision 33765)
+++ /applications/editors/josm/plugins/public_transport/src/org/openstreetmap/josm/plugins/public_transport/refs/TrackReference.java	(revision 33765)
@@ -0,0 +1,188 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.public_transport.refs;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Iterator;
+
+import javax.swing.JOptionPane;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.plugins.public_transport.actions.StopImporterAction;
+import org.openstreetmap.josm.plugins.public_transport.commands.TrackStoplistNameCommand;
+import org.openstreetmap.josm.plugins.public_transport.dialogs.StopImporterDialog;
+import org.openstreetmap.josm.plugins.public_transport.models.TrackStoplistTableModel;
+
+public class TrackReference implements Comparable<TrackReference>, TableModelListener {
+    public GpxTrack track;
+
+    public TrackStoplistTableModel stoplistTM;
+
+    public String stopwatchStart;
+
+    public String gpsStartTime;
+
+    public String gpsSyncTime;
+
+    public double timeWindow;
+
+    public double threshold;
+
+    private StopImporterAction controller = null;
+
+    public boolean inEvent = false;
+
+    public TrackReference(GpxTrack track, StopImporterAction controller) {
+        this.track = track;
+        this.stoplistTM = new TrackStoplistTableModel(this);
+        this.stopwatchStart = "00:00:00";
+        this.gpsStartTime = null;
+        this.gpsSyncTime = null;
+        this.controller = controller;
+        if (track != null) {
+            Iterator<GpxTrackSegment> siter = track.getSegments().iterator();
+            while ((siter.hasNext()) && (this.gpsSyncTime == null)) {
+                Iterator<WayPoint> witer = siter.next().getWayPoints().iterator();
+                if (witer.hasNext()) {
+                    this.gpsStartTime = witer.next().getString("time");
+                    if (this.gpsStartTime != null)
+                        this.gpsSyncTime = this.gpsStartTime.substring(11, 19);
+                }
+            }
+            if (this.gpsSyncTime == null) {
+                JOptionPane.showMessageDialog(null,
+                        tr("The GPX file doesn''t contain valid trackpoints. "
+                                + "Please use a GPX file that has trackpoints."),
+                        tr("GPX File Trouble"), JOptionPane.ERROR_MESSAGE);
+
+                this.gpsStartTime = "1970-01-01T00:00:00Z";
+                this.gpsSyncTime = this.stopwatchStart;
+            }
+        } else
+            this.gpsSyncTime = this.stopwatchStart;
+        this.timeWindow = 20;
+        this.threshold = 20;
+    }
+
+    public GpxTrack getGpxTrack() {
+        return track;
+    }
+
+    @Override
+    public int compareTo(TrackReference tr) {
+        String name = (String) track.getAttributes().get("name");
+        String tr_name = (String) tr.track.getAttributes().get("name");
+        if (name != null) {
+            if (tr_name == null)
+                return -1;
+            return name.compareTo(tr_name);
+        }
+        return 1;
+    }
+
+    @Override
+    public String toString() {
+        String buf = (String) track.getAttributes().get("name");
+        if (buf == null)
+            return tr("unnamed");
+        return buf;
+    }
+
+    @Override
+    public void tableChanged(TableModelEvent e) {
+        if ((e.getType() == TableModelEvent.UPDATE) && (e.getFirstRow() >= 0)) {
+            if (inEvent)
+                return;
+
+            double time = StopImporterDialog
+                    .parseTime((String) stoplistTM.getValueAt(e.getFirstRow(), 0));
+            if (time < 0) {
+                stoplistTM.setValueAt(stoplistTM.timeAt(e.getFirstRow()), e.getFirstRow(), 0);
+                JOptionPane.showMessageDialog(null, tr("Can''t parse a time from this string."),
+                        tr("Invalid value"), JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+
+            Main.main.undoRedo.add(new TrackStoplistNameCommand(this, e.getFirstRow()));
+            stoplistTM.setTimeAt(e.getFirstRow(),
+                    (String) stoplistTM.getValueAt(e.getFirstRow(), 0));
+        }
+    }
+
+    public LatLon computeCoor(double time) {
+        double gpsSyncTime = StopImporterDialog.parseTime(this.gpsSyncTime);
+        double dGpsStartTime = StopImporterDialog.parseTime(gpsStartTime);
+        if (gpsSyncTime < dGpsStartTime - 12 * 60 * 60)
+            gpsSyncTime += 24 * 60 * 60;
+        double timeDelta = gpsSyncTime - StopImporterDialog.parseTime(stopwatchStart);
+        time += timeDelta;
+
+        WayPoint wayPoint = null;
+        WayPoint lastWayPoint = null;
+        double wayPointTime = 0;
+        double lastWayPointTime = 0;
+        Iterator<GpxTrackSegment> siter = track.getSegments().iterator();
+        while (siter.hasNext()) {
+            Iterator<WayPoint> witer = siter.next().getWayPoints().iterator();
+            while (witer.hasNext()) {
+                wayPoint = witer.next();
+                String startTime = wayPoint.getString("time");
+                wayPointTime = StopImporterDialog.parseTime(startTime.substring(11, 19));
+                if (startTime.substring(11, 19).compareTo(gpsStartTime.substring(11, 19)) == -1)
+                    wayPointTime += 24 * 60 * 60;
+                if (wayPointTime >= time)
+                    break;
+                lastWayPoint = wayPoint;
+                lastWayPointTime = wayPointTime;
+            }
+            if (wayPointTime >= time)
+                break;
+        }
+
+        double lat = 0;
+        if ((wayPointTime == lastWayPointTime) || (lastWayPoint == null))
+            lat = wayPoint.getCoor().lat();
+        else
+            lat = wayPoint.getCoor().lat() * (time - lastWayPointTime)
+                    / (wayPointTime - lastWayPointTime)
+                    + lastWayPoint.getCoor().lat() * (wayPointTime - time)
+                            / (wayPointTime - lastWayPointTime);
+        double lon = 0;
+        if ((wayPointTime == lastWayPointTime) || (lastWayPoint == null))
+            lon = wayPoint.getCoor().lon();
+        else
+            lon = wayPoint.getCoor().lon() * (time - lastWayPointTime)
+                    / (wayPointTime - lastWayPointTime)
+                    + lastWayPoint.getCoor().lon() * (wayPointTime - time)
+                            / (wayPointTime - lastWayPointTime);
+
+        return new LatLon(lat, lon);
+    }
+
+    public void relocateNodes() {
+        for (int i = 0; i < stoplistTM.getNodes().size(); ++i) {
+            Node node = stoplistTM.nodeAt(i);
+            if (node == null)
+                continue;
+
+            double time = StopImporterDialog.parseTime((String) stoplistTM.getValueAt(i, 0));
+            LatLon latLon = computeCoor(time);
+
+            Node newNode = new Node(node);
+            newNode.setCoor(latLon);
+            Command cmd = new ChangeCommand(node, newNode);
+            if (cmd != null) {
+                Main.main.undoRedo.add(cmd);
+            }
+        }
+    }
+}
